-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from fajarnugraha37/develop
Develop
- Loading branch information
Showing
10 changed files
with
439 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import type { PRNG } from "../types/index.d.ts"; | ||
import { GLOBAL } from "./const.ts"; | ||
import { randomChar } from "./util.ts"; | ||
|
||
/** | ||
* Extracts the timestamp given a valid ULID. | ||
* | ||
* @example encode the time to ULID | ||
* ```ts | ||
* import { encodeTime } from "@std/ulid"; | ||
* import { assertEquals } from "@std/assert"; | ||
* | ||
* assertEquals("01ARYZ6S41", encodeTime(1469918176385, 10)); | ||
* ``` | ||
* | ||
* @param now The number of milliseconds since the Unix epoch. | ||
* @param len length of the generated string. | ||
* @returns The ULID to extract the timestamp from. | ||
*/ | ||
export function encodeTime(now: number, len: number = GLOBAL.TIME_LEN): string { | ||
if (now > GLOBAL.TIME_MAX) { | ||
throw new Deno.errors.InvalidData( | ||
"cannot encode time greater than " + GLOBAL.TIME_MAX, | ||
); | ||
} | ||
if (now < 0) { | ||
throw new Deno.errors.InvalidData("time must be positive"); | ||
} | ||
if (Number.isInteger(now) === false) { | ||
throw new Deno.errors.InvalidData("time must be an integer"); | ||
} | ||
let str = ""; | ||
for (; len > 0; len--) { | ||
const mod = now % GLOBAL.ENCODING_LEN; | ||
str = GLOBAL.ENCODING[mod] + str; | ||
now = (now - mod) / GLOBAL.ENCODING_LEN; | ||
} | ||
|
||
return str; | ||
} | ||
|
||
/** | ||
* Extracts the number of milliseconds since the Unix epoch that had passed when | ||
* the ULID was generated. If the ULID is malformed, an error will be thrown. | ||
* | ||
* @example Decode the time from a ULID | ||
* ```ts | ||
* import { decodeTime, ulid } from "@std/ulid"; | ||
* import { assertEquals } from "@std/assert"; | ||
* | ||
* const timestamp = 150_000; | ||
* const ulidString = ulid(timestamp); | ||
* | ||
* assertEquals(decodeTime(ulidString), timestamp); | ||
* ``` | ||
* | ||
* @param ulid The ULID to extract the timestamp from. | ||
* @returns The number of milliseconds since the Unix epoch that had passed when the ULID was generated. | ||
*/ | ||
export function decodeTime(id: string): number { | ||
if (id.length !== GLOBAL.TIME_LEN + GLOBAL.RANDOM_LEN) { | ||
throw new Deno.errors.InvalidData("malformed ulid"); | ||
} | ||
const time = id | ||
.substring(0, GLOBAL.TIME_LEN) | ||
.split("") | ||
.reverse() | ||
.reduce((carry, char, index) => { | ||
const encodingIndex = GLOBAL.ENCODING.indexOf(char); | ||
if (encodingIndex === -1) { | ||
throw new Deno.errors.InvalidData("invalid character found: " + char); | ||
} | ||
return (carry += encodingIndex * Math.pow(GLOBAL.ENCODING_LEN, index)); | ||
}, 0); | ||
if (time > GLOBAL.TIME_MAX) { | ||
throw new Deno.errors.InvalidData("malformed ulid, timestamp too large"); | ||
} | ||
|
||
return time; | ||
} | ||
|
||
/** | ||
* Encodes a random string of specified length using the provided PRNG. | ||
* | ||
* This function iterates for the specified length, calling the `randomChar` function with the PRNG | ||
* to generate a random character and prepend it to the string being built. | ||
* | ||
* @param {number} len - The desired length of the random string. | ||
* @param {PRNG} prng - The PRNG to use for generating random characters. | ||
* @returns {string} A random string of the specified length. | ||
*/ | ||
export function encodeRandom(len: number, prng: PRNG): string { | ||
let str = ""; | ||
for (; len > 0; len--) { | ||
str = randomChar(prng) + str; | ||
} | ||
return str; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
export * from "./ulid-converter.ts"; | ||
export * from "./ulid-monotonic.ts"; | ||
export * from "./ulid.ts"; | ||
export * from "./util.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { GLOBAL } from "./const.ts"; | ||
import { crockford } from "./crockford.ts"; | ||
|
||
/** | ||
* Converts a ULID string to a UUID string. | ||
* | ||
* This function validates the ULID string using a pre-defined regular expression (`GLOBAL.ULID_REGEX`). | ||
* If invalid, it throws an `InvalidData` error. Otherwise, it decodes the ULID string using the `crockford.decode` | ||
* function (assumed to be an external library) and converts the resulting Uint8Array to a UUID string | ||
* in the standard format (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). | ||
* | ||
* @param {string} ulid - The ULID string to convert. | ||
* @returns {string} The corresponding UUID string. | ||
* @throws {Deno.errors.InvalidData} If the provided ULID is invalid. | ||
*/ | ||
export function ulidToUUID(ulid: string): string { | ||
const isValid = GLOBAL.ULID_REGEX.test(ulid); | ||
if (!isValid) { | ||
throw new Deno.errors.InvalidData("Invalid ULID"); | ||
} | ||
|
||
const uint8Array = crockford.decode(ulid); | ||
const uuid = Array.from(uint8Array) | ||
.map((byte) => byte.toString(16).padStart(2, "0")) | ||
.join(""); | ||
|
||
return ( | ||
uuid.substring(0, 8) + | ||
"-" + | ||
uuid.substring(8, 12) + | ||
"-" + | ||
uuid.substring(12, 16) + | ||
"-" + | ||
uuid.substring(16, 20) + | ||
"-" + | ||
uuid.substring(20) | ||
); | ||
} | ||
|
||
/** | ||
* Converts a UUID string to a ULID string. | ||
* | ||
* This function validates the UUID string using a pre-defined regular expression (`GLOBAL.UUID_REGEX`). | ||
* If invalid, it throws an `InvalidData` error. Otherwise, it removes hyphens from the UUID string and splits | ||
* it into an array of byte pairs. It then converts each byte pair back to a number using hexadecimal parsing | ||
* and creates a new Uint8Array. Finally, it uses the `crockford.encode` function (assumed to be an external library) | ||
* to encode the Uint8Array into a ULID string. | ||
* | ||
* @param {string} uuid - The UUID string to convert. | ||
* @returns {string} The corresponding ULID string. | ||
* @throws {Deno.errors.InvalidData} If the provided UUID is invalid. | ||
*/ | ||
export function uuidToULID(uuid: string): string { | ||
const isValid = GLOBAL.UUID_REGEX.test(uuid); | ||
if (!isValid) { | ||
throw new Deno.errors.InvalidData("Invalid UUID"); | ||
} | ||
const clean = uuid.replace(/-/g, "") | ||
.match(/.{1,2}/g); | ||
if (!clean) { | ||
throw new Deno.errors.InvalidData("Invalid UUID"); | ||
} | ||
const uint8Array = new Uint8Array(clean.map((byte) => parseInt(byte, 16))); | ||
|
||
return crockford.encode(uint8Array); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import type { PRNG, ULID } from "../types/index.d.ts"; | ||
import { GLOBAL } from "./const.ts"; | ||
import { encodeRandom, encodeTime } from "./encode-decode.ts"; | ||
import { detectPrng, incrementBase32 } from "./util.ts"; | ||
|
||
/** | ||
* Creates a ULID generation factory function that ensures monotonic generation. | ||
* | ||
* This factory function generates ULIDs where the timestamp is always increasing or remains the same, | ||
* and the random part is incremented only if the timestamp doesn't change. This ensures lexicographic sorting | ||
* based on the ULID string. | ||
* | ||
* The default PRNG is chosen by the `detectPrng` function. | ||
* | ||
* @param {PRNG} [prng=detectPrng()] - The PRNG to use for generating random parts of the ULID. | ||
* @returns {ULID} A function that generates monotonic ULIDs. | ||
*/ | ||
export function monotonicFactory(prng: PRNG = detectPrng()): ULID { | ||
let lastTime = 0; | ||
let lastRandom: string; | ||
return function ulid(seedTime: number = Date.now()): string { | ||
if (seedTime <= lastTime) { | ||
const incrementedRandom = (lastRandom = incrementBase32(lastRandom)); | ||
return encodeTime(lastTime, GLOBAL.TIME_LEN) + incrementedRandom; | ||
} | ||
lastTime = seedTime; | ||
const newRandom = (lastRandom = encodeRandom(GLOBAL.RANDOM_LEN, prng)); | ||
return encodeTime(seedTime, GLOBAL.TIME_LEN) + newRandom; | ||
}; | ||
} | ||
|
||
/** | ||
* Generate a ULID that monotonically increases even for the same millisecond, | ||
* optionally passing the current time. If the current time is not passed, it | ||
* will default to `Date.now()`. | ||
* | ||
* Unlike the {@linkcode ulid} function, this function is guaranteed to return | ||
* strictly increasing ULIDs, even for the same seed time, but only if the seed | ||
* time only ever increases. If the seed time ever goes backwards, the ULID will | ||
* still be generated, but it will not be guaranteed to be monotonic with | ||
* previous ULIDs for that same seed time. | ||
* | ||
* @example Generate a monotonic ULID | ||
* ```ts no-assert | ||
* import { monotonicUlid } from "@std/ulid"; | ||
* | ||
* monotonicUlid(); // 01HYFKHG5F8RHM2PM3D7NSTDAS | ||
* ``` | ||
* | ||
* @example Generate a monotonic ULID with a seed time | ||
* ```ts no-assert | ||
* import { monotonicUlid } from "@std/ulid"; | ||
* | ||
* // Strict ordering for the same timestamp, by incrementing the least-significant random bit by 1 | ||
* monotonicUlid(150000); // 0000004JFHJJ2Z7X64FN2B4F1Q | ||
* | ||
* // A different timestamp will reset the random bits | ||
* monotonicUlid(150001); // 0000004JFHJJ2Z7X64FN2B4F1P | ||
* | ||
* // A previous seed time will not guarantee ordering, and may result in a | ||
* // ULID lower than one with the same seed time generated previously | ||
* monotonicUlid(150000); // 0000004JFJ7XF6D76ES95SZR0X | ||
* ``` | ||
* | ||
* @param seedTime The time to base the ULID on, in milliseconds since the Unix epoch. Defaults to `Date.now()`. | ||
* @returns A ULID that is guaranteed to be strictly increasing for the same seed time. | ||
*/ | ||
export const monotonicUlid: ULID = monotonicFactory(); |
Oops, something went wrong.