@@ -19,13 +19,13 @@ import { i as UniqueIdError, n as InvalidInputError } from "../errors-Dgoyi-ci.mjs"; | ||
| type Cuid2 = { | ||
| (options?: Cuid2Options): string; | ||
| /** Generate a CUID v2 string. */(options?: Cuid2Options): string; /** Return whether a value is a syntactically valid CUID v2 string. */ | ||
| isValid(id: unknown): id is string; | ||
| }; | ||
| /** | ||
| * Generate a CUID2 string. | ||
| * Generate a CUID v2 string. | ||
| * | ||
| * CUID2 is a secure, collision-resistant identifier that hashes multiple | ||
| * CUID v2 is a secure, collision-resistant identifier that hashes multiple | ||
| * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7), | ||
| * CUID2 prevents enumeration attacks by making IDs non-predictable. | ||
| * CUID v2 prevents enumeration attacks by making IDs non-predictable. | ||
| * | ||
| * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native | ||
| * Note: CUID v2 does not provide toBytes/fromBytes because it is a string-native | ||
| * format with no canonical binary representation (unlike UUID's 16-byte format). | ||
@@ -32,0 +32,0 @@ * |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"cuid2.d.mts","names":[],"sources":["../../src/cuid2/cuid2.ts"],"mappings":";;;KAIY,YAAA;;;AAAZ;;EAKE,MAAA;EAOS;;;;;AAAA;EAAT,MAAA,GAAS,UAAA;AAAA;AAAA,KAGC,KAAA;EAAA,CACT,OAAA,GAAU,YAAA;EACX,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;AAAA;AAiMxB;;;;AAAoB;;;;;;;;;;;;;;;;;;;;;;;cAAP,KAAA,EAAO,KAAA"} | ||
| {"version":3,"file":"cuid2.d.mts","names":[],"sources":["../../src/cuid2/cuid2.ts"],"mappings":";;;KAIY,YAAA;;;AAAZ;;EAKE,MAAA;EAOS;;;;;AAAA;EAAT,MAAA,GAAS,UAAA;AAAA;AAAA,KAGC,KAAA;EAEC,kCAAV,OAAA,GAAU,YAAA;EAEX,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;AAAA;AAiMxB;;;;AAAoB;;;;;;;;;;;;;;;;;;;;;;;;;cAAP,KAAA,EAAO,KAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"cuid2.mjs","names":[],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":["import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { randomUint32 } from '../common/random'\nimport { InvalidInputError } from '../errors'\n\nexport type Cuid2Options = {\n /**\n * Length of the generated ID (2-32 characters).\n * Default: 24\n */\n length?: number\n /**\n * Custom random bytes for deterministic testing.\n * Must be at least 1 byte. For adequate entropy, use at least 16 bytes.\n * Note: The fingerprint always uses cryptographically secure random bytes,\n * regardless of this option.\n */\n random?: Uint8Array\n}\n\nexport type Cuid2 = {\n (options?: Cuid2Options): string\n isValid(id: unknown): id is string\n}\n\nconst DEFAULT_LENGTH = 24\nconst MAX_LENGTH = 32\nconst MIN_LENGTH = 2\n\n// Maximum initial counter value from cuid2 spec - provides ~29 bits of\n// initial entropy to prevent cross-process collisions at startup\nconst INITIAL_COUNT_MAX = 476782367\n\n// Validation regex: first char must be a-z, rest can be a-z or 0-9\nconst CUID2_REGEX = /^[a-z][0-9a-z]+$/\n\n// Base36 alphabet\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\nconst LETTER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz'\n\n// Reusable TextEncoder instance (stateless, safe to share)\nconst textEncoder = new TextEncoder()\n\n/**\n * Module-level state for counter and fingerprint.\n * Counter is initialized lazily on first call to prevent unnecessary crypto operations.\n * Fingerprint is also generated lazily on first call.\n */\nconst state: { counter: number | undefined; fingerprint: string | undefined } = {\n counter: undefined,\n fingerprint: undefined,\n}\n\n/**\n * Initialize counter using crypto for consistency with other entropy sources.\n */\nfunction initializeCounter(): number {\n return randomUint32() % (INITIAL_COUNT_MAX + 1)\n}\n\n// --- Base36 utilities ---\n\nfunction bufToBigInt(buf: Uint8Array): bigint {\n let value = 0n\n for (const byte of buf) {\n value = value * 256n + BigInt(byte)\n }\n return value\n}\n\nfunction bigIntToBase36(value: bigint): string {\n if (value === 0n) return '0'\n const chars: string[] = []\n while (value > 0n) {\n chars.push(ALPHABET[Number(value % 36n)])\n value = value / 36n\n }\n return chars.reverse().join('')\n}\n\nfunction randomLetter(random: () => number): string {\n return LETTER_ALPHABET[Math.floor(random() * 26)]\n}\n\nfunction createEntropy(length: number, random: () => number): string {\n const chars = new Array<string>(length)\n for (let i = 0; i < length; i++) {\n chars[i] = ALPHABET[Math.floor(random() * 36)]\n }\n return chars.join('')\n}\n\n// --- SHA3 hash wrapper ---\n\nfunction hash(input: string): Uint8Array {\n return sha3_512(textEncoder.encode(input))\n}\n\n// --- Fingerprint generation ---\n\nconst BIG_LENGTH = 32\n\nfunction createFingerprint(): string {\n // Always use CSPRNG for fingerprint to ensure security regardless of custom random option\n const random = getCryptoRandom\n const globals = Object.keys(globalThis).toString()\n const sourceString = globals + createEntropy(BIG_LENGTH, random)\n const hashed = hash(sourceString)\n return bigIntToBase36(bufToBigInt(hashed)).slice(1, BIG_LENGTH + 1)\n}\n\n// --- Random function factory ---\n\n/**\n * Get a random number in [0, 1) using CUID2's own random pool.\n */\nfunction getCryptoRandom(): number {\n return randomUint32() / 0x100000000\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new InvalidInputError('CUID2_RANDOM_BYTES_EMPTY', 'Random byte array cannot be empty')\n }\n let index = 0\n return () => {\n const value = random[index % random.length] / 256\n index += 1\n return value\n }\n }\n return getCryptoRandom\n}\n\n// --- Main generator ---\n\nfunction cuid2Fn(options?: Cuid2Options): string {\n const requestedLength = options?.length\n\n if (\n requestedLength !== undefined &&\n (!Number.isInteger(requestedLength) || requestedLength < MIN_LENGTH || requestedLength > MAX_LENGTH)\n ) {\n throw new InvalidInputError(\n 'CUID2_LENGTH_OUT_OF_RANGE',\n `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${requestedLength}`,\n )\n }\n const length = requestedLength ?? DEFAULT_LENGTH\n\n const random = getRandomFn(options?.random)\n\n // Initialize counter lazily on first call\n if (state.counter === undefined) {\n state.counter = initializeCounter()\n }\n\n // Initialize fingerprint lazily on first call (always uses CSPRNG)\n if (state.fingerprint === undefined) {\n state.fingerprint = createFingerprint()\n }\n\n const firstLetter = randomLetter(random)\n const time = Date.now().toString(36)\n state.counter += 1\n const count = state.counter.toString(36)\n const salt = createEntropy(length, random)\n\n const hashInput = time + salt + count + state.fingerprint\n const hashed = hash(hashInput)\n const base36Hash = bigIntToBase36(bufToBigInt(hashed))\n\n // Drop first char of hash to avoid histogram bias, prepend random letter\n return firstLetter + base36Hash.slice(1, length)\n}\n\n// --- Validation (type guard) ---\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id)\n}\n\n/**\n * Generate a CUID2 string.\n *\n * CUID2 is a secure, collision-resistant identifier that hashes multiple\n * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),\n * CUID2 prevents enumeration attacks by making IDs non-predictable.\n *\n * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native\n * format with no canonical binary representation (unlike UUID's 16-byte format).\n *\n * @example\n * ```ts\n * import { cuid2 } from 'uniku/cuid2'\n *\n * const id = cuid2()\n * // => \"pfh0haxfpzowht3oi213cqos\"\n *\n * // Custom length\n * const shortId = cuid2({ length: 10 })\n * // => \"tz4a98xxat\"\n *\n * // Validation (type guard)\n * const maybeId: unknown = getUserInput()\n * if (cuid2.isValid(maybeId)) {\n * console.log(maybeId.length) // TypeScript knows maybeId is string\n * }\n * ```\n *\n * @deprecated Use `cuidv2` from `uniku/cuid/v2` instead. This entry point keeps\n * working unchanged, but `uniku/cuid/v2` is the canonical versioned subpath\n * (mirroring `uniku/uuid/v4` / `uniku/uuid/v7`).\n */\nexport const cuid2: Cuid2 = Object.assign(cuid2Fn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"iKAwBA,MASM,EAAc,mBAGd,EAAW,uCAIX,EAAc,IAAI,YAOlB,EAA0E,CAC9E,QAAS,IAAA,GACT,YAAa,IAAA,EACf,EAKA,SAAS,GAA4B,CACnC,OAAO,EAAa,EAAK,SAC3B,CAIA,SAAS,EAAY,EAAyB,CAC5C,IAAI,EAAQ,GACZ,IAAK,IAAM,KAAQ,EACjB,EAAQ,EAAQ,KAAO,OAAO,CAAI,EAEpC,OAAO,CACT,CAEA,SAAS,EAAe,EAAuB,CAC7C,GAAI,IAAU,GAAI,MAAO,IACzB,IAAM,EAAkB,CAAC,EACzB,KAAO,EAAQ,IACb,EAAM,KAAK,EAAS,OAAO,EAAQ,GAAG,EAAE,EACxC,GAAgB,IAElB,OAAO,EAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,CAChC,CAEA,SAAS,EAAa,EAA8B,CAClD,MAAO,6BAAgB,KAAK,MAAM,EAAO,EAAI,EAAE,EACjD,CAEA,SAAS,EAAc,EAAgB,EAA8B,CACnE,IAAM,EAAY,MAAc,CAAM,EACtC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAC1B,EAAM,GAAK,EAAS,KAAK,MAAM,EAAO,EAAI,EAAE,GAE9C,OAAO,EAAM,KAAK,EAAE,CACtB,CAIA,SAAS,EAAK,EAA2B,CACvC,OAAO,EAAS,EAAY,OAAO,CAAK,CAAC,CAC3C,CAMA,SAAS,GAA4B,CAEnC,IAAM,EAAS,EAIf,OAAO,EAAe,EADP,EAFC,OAAO,KAAK,UAAU,CAAC,CAAC,SACb,EAAI,EAAc,GAAY,CAAM,CAExB,CAAC,CAAC,CAAC,CAAC,MAAM,EAAG,EAAc,CACpE,CAOA,SAAS,GAA0B,CACjC,OAAO,EAAa,EAAI,UAC1B,CAEA,SAAS,EAAY,EAAmC,CACtD,GAAI,EAAQ,CACV,GAAI,EAAO,SAAW,EACpB,MAAM,IAAI,EAAkB,2BAA4B,mCAAmC,EAE7F,IAAI,EAAQ,EACZ,UAAa,CACX,IAAM,EAAQ,EAAO,EAAQ,EAAO,QAAU,IAE9C,MADA,IAAS,EACF,CACT,CACF,CACA,OAAO,CACT,CAIA,SAAS,EAAQ,EAAgC,CAC/C,IAAM,EAAkB,GAAS,OAEjC,GACE,IAAoB,IAAA,KACnB,CAAC,OAAO,UAAU,CAAe,GAAK,EAAkB,GAAc,EAAkB,IAEzF,MAAM,IAAI,EACR,4BACA,oDAA2E,GAC7E,EAEF,IAAM,EAAS,GAAmB,GAE5B,EAAS,EAAY,GAAS,MAAM,EAGtC,EAAM,UAAY,IAAA,KACpB,EAAM,QAAU,EAAkB,GAIhC,EAAM,cAAgB,IAAA,KACxB,EAAM,YAAc,EAAkB,GAGxC,IAAM,EAAc,EAAa,CAAM,EACjC,EAAO,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EACnC,EAAM,SAAW,EACjB,IAAM,EAAQ,EAAM,QAAQ,SAAS,EAAE,EAQvC,OAAO,EAHY,EAAe,EADnB,EADG,EAFL,EAAc,EAAQ,CAEP,EAAI,EAAQ,EAAM,WAEK,CAAC,CAGtB,CAAC,CAAC,MAAM,EAAG,CAAM,CACjD,CAIA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAG,QAAU,GAAc,EAAG,QAAU,IAAc,EAAY,KAAK,CAAE,CAC5G,CAkCA,MAAa,EAAe,OAAO,OAAO,EAAS,CACjD,SACF,CAAC"} | ||
| {"version":3,"file":"cuid2.mjs","names":[],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":["import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { randomUint32 } from '../common/random'\nimport { InvalidInputError } from '../errors'\n\nexport type Cuid2Options = {\n /**\n * Length of the generated ID (2-32 characters).\n * Default: 24\n */\n length?: number\n /**\n * Custom random bytes for deterministic testing.\n * Must be at least 1 byte. For adequate entropy, use at least 16 bytes.\n * Note: The fingerprint always uses cryptographically secure random bytes,\n * regardless of this option.\n */\n random?: Uint8Array\n}\n\nexport type Cuid2 = {\n /** Generate a CUID v2 string. */\n (options?: Cuid2Options): string\n /** Return whether a value is a syntactically valid CUID v2 string. */\n isValid(id: unknown): id is string\n}\n\nconst DEFAULT_LENGTH = 24\nconst MAX_LENGTH = 32\nconst MIN_LENGTH = 2\n\n// Maximum initial counter value from cuid2 spec - provides ~29 bits of\n// initial entropy to prevent cross-process collisions at startup\nconst INITIAL_COUNT_MAX = 476782367\n\n// Validation regex: first char must be a-z, rest can be a-z or 0-9\nconst CUID2_REGEX = /^[a-z][0-9a-z]+$/\n\n// Base36 alphabet\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\nconst LETTER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz'\n\n// Reusable TextEncoder instance (stateless, safe to share)\nconst textEncoder = new TextEncoder()\n\n/**\n * Module-level state for counter and fingerprint.\n * Counter is initialized lazily on first call to prevent unnecessary crypto operations.\n * Fingerprint is also generated lazily on first call.\n */\nconst state: { counter: number | undefined; fingerprint: string | undefined } = {\n counter: undefined,\n fingerprint: undefined,\n}\n\n/**\n * Initialize counter using crypto for consistency with other entropy sources.\n */\nfunction initializeCounter(): number {\n return randomUint32() % (INITIAL_COUNT_MAX + 1)\n}\n\n// --- Base36 utilities ---\n\nfunction bufToBigInt(buf: Uint8Array): bigint {\n let value = 0n\n for (const byte of buf) {\n value = value * 256n + BigInt(byte)\n }\n return value\n}\n\nfunction bigIntToBase36(value: bigint): string {\n if (value === 0n) return '0'\n const chars: string[] = []\n while (value > 0n) {\n chars.push(ALPHABET[Number(value % 36n)])\n value = value / 36n\n }\n return chars.reverse().join('')\n}\n\nfunction randomLetter(random: () => number): string {\n return LETTER_ALPHABET[Math.floor(random() * 26)]\n}\n\nfunction createEntropy(length: number, random: () => number): string {\n const chars = new Array<string>(length)\n for (let i = 0; i < length; i++) {\n chars[i] = ALPHABET[Math.floor(random() * 36)]\n }\n return chars.join('')\n}\n\n// --- SHA3 hash wrapper ---\n\nfunction hash(input: string): Uint8Array {\n return sha3_512(textEncoder.encode(input))\n}\n\n// --- Fingerprint generation ---\n\nconst BIG_LENGTH = 32\n\nfunction createFingerprint(): string {\n // Always use CSPRNG for fingerprint to ensure security regardless of custom random option\n const random = getCryptoRandom\n const globals = Object.keys(globalThis).toString()\n const sourceString = globals + createEntropy(BIG_LENGTH, random)\n const hashed = hash(sourceString)\n return bigIntToBase36(bufToBigInt(hashed)).slice(1, BIG_LENGTH + 1)\n}\n\n// --- Random function factory ---\n\n/**\n * Get a random number in [0, 1) using CUID2's own random pool.\n */\nfunction getCryptoRandom(): number {\n return randomUint32() / 0x100000000\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new InvalidInputError('CUID2_RANDOM_BYTES_EMPTY', 'Random byte array cannot be empty')\n }\n let index = 0\n return () => {\n const value = random[index % random.length] / 256\n index += 1\n return value\n }\n }\n return getCryptoRandom\n}\n\n// --- Main generator ---\n\nfunction cuid2Fn(options?: Cuid2Options): string {\n const requestedLength = options?.length\n\n if (\n requestedLength !== undefined &&\n (!Number.isInteger(requestedLength) || requestedLength < MIN_LENGTH || requestedLength > MAX_LENGTH)\n ) {\n throw new InvalidInputError(\n 'CUID2_LENGTH_OUT_OF_RANGE',\n `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${requestedLength}`,\n )\n }\n const length = requestedLength ?? DEFAULT_LENGTH\n\n const random = getRandomFn(options?.random)\n\n // Initialize counter lazily on first call\n if (state.counter === undefined) {\n state.counter = initializeCounter()\n }\n\n // Initialize fingerprint lazily on first call (always uses CSPRNG)\n if (state.fingerprint === undefined) {\n state.fingerprint = createFingerprint()\n }\n\n const firstLetter = randomLetter(random)\n const time = Date.now().toString(36)\n state.counter += 1\n const count = state.counter.toString(36)\n const salt = createEntropy(length, random)\n\n const hashInput = time + salt + count + state.fingerprint\n const hashed = hash(hashInput)\n const base36Hash = bigIntToBase36(bufToBigInt(hashed))\n\n // Drop first char of hash to avoid histogram bias, prepend random letter\n return firstLetter + base36Hash.slice(1, length)\n}\n\n// --- Validation (type guard) ---\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id)\n}\n\n/**\n * Generate a CUID v2 string.\n *\n * CUID v2 is a secure, collision-resistant identifier that hashes multiple\n * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),\n * CUID v2 prevents enumeration attacks by making IDs non-predictable.\n *\n * Note: CUID v2 does not provide toBytes/fromBytes because it is a string-native\n * format with no canonical binary representation (unlike UUID's 16-byte format).\n *\n * @example\n * ```ts\n * import { cuid2 } from 'uniku/cuid2'\n *\n * const id = cuid2()\n * // => \"pfh0haxfpzowht3oi213cqos\"\n *\n * // Custom length\n * const shortId = cuid2({ length: 10 })\n * // => \"tz4a98xxat\"\n *\n * // Validation (type guard)\n * const maybeId: unknown = getUserInput()\n * if (cuid2.isValid(maybeId)) {\n * console.log(maybeId.length) // TypeScript knows maybeId is string\n * }\n * ```\n *\n * @deprecated Use `cuidv2` from `uniku/cuid/v2` instead. This entry point keeps\n * working unchanged, but `uniku/cuid/v2` is the canonical versioned subpath\n * (mirroring `uniku/uuid/v4` / `uniku/uuid/v7`).\n */\nexport const cuid2: Cuid2 = Object.assign(cuid2Fn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"iKA0BA,MASM,EAAc,mBAGd,EAAW,uCAIX,EAAc,IAAI,YAOlB,EAA0E,CAC9E,QAAS,IAAA,GACT,YAAa,IAAA,EACf,EAKA,SAAS,GAA4B,CACnC,OAAO,EAAa,EAAK,SAC3B,CAIA,SAAS,EAAY,EAAyB,CAC5C,IAAI,EAAQ,GACZ,IAAK,IAAM,KAAQ,EACjB,EAAQ,EAAQ,KAAO,OAAO,CAAI,EAEpC,OAAO,CACT,CAEA,SAAS,EAAe,EAAuB,CAC7C,GAAI,IAAU,GAAI,MAAO,IACzB,IAAM,EAAkB,CAAC,EACzB,KAAO,EAAQ,IACb,EAAM,KAAK,EAAS,OAAO,EAAQ,GAAG,EAAE,EACxC,GAAgB,IAElB,OAAO,EAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,CAChC,CAEA,SAAS,EAAa,EAA8B,CAClD,MAAO,6BAAgB,KAAK,MAAM,EAAO,EAAI,EAAE,EACjD,CAEA,SAAS,EAAc,EAAgB,EAA8B,CACnE,IAAM,EAAY,MAAc,CAAM,EACtC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAC1B,EAAM,GAAK,EAAS,KAAK,MAAM,EAAO,EAAI,EAAE,GAE9C,OAAO,EAAM,KAAK,EAAE,CACtB,CAIA,SAAS,EAAK,EAA2B,CACvC,OAAO,EAAS,EAAY,OAAO,CAAK,CAAC,CAC3C,CAMA,SAAS,GAA4B,CAEnC,IAAM,EAAS,EAIf,OAAO,EAAe,EADP,EAFC,OAAO,KAAK,UAAU,CAAC,CAAC,SACb,EAAI,EAAc,GAAY,CAAM,CAExB,CAAC,CAAC,CAAC,CAAC,MAAM,EAAG,EAAc,CACpE,CAOA,SAAS,GAA0B,CACjC,OAAO,EAAa,EAAI,UAC1B,CAEA,SAAS,EAAY,EAAmC,CACtD,GAAI,EAAQ,CACV,GAAI,EAAO,SAAW,EACpB,MAAM,IAAI,EAAkB,2BAA4B,mCAAmC,EAE7F,IAAI,EAAQ,EACZ,UAAa,CACX,IAAM,EAAQ,EAAO,EAAQ,EAAO,QAAU,IAE9C,MADA,IAAS,EACF,CACT,CACF,CACA,OAAO,CACT,CAIA,SAAS,EAAQ,EAAgC,CAC/C,IAAM,EAAkB,GAAS,OAEjC,GACE,IAAoB,IAAA,KACnB,CAAC,OAAO,UAAU,CAAe,GAAK,EAAkB,GAAc,EAAkB,IAEzF,MAAM,IAAI,EACR,4BACA,oDAA2E,GAC7E,EAEF,IAAM,EAAS,GAAmB,GAE5B,EAAS,EAAY,GAAS,MAAM,EAGtC,EAAM,UAAY,IAAA,KACpB,EAAM,QAAU,EAAkB,GAIhC,EAAM,cAAgB,IAAA,KACxB,EAAM,YAAc,EAAkB,GAGxC,IAAM,EAAc,EAAa,CAAM,EACjC,EAAO,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EACnC,EAAM,SAAW,EACjB,IAAM,EAAQ,EAAM,QAAQ,SAAS,EAAE,EAQvC,OAAO,EAHY,EAAe,EADnB,EADG,EAFL,EAAc,EAAQ,CAEP,EAAI,EAAQ,EAAM,WAEK,CAAC,CAGtB,CAAC,CAAC,MAAM,EAAG,CAAM,CACjD,CAIA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAG,QAAU,GAAc,EAAG,QAAU,IAAc,EAAY,KAAK,CAAE,CAC5G,CAkCA,MAAa,EAAe,OAAO,OAAO,EAAS,CACjD,SACF,CAAC"} |
@@ -17,8 +17,8 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| type Ksuid = { | ||
| (): string; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: KsuidOptions, buf?: undefined, offset?: number): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): string; | ||
| timestamp(id: string): number; | ||
| /** Generate a time-ordered KSUID string. */(): string; /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf; /** Generate a KSUID string with optional timestamp or random payload bytes. */ | ||
| (options?: KsuidOptions, buf?: undefined, offset?: number): string; /** Convert a KSUID string to its canonical 20-byte representation. */ | ||
| toBytes(id: string): Uint8Array; /** Convert 20 canonical KSUID bytes to a KSUID string. */ | ||
| fromBytes(bytes: Uint8Array): string; /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number; /** Return whether a value is a syntactically valid KSUID string. */ | ||
| isValid(id: unknown): id is string; /** The nil KSUID (all zeros) */ | ||
@@ -25,0 +25,0 @@ NIL: string; /** The max KSUID (maximum valid value) */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ksuid.d.mts","names":[],"sources":["../../src/ksuid/ksuid.ts"],"mappings":";;;KAiCY,YAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,IAAA;AAAA;AAAA,KAGU,KAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,YAAA,cAA0B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACtG,OAAA,GAAU,YAAA,EAAc,GAAA,cAAiB,MAAA;EAC1C,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;cAkIW,KAAA,EAAO,KAAA"} | ||
| {"version":3,"file":"ksuid.d.mts","names":[],"sources":["../../src/ksuid/ksuid.ts"],"mappings":";;;KAiCY,YAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,IAAA;AAAA;AAAA,KAGU,KAAA;EAAA;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,YAAA,cAA0B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAEtG,OAAA,GAAU,YAAA,EAAc,GAAA,cAAiB,MAAA;EAE1C,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,KAAA,EAAO,UAAA,WAAA;EAEjB,SAAA,CAAU,EAAA;EAEV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;cAkIW,KAAA,EAAO,KAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_TOO_SHORT',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError(\n 'KSUID_INVALID_LENGTH',\n `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('KSUID_INVALID_CHAR', `Invalid KSUID character: ${str[i]}`)\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('KSUID_OVERFLOW', 'KSUID string exceeds 160-bit range')\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n * KSUID natively uses second precision.\n */\n secs?: number\n}\n\nexport type Ksuid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('KSUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID')\n }\n\n let timestamp: number\n const secs = options?.secs\n if (secs !== undefined) {\n if (!Number.isInteger(secs) || secs < KSUID_EPOCH) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_LOW', 'Timestamp must be >= KSUID epoch')\n }\n\n if (secs > KSUID_MAX_SECS) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_HIGH', 'Timestamp must be <= maximum KSUID timestamp')\n }\n\n timestamp = secs - KSUID_EPOCH\n } else {\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'KSUID_BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} | ||
| {"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_TOO_SHORT',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError(\n 'KSUID_INVALID_LENGTH',\n `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('KSUID_INVALID_CHAR', `Invalid KSUID character: ${str[i]}`)\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('KSUID_OVERFLOW', 'KSUID string exceeds 160-bit range')\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n * KSUID natively uses second precision.\n */\n secs?: number\n}\n\nexport type Ksuid = {\n /** Generate a time-ordered KSUID string. */\n (): string\n /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a KSUID string with optional timestamp or random payload bytes. */\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n /** Convert a KSUID string to its canonical 20-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 20 canonical KSUID bytes to a KSUID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid KSUID string. */\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('KSUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID')\n }\n\n let timestamp: number\n const secs = options?.secs\n if (secs !== undefined) {\n if (!Number.isInteger(secs) || secs < KSUID_EPOCH) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_LOW', 'Timestamp must be >= KSUID epoch')\n }\n\n if (secs > KSUID_MAX_SECS) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_HIGH', 'Timestamp must be <= maximum KSUID timestamp')\n }\n\n timestamp = secs - KSUID_EPOCH\n } else {\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'KSUID_BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
@@ -24,4 +24,4 @@ import { i as UniqueIdError, n as InvalidInputError } from "../errors-Dgoyi-ci.mjs"; | ||
| type Nanoid = { | ||
| /** Generate nanoid with default settings */(): string; /** Generate nanoid with custom size */ | ||
| (size: number): string; /** Generate nanoid with options */ | ||
| /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */(): string; /** Generate a Nanoid with the default alphabet and a custom length. */ | ||
| (size: number): string; /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */ | ||
| (options: NanoidOptions): string; | ||
@@ -28,0 +28,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. It consumes `size`\n// bytes directly without the shared helper's UUID/CUID2-oriented branches.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nlet pool: Uint8Array | undefined\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!pool || pool.length < size) {\n pool = new Uint8Array(size)\n crypto.getRandomValues(pool)\n poolOffset = 0\n } else if (poolOffset + bytes > pool.length) {\n crypto.getRandomValues(pool)\n poolOffset = 0\n }\n poolOffset += bytes\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n let id = ''\n for (let i = poolOffset - size; i < poolOffset; i++) {\n id += URL_ALPHABET[pool![i] & 63]\n }\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `size` bytes needed.\n * For other alphabets: ~size * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate nanoid with default settings */\n (): string\n /** Generate nanoid with custom size */\n (size: number): string\n /** Generate nanoid with options */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_SHORT', 'Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_LONG', 'Alphabet must not exceed 256 characters')\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'NANOID_ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('NANOID_ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`)\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate size parameter\n */\nfunction validateSize(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('NANOID_SIZE_INVALID', 'Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('NANOID_SIZE_TOO_LARGE', `Size must not exceed ${MAX_SIZE}`)\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom size\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Size must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested size\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"} | ||
| {"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. It consumes `size`\n// bytes directly without the shared helper's UUID/CUID2-oriented branches.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nlet pool: Uint8Array | undefined\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!pool || pool.length < size) {\n pool = new Uint8Array(size)\n crypto.getRandomValues(pool)\n poolOffset = 0\n } else if (poolOffset + bytes > pool.length) {\n crypto.getRandomValues(pool)\n poolOffset = 0\n }\n poolOffset += bytes\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n let id = ''\n for (let i = poolOffset - size; i < poolOffset; i++) {\n id += URL_ALPHABET[pool![i] & 63]\n }\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `size` bytes needed.\n * For other alphabets: ~size * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_SHORT', 'Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_LONG', 'Alphabet must not exceed 256 characters')\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'NANOID_ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('NANOID_ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`)\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate size parameter\n */\nfunction validateSize(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('NANOID_SIZE_INVALID', 'Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('NANOID_SIZE_TOO_LARGE', `Size must not exceed ${MAX_SIZE}`)\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom size\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Size must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested size\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
@@ -20,8 +20,8 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| type ObjectId = { | ||
| (): string; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: ObjectIdOptions, buf?: undefined, offset?: number): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): string; | ||
| timestamp(id: string): number; | ||
| /** Generate a MongoDB-compatible ObjectID string. */(): string; /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf; /** Generate an ObjectID string with optional timestamp, random field, or counter. */ | ||
| (options?: ObjectIdOptions, buf?: undefined, offset?: number): string; /** Convert an ObjectID string to its canonical 12-byte representation. */ | ||
| toBytes(id: string): Uint8Array; /** Convert 12 canonical ObjectID bytes to an ObjectID string. */ | ||
| fromBytes(bytes: Uint8Array): string; /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number; /** Return whether a value is a syntactically valid ObjectID string. */ | ||
| isValid(id: unknown): id is string; /** The nil ObjectID (all zeros) */ | ||
@@ -28,0 +28,0 @@ NIL: string; /** The max ObjectID (all 0xff) */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"objectid.d.mts","names":[],"sources":["../../src/objectid/objectid.ts"],"mappings":";;;KA2BY,eAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;EAKT,IAAA;;;AAIA;EAAA,OAAA;AAAA;AAAA,KAGU,QAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,eAAA,cAA6B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACzG,OAAA,GAAU,eAAA,EAAiB,GAAA,cAAiB,MAAA;EAC7C,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA,UAJiB;EAMjB,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAkNF;cAAa,QAAA,EAAU,QAAA"} | ||
| {"version":3,"file":"objectid.d.mts","names":[],"sources":["../../src/objectid/objectid.ts"],"mappings":";;;KA2BY,eAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;EAKT,IAAA;;;AAIA;EAAA,OAAA;AAAA;AAAA,KAGU,QAAA;;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,eAAA,cAA6B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAEzG,OAAA,GAAU,eAAA,EAAiB,GAAA,cAAiB,MAAA;EAE7C,OAAA,CAAQ,EAAA,WAAa,UAAA,EAEJ;EAAjB,SAAA,CAAU,KAAA,EAAO,UAAA;EAEjB,SAAA,CAAU,EAAA;EAEV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;;AAAA;AAkNF;;;;AAAuB;;;;cAAV,QAAA,EAAU,QAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES"],"sources":["../../src/objectid/hex.ts","../../src/objectid/objectid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Hex encoding/decoding for ObjectID.\n * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode)\n *\n * ObjectID binary format: 12 bytes (96 bits)\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter\n *\n * String format: 24 characters of hex\n */\n\nconst HEX_CHARS = '0123456789abcdef'\nconst OBJECTID_BYTES = 12\nconst OBJECTID_STRING_LEN = 24\n\n// Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string.\nconst ENCODE_TABLE: string[] = Array.from(\n { length: 256 },\n (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf],\n)\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 128 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i <= 9; i += 1) {\n DECODING['0'.charCodeAt(0) + i] = i\n}\nfor (let i = 0; i < 6; i += 1) {\n DECODING['a'.charCodeAt(0) + i] = 10 + i\n DECODING['A'.charCodeAt(0) + i] = 10 + i\n}\n\n/**\n * Encode 12 ObjectID bytes to a 24-character lowercase hex string.\n */\nexport function encodeObjectIdHex(bytes: Uint8Array): string {\n if (bytes.length < OBJECTID_BYTES) {\n throw new BufferError(\n 'OBJECTID_BYTES_TOO_SHORT',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n let encoded = ''\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n encoded += ENCODE_TABLE[bytes[i]]\n }\n return encoded\n}\n\n/**\n * Decode a 24-character hex string to 12 ObjectID bytes.\n */\nexport function decodeObjectIdHex(str: string): Uint8Array {\n if (str.length !== OBJECTID_STRING_LEN) {\n throw new ParseError(\n 'OBJECTID_INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n const hiIndex = i * 2\n const loIndex = hiIndex + 1\n const hi = DECODING[str.charCodeAt(hiIndex)]\n const lo = DECODING[str.charCodeAt(loIndex)]\n\n if (hi === 255) {\n throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`)\n }\n if (lo === 255) {\n throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`)\n }\n\n bytes[i] = (hi << 4) | lo\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeObjectIdHex, encodeObjectIdHex } from './hex'\n\n/**\n * MongoDB ObjectID\n *\n * A 96-bit (12-byte) identifier consisting of:\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0)\n *\n * Encoded as a 24-character lowercase hex string.\n */\n\nconst OBJECTID_BYTES = 12\nconst TIMESTAMP_BYTES = 4\nconst RANDOM_BYTES = 5\nconst COUNTER_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_COUNTER = 0xffffff\n\n// Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid()\nconst OBJECTID_REGEX = /^[0-9a-f]{24}$/i\n\nexport type ObjectIdOptions = {\n /**\n * 5 bytes of random data to use for the ObjectID's random field.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n */\n secs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil ObjectID (all zeros) */\n NIL: string\n /** The max ObjectID (all 0xff) */\n MAX: string\n}\n\ntype ObjectIdState = {\n random: Uint8Array | undefined\n counter: number | undefined\n}\n\n/**\n * Module-level state for the per-process random value and the always-incrementing counter.\n *\n * IMPORTANT: This state persists across all objectid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb\n * across warm starts is exactly the anti-collision behavior the ObjectID spec intends.\n * - For isolated state, pass explicit `random`, `secs`, or `counter` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: ObjectIdState = {\n random: undefined,\n counter: undefined,\n}\n\n/**\n * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the\n * result is unaffected by any later pool refill triggered by other random draws\n * (e.g. the counter's `randomUint32()` call) before these bytes are consumed.\n */\nfunction freshRandom(): Uint8Array {\n const bytes = new Uint8Array(RANDOM_BYTES)\n bytes.set(randomBytes(RANDOM_BYTES))\n return bytes\n}\n\nfunction writeObjectIdBytesUnchecked(\n secs: number,\n random: Uint8Array,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, secs)\n\n // Random (5 bytes) -> bytes 4-8\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = random[i]\n }\n\n // Counter (24-bit big-endian) -> bytes 9-11\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff\n}\n\n/*\n * Overload: no buffer => return an ObjectID string.\n */\nfunction objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ObjectID bytes.\n */\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options: ObjectIdOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options?: ObjectIdOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n let secs: number\n let random: Uint8Array\n let counter: number\n\n if (options) {\n const optRandom = options.random\n if (optRandom && optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'OBJECTID_RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n )\n }\n\n const optSecs = options.secs\n if (optSecs !== undefined && !isIntegerInRange(optSecs, 0, MAX_SECS)) {\n throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)\n }\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)\n }\n\n // Options bypass persistent state entirely: every field is sourced fresh (given\n // value, or an independently-defaulted one), never read from or written to\n // `state` (see KTD2). This is what makes options-based generation deterministic\n // when all three fields are supplied.\n secs = optSecs ?? Math.floor(Date.now() / 1000)\n random = optRandom ?? freshRandom()\n counter = optCounter ?? randomUint32() & MAX_COUNTER\n } else {\n // Lazily initialize persistent state on first no-option call.\n if (state.random === undefined) {\n state.random = freshRandom()\n }\n if (state.counter === undefined) {\n state.counter = randomUint32() & MAX_COUNTER\n }\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ObjectIDs generated within a single request will share the same timestamp.\n * - monotonic ordering relies entirely on the ever-incrementing counter.\n */\n secs = Math.floor(Date.now() / 1000)\n random = state.random\n state.counter = (state.counter + 1) & MAX_COUNTER\n counter = state.counter\n }\n\n if (buf) {\n if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {\n throw new BufferError(\n 'OBJECTID_BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Convert an ObjectID hex string to 12 bytes.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeObjectIdHex(id)\n}\n\n/**\n * Convert 12 bytes to an ObjectID hex string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== OBJECTID_BYTES) {\n throw new BufferError(\n 'OBJECTID_BYTES_INVALID_LENGTH',\n `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Extract the timestamp from an ObjectID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid.\n * Note: ObjectID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeObjectIdHex(id)\n // First 4 bytes are big-endian timestamp (seconds since Unix epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n return secs * 1000\n}\n\n/**\n * Validate an ObjectID string format.\n * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing\n * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though\n * generation always emits lowercase (matching MongoDB driver output).\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && OBJECTID_REGEX.test(id)\n}\n\n/**\n * Generate a MongoDB ObjectID string or write the bytes into a buffer.\n *\n * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte\n * per-process random value, and a 3-byte always-incrementing counter, encoded\n * as a 24-character lowercase hex string. It is time-ordered and compatible\n * with MongoDB's own ObjectID implementation.\n *\n * @example\n * ```ts\n * import { objectid } from 'uniku/objectid'\n *\n * const id = objectid()\n * // => \"667c3f2a1e2b3c4d5e6f7081\"\n *\n * // Extract timestamp\n * const ts = objectid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * objectid.isValid(id) // true\n *\n * // Convert to/from bytes (12 bytes)\n * const bytes = objectid.toBytes(id)\n * const restored = objectid.fromBytes(bytes)\n * ```\n */\nexport const objectid: ObjectId = Object.assign(objectIdFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(24),\n MAX: 'f'.repeat(24),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} | ||
| {"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES"],"sources":["../../src/objectid/hex.ts","../../src/objectid/objectid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Hex encoding/decoding for ObjectID.\n * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode)\n *\n * ObjectID binary format: 12 bytes (96 bits)\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter\n *\n * String format: 24 characters of hex\n */\n\nconst HEX_CHARS = '0123456789abcdef'\nconst OBJECTID_BYTES = 12\nconst OBJECTID_STRING_LEN = 24\n\n// Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string.\nconst ENCODE_TABLE: string[] = Array.from(\n { length: 256 },\n (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf],\n)\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 128 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i <= 9; i += 1) {\n DECODING['0'.charCodeAt(0) + i] = i\n}\nfor (let i = 0; i < 6; i += 1) {\n DECODING['a'.charCodeAt(0) + i] = 10 + i\n DECODING['A'.charCodeAt(0) + i] = 10 + i\n}\n\n/**\n * Encode 12 ObjectID bytes to a 24-character lowercase hex string.\n */\nexport function encodeObjectIdHex(bytes: Uint8Array): string {\n if (bytes.length < OBJECTID_BYTES) {\n throw new BufferError(\n 'OBJECTID_BYTES_TOO_SHORT',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n let encoded = ''\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n encoded += ENCODE_TABLE[bytes[i]]\n }\n return encoded\n}\n\n/**\n * Decode a 24-character hex string to 12 ObjectID bytes.\n */\nexport function decodeObjectIdHex(str: string): Uint8Array {\n if (str.length !== OBJECTID_STRING_LEN) {\n throw new ParseError(\n 'OBJECTID_INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n const hiIndex = i * 2\n const loIndex = hiIndex + 1\n const hi = DECODING[str.charCodeAt(hiIndex)]\n const lo = DECODING[str.charCodeAt(loIndex)]\n\n if (hi === 255) {\n throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`)\n }\n if (lo === 255) {\n throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`)\n }\n\n bytes[i] = (hi << 4) | lo\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeObjectIdHex, encodeObjectIdHex } from './hex'\n\n/**\n * MongoDB ObjectID\n *\n * A 96-bit (12-byte) identifier consisting of:\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0)\n *\n * Encoded as a 24-character lowercase hex string.\n */\n\nconst OBJECTID_BYTES = 12\nconst TIMESTAMP_BYTES = 4\nconst RANDOM_BYTES = 5\nconst COUNTER_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_COUNTER = 0xffffff\n\n// Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid()\nconst OBJECTID_REGEX = /^[0-9a-f]{24}$/i\n\nexport type ObjectIdOptions = {\n /**\n * 5 bytes of random data to use for the ObjectID's random field.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n */\n secs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n /** Generate a MongoDB-compatible ObjectID string. */\n (): string\n /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate an ObjectID string with optional timestamp, random field, or counter. */\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n /** Convert an ObjectID string to its canonical 12-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 12 canonical ObjectID bytes to an ObjectID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid ObjectID string. */\n isValid(id: unknown): id is string\n /** The nil ObjectID (all zeros) */\n NIL: string\n /** The max ObjectID (all 0xff) */\n MAX: string\n}\n\ntype ObjectIdState = {\n random: Uint8Array | undefined\n counter: number | undefined\n}\n\n/**\n * Module-level state for the per-process random value and the always-incrementing counter.\n *\n * IMPORTANT: This state persists across all objectid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb\n * across warm starts is exactly the anti-collision behavior the ObjectID spec intends.\n * - For isolated state, pass explicit `random`, `secs`, or `counter` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: ObjectIdState = {\n random: undefined,\n counter: undefined,\n}\n\n/**\n * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the\n * result is unaffected by any later pool refill triggered by other random draws\n * (e.g. the counter's `randomUint32()` call) before these bytes are consumed.\n */\nfunction freshRandom(): Uint8Array {\n const bytes = new Uint8Array(RANDOM_BYTES)\n bytes.set(randomBytes(RANDOM_BYTES))\n return bytes\n}\n\nfunction writeObjectIdBytesUnchecked(\n secs: number,\n random: Uint8Array,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, secs)\n\n // Random (5 bytes) -> bytes 4-8\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = random[i]\n }\n\n // Counter (24-bit big-endian) -> bytes 9-11\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff\n}\n\n/*\n * Overload: no buffer => return an ObjectID string.\n */\nfunction objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ObjectID bytes.\n */\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options: ObjectIdOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options?: ObjectIdOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n let secs: number\n let random: Uint8Array\n let counter: number\n\n if (options) {\n const optRandom = options.random\n if (optRandom && optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'OBJECTID_RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n )\n }\n\n const optSecs = options.secs\n if (optSecs !== undefined && !isIntegerInRange(optSecs, 0, MAX_SECS)) {\n throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)\n }\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)\n }\n\n // Options bypass persistent state entirely: every field is sourced fresh (given\n // value, or an independently-defaulted one), never read from or written to\n // `state` (see KTD2). This is what makes options-based generation deterministic\n // when all three fields are supplied.\n secs = optSecs ?? Math.floor(Date.now() / 1000)\n random = optRandom ?? freshRandom()\n counter = optCounter ?? randomUint32() & MAX_COUNTER\n } else {\n // Lazily initialize persistent state on first no-option call.\n if (state.random === undefined) {\n state.random = freshRandom()\n }\n if (state.counter === undefined) {\n state.counter = randomUint32() & MAX_COUNTER\n }\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ObjectIDs generated within a single request will share the same timestamp.\n * - monotonic ordering relies entirely on the ever-incrementing counter.\n */\n secs = Math.floor(Date.now() / 1000)\n random = state.random\n state.counter = (state.counter + 1) & MAX_COUNTER\n counter = state.counter\n }\n\n if (buf) {\n if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {\n throw new BufferError(\n 'OBJECTID_BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Convert an ObjectID hex string to 12 bytes.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeObjectIdHex(id)\n}\n\n/**\n * Convert 12 bytes to an ObjectID hex string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== OBJECTID_BYTES) {\n throw new BufferError(\n 'OBJECTID_BYTES_INVALID_LENGTH',\n `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Extract the timestamp from an ObjectID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid.\n * Note: ObjectID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeObjectIdHex(id)\n // First 4 bytes are big-endian timestamp (seconds since Unix epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n return secs * 1000\n}\n\n/**\n * Validate an ObjectID string format.\n * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing\n * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though\n * generation always emits lowercase (matching MongoDB driver output).\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && OBJECTID_REGEX.test(id)\n}\n\n/**\n * Generate a MongoDB ObjectID string or write the bytes into a buffer.\n *\n * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte\n * per-process random value, and a 3-byte always-incrementing counter, encoded\n * as a 24-character lowercase hex string. It is time-ordered and compatible\n * with MongoDB's own ObjectID implementation.\n *\n * @example\n * ```ts\n * import { objectid } from 'uniku/objectid'\n *\n * const id = objectid()\n * // => \"667c3f2a1e2b3c4d5e6f7081\"\n *\n * // Extract timestamp\n * const ts = objectid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * objectid.isValid(id) // true\n *\n * // Convert to/from bytes (12 bytes)\n * const bytes = objectid.toBytes(id)\n * const restored = objectid.fromBytes(bytes)\n * ```\n */\nexport const objectid: ObjectId = Object.assign(objectIdFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(24),\n MAX: 'f'.repeat(24),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
@@ -34,10 +34,10 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| type Tsid = { | ||
| (): bigint; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: TsidOptions, buf?: undefined, offset?: number): bigint; | ||
| toBytes(id: bigint): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): bigint; | ||
| toString(id: bigint): string; | ||
| fromString(str: string): bigint; | ||
| timestamp(id: bigint, epoch?: number): number; | ||
| /** Generate a time-sorted TSID bigint. */(): bigint; /** Generate a TSID with explicit options or write its 8 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf; /** Generate a TSID bigint with optional timestamp, epoch, node, or counter controls. */ | ||
| (options?: TsidOptions, buf?: undefined, offset?: number): bigint; /** Convert a TSID bigint to its canonical 8-byte representation. */ | ||
| toBytes(id: bigint): Uint8Array; /** Convert 8 canonical TSID bytes to a TSID bigint. */ | ||
| fromBytes(bytes: Uint8Array): bigint; /** Convert a TSID bigint to its canonical 13-character string. */ | ||
| toString(id: bigint): string; /** Convert a canonical TSID string to a bigint. */ | ||
| fromString(str: string): bigint; /** Read a TSID timestamp in milliseconds, using the supplied or default epoch. */ | ||
| timestamp(id: bigint, epoch?: number): number; /** Return whether a value is a valid 64-bit TSID bigint. */ | ||
| isValid(id: unknown): id is bigint; /** The nil TSID (all zeros) */ | ||
@@ -44,0 +44,0 @@ NIL: bigint; /** The max TSID (maximum valid 64-bit value) */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"tsid.d.mts","names":[],"sources":["../../src/tsid/tsid.ts"],"mappings":";;;KAiCY,WAAA;;;AAAZ;;EAKE,KAAA;EALU;;;;EAUV,KAAA;;;AAiBA;AAGF;EAfE,IAAA;;;;;;EAMA,QAAA;;;;;;EAMA,OAAA;AAAA;AAAA,KAGU,IAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,WAAA,cAAyB,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACrG,OAAA,GAAU,WAAA,EAAa,GAAA,cAAiB,MAAA;EACzC,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,QAAA,CAAS,EAAA;EACT,UAAA,CAAW,GAAA;EACX,SAAA,CAAU,EAAA,UAAY,KAAA;EACtB,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;AAAA;AA4PF;;;;AAAmB;;;;;;;;;;;;;cAAN,IAAA,EAAM,IAAA"} | ||
| {"version":3,"file":"tsid.d.mts","names":[],"sources":["../../src/tsid/tsid.ts"],"mappings":";;;KAiCY,WAAA;;;AAAZ;;EAKE,KAAA;EALU;;;;EAUV,KAAA;;;AAiBA;AAGF;EAfE,IAAA;;;;;;EAMA,QAAA;;;;;;EAMA,OAAA;AAAA;AAAA,KAGU,IAAA;;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,WAAA,cAAyB,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAErG,OAAA,GAAU,WAAA,EAAa,GAAA,cAAiB,MAAA;EAEzC,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,KAAA,EAAO,UAAA;EAEjB,QAAA,CAAS,EAAA;EAET,UAAA,CAAW,GAAA;EAEX,SAAA,CAAU,EAAA,UAAY,KAAA;EAEtB,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;AAAA;AA4PF;;;;AAAmB;;;;;;;;;;;;;;;;;;;;;;cAAN,IAAA,EAAM,IAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"tsid.mjs","names":[],"sources":["../../src/tsid/crockford64.ts","../../src/tsid/tsid.ts"],"sourcesContent":["import { ParseError } from '../errors'\n\n/**\n * Crockford Base32 codec for TSID, operating directly on the primary `bigint`\n * value rather than an intermediate byte array (unlike ulid/crockford.ts) -\n * tsid's primary type never routes through a `Uint8Array` during string\n * conversion.\n *\n * TSID binary format: 64 bits (8 bytes), encoded as 13 Crockford Base32 characters.\n * 13 chars * 5 bits = 65 bits of capacity for a 64-bit value, leaving exactly 1 bit\n * of headroom on the leading character. That headroom bit is always 0, so the\n * leading character only carries 4 significant real bits (values 0-15, i.e. the\n * alphabet's first 16 symbols \"0\"-\"9\"/\"A\"-\"F\") rather than the full 32-symbol\n * range - the same overflow-prevention technique ULID uses for its own leading\n * `[0-7]` restriction (2 headroom bits there vs 1 here). Confirmed against\n * `tsid-ts`'s own `toCanonicalString`: `(number >> BigInt(60 - i * 5)) & 0x1f`.\n */\n\nconst TSID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\nconst TSID_STRING_LEN = 13\nconst TSID_LEADING_MAX = 15 // 4 significant bits on the leading character (see above)\n\n// Pre-computed decode table (case-insensitive) covering ASCII, so lookups never\n// go out of bounds and a single `=== 255` check rejects invalid input.\nconst DECODING = new Uint8Array(128).fill(255)\nfor (let i = 0; i < TSID_ALPHABET.length; i += 1) {\n const char = TSID_ALPHABET[i]\n const upperCode = TSID_ALPHABET.charCodeAt(i)\n DECODING[upperCode] = i\n // Only letters have a lowercase counterpart - applying +32 to digit codes\n // would land on unrelated uppercase letters (e.g. '5' + 32 -> 'U', which is\n // excluded from this alphabet and would never get overwritten back to 255).\n if (char >= 'A' && char <= 'Z') {\n DECODING[upperCode + 32] = i\n }\n}\n\n/**\n * Encode a TSID `bigint` value to its 13-character canonical Crockford Base32 string.\n */\nexport function encodeTsidString(value: bigint): string {\n let result = ''\n for (let i = 0; i < TSID_STRING_LEN; i += 1) {\n const shift = BigInt(60 - i * 5)\n const group = Number((value >> shift) & 0x1fn)\n result += TSID_ALPHABET[group]\n }\n return result\n}\n\n/**\n * Decode a 13-character canonical Crockford Base32 string to a TSID `bigint` value.\n */\nexport function decodeTsidString(str: string): bigint {\n if (str.length !== TSID_STRING_LEN) {\n throw new ParseError('TSID_INVALID_LENGTH', `TSID string must be ${TSID_STRING_LEN} characters, got ${str.length}`)\n }\n\n let value = 0n\n for (let i = 0; i < TSID_STRING_LEN; i += 1) {\n const code = str.charCodeAt(i)\n const group = code < 128 ? DECODING[code] : 255\n if (group === 255) {\n throw new ParseError('TSID_INVALID_CHAR', `Invalid TSID character: ${str[i]}`)\n }\n if (i === 0 && group > TSID_LEADING_MAX) {\n throw new ParseError(\n 'TSID_LEADING_CHAR_OUT_OF_RANGE',\n `TSID leading character must be one of 0-9, A-F, got: ${str[i]}`,\n )\n }\n value = (value << 5n) | BigInt(group)\n }\n return value\n}\n","import { randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeTsidString, encodeTsidString } from './crockford64'\n\n/**\n * TSID (Time-Sorted Unique Identifier)\n *\n * A 64-bit Snowflake-style identifier consisting of:\n * - 42 bits: millisecond timestamp relative to a custom epoch (default 2020-01-01T00:00:00.000Z)\n * - 10 bits: node ID (default; configurable via `nodeBits`)\n * - 12 bits: per-millisecond counter (default; `22 - nodeBits`)\n *\n * Unlike every other uniku generator, `tsid()` returns a `bigint` by default -\n * this is a deliberate design decision reflecting that TSID's value proposition\n * is native numeric storage (e.g. a database BIGINT primary key), not a\n * string-first identifier that happens to have a byte encoding. `toString`/\n * `fromString` are the boundary conversions to/from the 13-character canonical\n * Crockford Base32 string.\n *\n * Encoded as 8 big-endian bytes in buffer mode.\n */\n\nconst TSID_EPOCH = 1577836800000 // 2020-01-01T00:00:00.000Z\nconst TSID_BYTES = 8\nconst RANDOM_BITS = 22 // combined node + counter bits\nconst DEFAULT_NODE_BITS = 10\nconst DEFAULT_COUNTER_BITS = RANDOM_BITS - DEFAULT_NODE_BITS\nconst DEFAULT_NODE_MASK = (1 << DEFAULT_NODE_BITS) - 1\nconst DEFAULT_COUNTER_MASK = (1 << DEFAULT_COUNTER_BITS) - 1\nconst MAX_NODE_BITS = 20\nconst MAX_TIMESTAMP_DIFF = (1n << 42n) - 1n\n\nexport type TsidOptions = {\n /**\n * Timestamp in milliseconds since Unix epoch.\n * Defaults to Date.now().\n */\n msecs?: number\n /**\n * Custom epoch in milliseconds since Unix epoch.\n * Defaults to 1577836800000 (2020-01-01T00:00:00.000Z).\n */\n epoch?: number\n /**\n * Node ID (0 to 2^nodeBits - 1).\n * Defaults to a lazily-initialized, persistent random value.\n */\n node?: number\n /**\n * Number of bits allocated to the node ID (0-20).\n * The remaining bits (22 - nodeBits) are allocated to the counter.\n * Defaults to 10.\n */\n nodeBits?: number\n /**\n * Per-millisecond counter (0 to 2^(22 - nodeBits) - 1).\n * Defaults to a fresh random value on a new millisecond, or the previous\n * value incremented by 1 within the same millisecond.\n */\n counter?: number\n}\n\nexport type Tsid = {\n (): bigint\n <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: TsidOptions, buf?: undefined, offset?: number): bigint\n toBytes(id: bigint): Uint8Array\n fromBytes(bytes: Uint8Array): bigint\n toString(id: bigint): string\n fromString(str: string): bigint\n timestamp(id: bigint, epoch?: number): number\n isValid(id: unknown): id is bigint\n /** The nil TSID (all zeros) */\n NIL: bigint\n /** The max TSID (maximum valid 64-bit value) */\n MAX: bigint\n}\n\ntype TsidState = {\n node: number | undefined\n msecs: number\n counter: number\n}\n\n/**\n * Module-level state for the lazily-initialized persistent node ID and the\n * per-millisecond-reset counter.\n *\n * IMPORTANT: This state persists across all tsid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * - For isolated state, pass explicit `msecs`, `epoch`, `node`, `nodeBits`, or\n * `counter` via options - doing so bypasses `state` entirely.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: TsidState = {\n node: undefined,\n msecs: -Infinity,\n counter: 0,\n}\n\nfunction pack(msecsDiff: bigint, node: number, counterBits: number, counter: number): bigint {\n return (msecsDiff << BigInt(RANDOM_BITS)) | (BigInt(node) << BigInt(counterBits)) | BigInt(counter)\n}\n\nfunction writeTsidBytesUnchecked(value: bigint, buf: Uint8Array, offset: number): void {\n for (let i = 0; i < TSID_BYTES; i += 1) {\n buf[offset + i] = Number((value >> BigInt((TSID_BYTES - 1 - i) * 8)) & 0xffn)\n }\n}\n\ntype ResolvedTsidOptions = {\n msecsDiff: bigint\n node: number\n counterBits: number\n counter: number\n}\n\nfunction resolveTsidOptions(options: TsidOptions): ResolvedTsidOptions {\n const nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS\n if (!isIntegerInRange(nodeBits, 0, MAX_NODE_BITS)) {\n throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`)\n }\n\n const counterBits = RANDOM_BITS - nodeBits\n const nodeMask = (1 << nodeBits) - 1\n const counterMask = (1 << counterBits) - 1\n const optNode = options.node\n if (optNode !== undefined && !isIntegerInRange(optNode, 0, nodeMask)) {\n throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`)\n }\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, counterMask)) {\n throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`)\n }\n\n const epoch = options.epoch ?? TSID_EPOCH\n if (!Number.isInteger(epoch)) {\n throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer')\n }\n const msecs = options.msecs ?? Date.now()\n if (!Number.isInteger(msecs)) {\n throw new InvalidInputError('TSID_TIMESTAMP_INVALID', 'msecs must be a finite integer')\n }\n const msecsDiff = BigInt(msecs) - BigInt(epoch)\n if (msecsDiff < 0n || msecsDiff > MAX_TIMESTAMP_DIFF) {\n throw new InvalidInputError(\n 'TSID_TIMESTAMP_OUT_OF_RANGE',\n `msecs - epoch must be between 0 and ${MAX_TIMESTAMP_DIFF}`,\n )\n }\n\n return {\n msecsDiff,\n node: optNode ?? randomUint32() & nodeMask,\n counterBits,\n counter: optCounter ?? randomUint32() & counterMask,\n }\n}\n\n/*\n * Overload: no buffer => return a TSID bigint.\n */\nfunction tsidFn(options?: TsidOptions, buf?: undefined, offset?: number): bigint\n/*\n * Overload: caller provides a buffer slice to fill with TSID bytes.\n */\nfunction tsidFn<TBuf extends Uint8Array = Uint8Array>(\n options: TsidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction tsidFn<TBuf extends Uint8Array = Uint8Array>(options?: TsidOptions, buf?: TBuf, offset = 0): bigint | TBuf {\n let msecsDiff: bigint\n let node: number\n let counterBits: number\n let counter: number\n\n if (options) {\n const resolved = resolveTsidOptions(options)\n msecsDiff = resolved.msecsDiff\n node = resolved.node\n counterBits = resolved.counterBits\n counter = resolved.counter\n } else {\n // Lazily initialize the persistent node ID on first no-option call.\n if (state.node === undefined) {\n state.node = randomUint32() & DEFAULT_NODE_MASK\n }\n node = state.node\n counterBits = DEFAULT_COUNTER_BITS\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling\n * to prevent side-channel attacks, so Date.now() returns the same value for\n * the entire duration of a request. Monotonic ordering within such a request\n * relies entirely on the counter (and its clock-drift-ahead overflow below).\n */\n const now = Date.now()\n if (now > state.msecs) {\n state.msecs = now\n state.counter = randomUint32() & DEFAULT_COUNTER_MASK\n } else {\n state.counter += 1\n if (state.counter > DEFAULT_COUNTER_MASK) {\n // Counter overflowed within this real millisecond: advance the internal\n // virtual timestamp ahead of wall-clock time rather than throwing. A\n // 12-bit counter (4096 values/ms) is a realistic throughput ceiling.\n state.msecs += 1\n state.counter = 0\n }\n }\n counter = state.counter\n msecsDiff = BigInt(state.msecs) - BigInt(TSID_EPOCH)\n }\n\n const packed = pack(msecsDiff, node, counterBits, counter)\n\n if (buf) {\n if (!isWritableRange(buf, offset, TSID_BYTES)) {\n throw new BufferError(\n 'TSID_BUFFER_OUT_OF_BOUNDS',\n `TSID byte range ${offset}:${offset + TSID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeTsidBytesUnchecked(packed, buf, offset)\n return buf\n }\n\n return packed\n}\n\n/**\n * Convert a TSID bigint to 8 bytes.\n */\nfunction toBytes(id: bigint): Uint8Array {\n assertValidTsid(id)\n const bytes = new Uint8Array(TSID_BYTES)\n writeTsidBytesUnchecked(id, bytes, 0)\n return bytes\n}\n\n/**\n * Convert 8 bytes to a TSID bigint.\n */\nfunction fromBytes(bytes: Uint8Array): bigint {\n if (bytes.length !== TSID_BYTES) {\n throw new BufferError(\n 'TSID_BYTES_INVALID_LENGTH',\n `TSID bytes must be exactly ${TSID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n let value = 0n\n for (let i = 0; i < TSID_BYTES; i += 1) {\n value = (value << 8n) | BigInt(bytes[i])\n }\n return value\n}\n\n/**\n * Extract the timestamp (milliseconds since Unix epoch) from a TSID.\n * The epoch is not self-describing in the packed value - pass the same\n * `epoch` used at generation time if it was overridden from the default.\n */\nfunction timestamp(id: bigint, epoch: number = TSID_EPOCH): number {\n assertValidTsid(id)\n if (!Number.isInteger(epoch)) {\n throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer')\n }\n return epoch + Number(id >> BigInt(RANDOM_BITS))\n}\n\n/**\n * Validate that a value is a TSID bigint within the 64-bit range.\n */\nfunction isValid(id: unknown): id is bigint {\n return typeof id === 'bigint' && id >= 0n && id <= MAX\n}\n\nconst NIL = 0n\nconst MAX = (1n << 64n) - 1n\n\nfunction assertValidTsid(id: bigint): void {\n if (id < NIL || id > MAX) {\n throw new InvalidInputError('TSID_VALUE_OUT_OF_RANGE', `TSID value must be between ${NIL} and ${MAX}`)\n }\n}\n\nfunction toString(id: bigint): string {\n assertValidTsid(id)\n return encodeTsidString(id)\n}\n\n/**\n * Generate a TSID bigint or write the bytes into a buffer.\n *\n * TSID (Time-Sorted Unique Identifier) is a 64-bit Snowflake-style identifier\n * combining a millisecond timestamp, a node ID, and a per-millisecond counter.\n * Unlike every other uniku generator, it returns a `bigint` by default, since\n * TSID's entire value proposition is native numeric storage (e.g. a database\n * BIGINT primary key).\n *\n * @example\n * ```ts\n * import { tsid } from 'uniku/tsid'\n *\n * const id = tsid()\n * // => 862301223059968074n\n *\n * // Canonical string form\n * const str = tsid.toString(id)\n * // => \"0QXW2CK4XZM2A\"\n * tsid.fromString(str) === id // true\n *\n * // Extract timestamp\n * const ts = tsid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * tsid.isValid(id) // true\n *\n * // Convert to/from bytes (8 bytes)\n * const bytes = tsid.toBytes(id)\n * const restored = tsid.fromBytes(bytes)\n * ```\n */\nexport const tsid: Tsid = Object.assign(tsidFn, {\n toBytes,\n fromBytes,\n toString,\n fromString: decodeTsidString,\n timestamp,\n isValid,\n NIL,\n MAX,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} | ||
| {"version":3,"file":"tsid.mjs","names":[],"sources":["../../src/tsid/crockford64.ts","../../src/tsid/tsid.ts"],"sourcesContent":["import { ParseError } from '../errors'\n\n/**\n * Crockford Base32 codec for TSID, operating directly on the primary `bigint`\n * value rather than an intermediate byte array (unlike ulid/crockford.ts) -\n * tsid's primary type never routes through a `Uint8Array` during string\n * conversion.\n *\n * TSID binary format: 64 bits (8 bytes), encoded as 13 Crockford Base32 characters.\n * 13 chars * 5 bits = 65 bits of capacity for a 64-bit value, leaving exactly 1 bit\n * of headroom on the leading character. That headroom bit is always 0, so the\n * leading character only carries 4 significant real bits (values 0-15, i.e. the\n * alphabet's first 16 symbols \"0\"-\"9\"/\"A\"-\"F\") rather than the full 32-symbol\n * range - the same overflow-prevention technique ULID uses for its own leading\n * `[0-7]` restriction (2 headroom bits there vs 1 here). Confirmed against\n * `tsid-ts`'s own `toCanonicalString`: `(number >> BigInt(60 - i * 5)) & 0x1f`.\n */\n\nconst TSID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\nconst TSID_STRING_LEN = 13\nconst TSID_LEADING_MAX = 15 // 4 significant bits on the leading character (see above)\n\n// Pre-computed decode table (case-insensitive) covering ASCII, so lookups never\n// go out of bounds and a single `=== 255` check rejects invalid input.\nconst DECODING = new Uint8Array(128).fill(255)\nfor (let i = 0; i < TSID_ALPHABET.length; i += 1) {\n const char = TSID_ALPHABET[i]\n const upperCode = TSID_ALPHABET.charCodeAt(i)\n DECODING[upperCode] = i\n // Only letters have a lowercase counterpart - applying +32 to digit codes\n // would land on unrelated uppercase letters (e.g. '5' + 32 -> 'U', which is\n // excluded from this alphabet and would never get overwritten back to 255).\n if (char >= 'A' && char <= 'Z') {\n DECODING[upperCode + 32] = i\n }\n}\n\n/**\n * Encode a TSID `bigint` value to its 13-character canonical Crockford Base32 string.\n */\nexport function encodeTsidString(value: bigint): string {\n let result = ''\n for (let i = 0; i < TSID_STRING_LEN; i += 1) {\n const shift = BigInt(60 - i * 5)\n const group = Number((value >> shift) & 0x1fn)\n result += TSID_ALPHABET[group]\n }\n return result\n}\n\n/**\n * Decode a 13-character canonical Crockford Base32 string to a TSID `bigint` value.\n */\nexport function decodeTsidString(str: string): bigint {\n if (str.length !== TSID_STRING_LEN) {\n throw new ParseError('TSID_INVALID_LENGTH', `TSID string must be ${TSID_STRING_LEN} characters, got ${str.length}`)\n }\n\n let value = 0n\n for (let i = 0; i < TSID_STRING_LEN; i += 1) {\n const code = str.charCodeAt(i)\n const group = code < 128 ? DECODING[code] : 255\n if (group === 255) {\n throw new ParseError('TSID_INVALID_CHAR', `Invalid TSID character: ${str[i]}`)\n }\n if (i === 0 && group > TSID_LEADING_MAX) {\n throw new ParseError(\n 'TSID_LEADING_CHAR_OUT_OF_RANGE',\n `TSID leading character must be one of 0-9, A-F, got: ${str[i]}`,\n )\n }\n value = (value << 5n) | BigInt(group)\n }\n return value\n}\n","import { randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeTsidString, encodeTsidString } from './crockford64'\n\n/**\n * TSID (Time-Sorted Unique Identifier)\n *\n * A 64-bit Snowflake-style identifier consisting of:\n * - 42 bits: millisecond timestamp relative to a custom epoch (default 2020-01-01T00:00:00.000Z)\n * - 10 bits: node ID (default; configurable via `nodeBits`)\n * - 12 bits: per-millisecond counter (default; `22 - nodeBits`)\n *\n * Unlike every other uniku generator, `tsid()` returns a `bigint` by default -\n * this is a deliberate design decision reflecting that TSID's value proposition\n * is native numeric storage (e.g. a database BIGINT primary key), not a\n * string-first identifier that happens to have a byte encoding. `toString`/\n * `fromString` are the boundary conversions to/from the 13-character canonical\n * Crockford Base32 string.\n *\n * Encoded as 8 big-endian bytes in buffer mode.\n */\n\nconst TSID_EPOCH = 1577836800000 // 2020-01-01T00:00:00.000Z\nconst TSID_BYTES = 8\nconst RANDOM_BITS = 22 // combined node + counter bits\nconst DEFAULT_NODE_BITS = 10\nconst DEFAULT_COUNTER_BITS = RANDOM_BITS - DEFAULT_NODE_BITS\nconst DEFAULT_NODE_MASK = (1 << DEFAULT_NODE_BITS) - 1\nconst DEFAULT_COUNTER_MASK = (1 << DEFAULT_COUNTER_BITS) - 1\nconst MAX_NODE_BITS = 20\nconst MAX_TIMESTAMP_DIFF = (1n << 42n) - 1n\n\nexport type TsidOptions = {\n /**\n * Timestamp in milliseconds since Unix epoch.\n * Defaults to Date.now().\n */\n msecs?: number\n /**\n * Custom epoch in milliseconds since Unix epoch.\n * Defaults to 1577836800000 (2020-01-01T00:00:00.000Z).\n */\n epoch?: number\n /**\n * Node ID (0 to 2^nodeBits - 1).\n * Defaults to a lazily-initialized, persistent random value.\n */\n node?: number\n /**\n * Number of bits allocated to the node ID (0-20).\n * The remaining bits (22 - nodeBits) are allocated to the counter.\n * Defaults to 10.\n */\n nodeBits?: number\n /**\n * Per-millisecond counter (0 to 2^(22 - nodeBits) - 1).\n * Defaults to a fresh random value on a new millisecond, or the previous\n * value incremented by 1 within the same millisecond.\n */\n counter?: number\n}\n\nexport type Tsid = {\n /** Generate a time-sorted TSID bigint. */\n (): bigint\n /** Generate a TSID with explicit options or write its 8 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a TSID bigint with optional timestamp, epoch, node, or counter controls. */\n (options?: TsidOptions, buf?: undefined, offset?: number): bigint\n /** Convert a TSID bigint to its canonical 8-byte representation. */\n toBytes(id: bigint): Uint8Array\n /** Convert 8 canonical TSID bytes to a TSID bigint. */\n fromBytes(bytes: Uint8Array): bigint\n /** Convert a TSID bigint to its canonical 13-character string. */\n toString(id: bigint): string\n /** Convert a canonical TSID string to a bigint. */\n fromString(str: string): bigint\n /** Read a TSID timestamp in milliseconds, using the supplied or default epoch. */\n timestamp(id: bigint, epoch?: number): number\n /** Return whether a value is a valid 64-bit TSID bigint. */\n isValid(id: unknown): id is bigint\n /** The nil TSID (all zeros) */\n NIL: bigint\n /** The max TSID (maximum valid 64-bit value) */\n MAX: bigint\n}\n\ntype TsidState = {\n node: number | undefined\n msecs: number\n counter: number\n}\n\n/**\n * Module-level state for the lazily-initialized persistent node ID and the\n * per-millisecond-reset counter.\n *\n * IMPORTANT: This state persists across all tsid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * - For isolated state, pass explicit `msecs`, `epoch`, `node`, `nodeBits`, or\n * `counter` via options - doing so bypasses `state` entirely.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: TsidState = {\n node: undefined,\n msecs: -Infinity,\n counter: 0,\n}\n\nfunction pack(msecsDiff: bigint, node: number, counterBits: number, counter: number): bigint {\n return (msecsDiff << BigInt(RANDOM_BITS)) | (BigInt(node) << BigInt(counterBits)) | BigInt(counter)\n}\n\nfunction writeTsidBytesUnchecked(value: bigint, buf: Uint8Array, offset: number): void {\n for (let i = 0; i < TSID_BYTES; i += 1) {\n buf[offset + i] = Number((value >> BigInt((TSID_BYTES - 1 - i) * 8)) & 0xffn)\n }\n}\n\ntype ResolvedTsidOptions = {\n msecsDiff: bigint\n node: number\n counterBits: number\n counter: number\n}\n\nfunction resolveTsidOptions(options: TsidOptions): ResolvedTsidOptions {\n const nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS\n if (!isIntegerInRange(nodeBits, 0, MAX_NODE_BITS)) {\n throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`)\n }\n\n const counterBits = RANDOM_BITS - nodeBits\n const nodeMask = (1 << nodeBits) - 1\n const counterMask = (1 << counterBits) - 1\n const optNode = options.node\n if (optNode !== undefined && !isIntegerInRange(optNode, 0, nodeMask)) {\n throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`)\n }\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, counterMask)) {\n throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`)\n }\n\n const epoch = options.epoch ?? TSID_EPOCH\n if (!Number.isInteger(epoch)) {\n throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer')\n }\n const msecs = options.msecs ?? Date.now()\n if (!Number.isInteger(msecs)) {\n throw new InvalidInputError('TSID_TIMESTAMP_INVALID', 'msecs must be a finite integer')\n }\n const msecsDiff = BigInt(msecs) - BigInt(epoch)\n if (msecsDiff < 0n || msecsDiff > MAX_TIMESTAMP_DIFF) {\n throw new InvalidInputError(\n 'TSID_TIMESTAMP_OUT_OF_RANGE',\n `msecs - epoch must be between 0 and ${MAX_TIMESTAMP_DIFF}`,\n )\n }\n\n return {\n msecsDiff,\n node: optNode ?? randomUint32() & nodeMask,\n counterBits,\n counter: optCounter ?? randomUint32() & counterMask,\n }\n}\n\n/*\n * Overload: no buffer => return a TSID bigint.\n */\nfunction tsidFn(options?: TsidOptions, buf?: undefined, offset?: number): bigint\n/*\n * Overload: caller provides a buffer slice to fill with TSID bytes.\n */\nfunction tsidFn<TBuf extends Uint8Array = Uint8Array>(\n options: TsidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction tsidFn<TBuf extends Uint8Array = Uint8Array>(options?: TsidOptions, buf?: TBuf, offset = 0): bigint | TBuf {\n let msecsDiff: bigint\n let node: number\n let counterBits: number\n let counter: number\n\n if (options) {\n const resolved = resolveTsidOptions(options)\n msecsDiff = resolved.msecsDiff\n node = resolved.node\n counterBits = resolved.counterBits\n counter = resolved.counter\n } else {\n // Lazily initialize the persistent node ID on first no-option call.\n if (state.node === undefined) {\n state.node = randomUint32() & DEFAULT_NODE_MASK\n }\n node = state.node\n counterBits = DEFAULT_COUNTER_BITS\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling\n * to prevent side-channel attacks, so Date.now() returns the same value for\n * the entire duration of a request. Monotonic ordering within such a request\n * relies entirely on the counter (and its clock-drift-ahead overflow below).\n */\n const now = Date.now()\n if (now > state.msecs) {\n state.msecs = now\n state.counter = randomUint32() & DEFAULT_COUNTER_MASK\n } else {\n state.counter += 1\n if (state.counter > DEFAULT_COUNTER_MASK) {\n // Counter overflowed within this real millisecond: advance the internal\n // virtual timestamp ahead of wall-clock time rather than throwing. A\n // 12-bit counter (4096 values/ms) is a realistic throughput ceiling.\n state.msecs += 1\n state.counter = 0\n }\n }\n counter = state.counter\n msecsDiff = BigInt(state.msecs) - BigInt(TSID_EPOCH)\n }\n\n const packed = pack(msecsDiff, node, counterBits, counter)\n\n if (buf) {\n if (!isWritableRange(buf, offset, TSID_BYTES)) {\n throw new BufferError(\n 'TSID_BUFFER_OUT_OF_BOUNDS',\n `TSID byte range ${offset}:${offset + TSID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeTsidBytesUnchecked(packed, buf, offset)\n return buf\n }\n\n return packed\n}\n\n/**\n * Convert a TSID bigint to 8 bytes.\n */\nfunction toBytes(id: bigint): Uint8Array {\n assertValidTsid(id)\n const bytes = new Uint8Array(TSID_BYTES)\n writeTsidBytesUnchecked(id, bytes, 0)\n return bytes\n}\n\n/**\n * Convert 8 bytes to a TSID bigint.\n */\nfunction fromBytes(bytes: Uint8Array): bigint {\n if (bytes.length !== TSID_BYTES) {\n throw new BufferError(\n 'TSID_BYTES_INVALID_LENGTH',\n `TSID bytes must be exactly ${TSID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n let value = 0n\n for (let i = 0; i < TSID_BYTES; i += 1) {\n value = (value << 8n) | BigInt(bytes[i])\n }\n return value\n}\n\n/**\n * Extract the timestamp (milliseconds since Unix epoch) from a TSID.\n * The epoch is not self-describing in the packed value - pass the same\n * `epoch` used at generation time if it was overridden from the default.\n */\nfunction timestamp(id: bigint, epoch: number = TSID_EPOCH): number {\n assertValidTsid(id)\n if (!Number.isInteger(epoch)) {\n throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer')\n }\n return epoch + Number(id >> BigInt(RANDOM_BITS))\n}\n\n/**\n * Validate that a value is a TSID bigint within the 64-bit range.\n */\nfunction isValid(id: unknown): id is bigint {\n return typeof id === 'bigint' && id >= 0n && id <= MAX\n}\n\nconst NIL = 0n\nconst MAX = (1n << 64n) - 1n\n\nfunction assertValidTsid(id: bigint): void {\n if (id < NIL || id > MAX) {\n throw new InvalidInputError('TSID_VALUE_OUT_OF_RANGE', `TSID value must be between ${NIL} and ${MAX}`)\n }\n}\n\nfunction toString(id: bigint): string {\n assertValidTsid(id)\n return encodeTsidString(id)\n}\n\n/**\n * Generate a TSID bigint or write the bytes into a buffer.\n *\n * TSID (Time-Sorted Unique Identifier) is a 64-bit Snowflake-style identifier\n * combining a millisecond timestamp, a node ID, and a per-millisecond counter.\n * Unlike every other uniku generator, it returns a `bigint` by default, since\n * TSID's entire value proposition is native numeric storage (e.g. a database\n * BIGINT primary key).\n *\n * @example\n * ```ts\n * import { tsid } from 'uniku/tsid'\n *\n * const id = tsid()\n * // => 862301223059968074n\n *\n * // Canonical string form\n * const str = tsid.toString(id)\n * // => \"0QXW2CK4XZM2A\"\n * tsid.fromString(str) === id // true\n *\n * // Extract timestamp\n * const ts = tsid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * tsid.isValid(id) // true\n *\n * // Convert to/from bytes (8 bytes)\n * const bytes = tsid.toBytes(id)\n * const restored = tsid.fromBytes(bytes)\n * ```\n */\nexport const tsid: Tsid = Object.assign(tsidFn, {\n toBytes,\n fromBytes,\n toString,\n fromString: decodeTsidString,\n timestamp,\n isValid,\n NIL,\n MAX,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
@@ -7,10 +7,10 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| type Typeid = { | ||
| (prefix: string, options?: TypeidOptions): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(prefix: string, bytes: Uint8Array): string; | ||
| toUuid(id: string): string; | ||
| fromUuid(prefix: string, uuid: string): string; | ||
| timestamp(id: string): number; | ||
| prefix(id: string): string; | ||
| suffix(id: string): string; | ||
| /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */(prefix: string, options?: TypeidOptions): string; /** Convert a TypeID's UUID v7 suffix to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array; /** Build a TypeID from a prefix and canonical UUID v7 bytes. */ | ||
| fromBytes(prefix: string, bytes: Uint8Array): string; /** Convert a TypeID to its UUID v7 string. */ | ||
| toUuid(id: string): string; /** Build a TypeID from a prefix and UUID v7 string. */ | ||
| fromUuid(prefix: string, uuid: string): string; /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. */ | ||
| timestamp(id: string): number; /** Read a TypeID's entity prefix. */ | ||
| prefix(id: string): string; /** Read a TypeID's UUID v7 suffix. */ | ||
| suffix(id: string): string; /** Return whether a value is a syntactically valid TypeID. */ | ||
| isValid(id: unknown): id is string; | ||
@@ -17,0 +17,0 @@ }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"typeid.d.mts","names":[],"sources":["../../src/typeid/typeid.ts"],"mappings":";;;;KAIY,aAAA,GAAgB,aAAA;AAAA,KAEhB,MAAA;EAAA,CACT,MAAA,UAAgB,OAAA,GAAU,aAAA;EAC3B,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,MAAA,UAAgB,KAAA,EAAO,UAAA;EACjC,MAAA,CAAO,EAAA;EACP,QAAA,CAAS,MAAA,UAAgB,IAAA;EACzB,SAAA,CAAU,EAAA;EACV,MAAA,CAAO,EAAA;EACP,MAAA,CAAO,EAAA;EACP,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;;;;;;;;;;;;;;;cAgQX,MAAA,EAAQ,MAAA"} | ||
| {"version":3,"file":"typeid.d.mts","names":[],"sources":["../../src/typeid/typeid.ts"],"mappings":";;;;KAIY,aAAA,GAAgB,aAAA;AAAA,KAEhB,MAAA;gFAET,MAAA,UAAgB,OAAA,GAAU,aAAA,WAJjB;EAMV,OAAA,CAAQ,EAAA,WAAa,UAAA,EANK;EAQ1B,SAAA,CAAU,MAAA,UAAgB,KAAA,EAAO,UAAA,WANnC;EAQE,MAAA,CAAO,EAAA;EAEP,QAAA,CAAS,MAAA,UAAgB,IAAA;EAEzB,SAAA,CAAU,EAAA,mBANuB;EAQjC,MAAA,CAAO,EAAA;EAEP,MAAA,CAAO,EAAA;EAEP,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;;;;;;;;;;;;;;;cAgQX,MAAA,EAAQ,MAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"typeid.mjs","names":[],"sources":["../../src/typeid/typeid.ts"],"sourcesContent":["import { InvalidInputError, ParseError } from '../errors'\nimport type { UuidV7Options } from '../uuid/v7'\nimport { uuidv7 } from '../uuid/v7'\n\nexport type TypeidOptions = UuidV7Options\n\nexport type Typeid = {\n (prefix: string, options?: TypeidOptions): string\n toBytes(id: string): Uint8Array\n fromBytes(prefix: string, bytes: Uint8Array): string\n toUuid(id: string): string\n fromUuid(prefix: string, uuid: string): string\n timestamp(id: string): number\n prefix(id: string): string\n suffix(id: string): string\n isValid(id: unknown): id is string\n}\n\nconst TYPEID_SUFFIX_LENGTH = 26\nconst TYPEID_UUID_BYTE_LENGTH = 16\nconst TYPEID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'\n\nconst BASE32_DECODE: number[] = Array.from({ length: 128 }, () => -1)\nfor (let i = 0; i < TYPEID_ALPHABET.length; i += 1) {\n BASE32_DECODE[TYPEID_ALPHABET.charCodeAt(i)] = i\n}\n\ntype ParsedTypeid = {\n prefix: string\n suffix: string\n bytes: Uint8Array\n}\n\nfunction assertValidPrefix(prefix: string): void {\n if (prefix.length === 0) {\n return\n }\n\n if (prefix.length > 63) {\n throw new InvalidInputError('TYPEID_PREFIX_TOO_LONG', 'TypeID prefix must be at most 63 characters')\n }\n\n for (let i = 0; i < prefix.length; i += 1) {\n const code = prefix.charCodeAt(i)\n const isLowercaseLetter = code >= 97 && code <= 122\n const isUnderscore = code === 95\n\n if (!isLowercaseLetter && !isUnderscore) {\n throw new InvalidInputError(\n 'TYPEID_PREFIX_INVALID_CHARACTER',\n 'TypeID prefix must contain only lowercase ASCII letters and underscores',\n )\n }\n\n if ((i === 0 || i === prefix.length - 1) && !isLowercaseLetter) {\n throw new InvalidInputError('TYPEID_PREFIX_INVALID_BOUNDARY', 'TypeID prefix must start and end with a-z')\n }\n }\n}\n\nfunction decodeSuffixValue(suffix: string, index: number): number {\n const code = suffix.charCodeAt(index)\n const value = code < BASE32_DECODE.length ? BASE32_DECODE[code] : -1\n\n if (value === -1) {\n throw new ParseError(\n 'TYPEID_SUFFIX_INVALID_CHARACTER',\n `TypeID suffix contains invalid character at position ${index}`,\n )\n }\n\n return value\n}\n\nfunction assertValidSuffixShape(suffix: string): void {\n if (suffix.length !== TYPEID_SUFFIX_LENGTH) {\n throw new ParseError('TYPEID_SUFFIX_INVALID_LENGTH', `TypeID suffix must be 26 characters, got ${suffix.length}`)\n }\n\n if (suffix[0] > '7') {\n throw new ParseError('TYPEID_SUFFIX_OVERFLOW', 'TypeID suffix must encode a 128-bit value')\n }\n}\n\nfunction encodeBytesToSuffix(bytes: Uint8Array): string {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('TYPEID_UUID_BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`)\n }\n\n return (\n TYPEID_ALPHABET[(bytes[0] & 0xe0) >> 5] +\n TYPEID_ALPHABET[bytes[0] & 0x1f] +\n TYPEID_ALPHABET[(bytes[1] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[1] & 0x07) << 2) | ((bytes[2] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[2] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[2] & 0x01) << 4) | ((bytes[3] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[3] & 0x0f) << 1) | ((bytes[4] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[4] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[4] & 0x03) << 3) | ((bytes[5] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[5] & 0x1f] +\n TYPEID_ALPHABET[(bytes[6] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[6] & 0x07) << 2) | ((bytes[7] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[7] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[7] & 0x01) << 4) | ((bytes[8] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[8] & 0x0f) << 1) | ((bytes[9] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[9] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[9] & 0x03) << 3) | ((bytes[10] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[10] & 0x1f] +\n TYPEID_ALPHABET[(bytes[11] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[11] & 0x07) << 2) | ((bytes[12] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[12] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[12] & 0x01) << 4) | ((bytes[13] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[13] & 0x0f) << 1) | ((bytes[14] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[14] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[14] & 0x03) << 3) | ((bytes[15] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[15] & 0x1f]\n )\n}\n\nfunction decodeSuffixToBytes(suffix: string): Uint8Array {\n assertValidSuffixShape(suffix)\n\n const values = new Array<number>(TYPEID_SUFFIX_LENGTH)\n for (let i = 0; i < TYPEID_SUFFIX_LENGTH; i += 1) {\n values[i] = decodeSuffixValue(suffix, i)\n }\n\n const bytes = new Uint8Array(TYPEID_UUID_BYTE_LENGTH)\n bytes[0] = (values[0] << 5) | values[1]\n bytes[1] = (values[2] << 3) | (values[3] >> 2)\n bytes[2] = ((values[3] & 0x03) << 6) | (values[4] << 1) | (values[5] >> 4)\n bytes[3] = ((values[5] & 0x0f) << 4) | (values[6] >> 1)\n bytes[4] = ((values[6] & 0x01) << 7) | (values[7] << 2) | (values[8] >> 3)\n bytes[5] = ((values[8] & 0x07) << 5) | values[9]\n bytes[6] = (values[10] << 3) | (values[11] >> 2)\n bytes[7] = ((values[11] & 0x03) << 6) | (values[12] << 1) | (values[13] >> 4)\n bytes[8] = ((values[13] & 0x0f) << 4) | (values[14] >> 1)\n bytes[9] = ((values[14] & 0x01) << 7) | (values[15] << 2) | (values[16] >> 3)\n bytes[10] = ((values[16] & 0x07) << 5) | values[17]\n bytes[11] = (values[18] << 3) | (values[19] >> 2)\n bytes[12] = ((values[19] & 0x03) << 6) | (values[20] << 1) | (values[21] >> 4)\n bytes[13] = ((values[21] & 0x0f) << 4) | (values[22] >> 1)\n bytes[14] = ((values[22] & 0x01) << 7) | (values[23] << 2) | (values[24] >> 3)\n bytes[15] = ((values[24] & 0x07) << 5) | values[25]\n\n return bytes\n}\n\nfunction assertUuidV7Bytes(bytes: Uint8Array): void {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('TYPEID_UUID_BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`)\n }\n\n if (bytes[6] >> 4 !== 7 || (bytes[8] & 0xc0) !== 0x80) {\n throw new InvalidInputError('TYPEID_UUID_NOT_V7', 'TypeID UUID bytes must encode a UUID v7 value')\n }\n}\n\nfunction uuidV7BytesFromSuffix(suffix: string): Uint8Array {\n const bytes = decodeSuffixToBytes(suffix)\n assertUuidV7Bytes(bytes)\n return bytes\n}\n\nfunction uuidV7BytesFromUuid(uuid: string): Uint8Array {\n if (!uuidv7.isValid(uuid)) {\n throw new InvalidInputError('TYPEID_UUID_NOT_V7', 'TypeID can only wrap UUID v7 values')\n }\n\n return uuidv7.toBytes(uuid)\n}\n\nfunction parseTypeid(id: string): ParsedTypeid {\n const separatorIndex = id.lastIndexOf('_')\n\n if (separatorIndex === 0) {\n throw new ParseError('TYPEID_INVALID_FORMAT', 'TypeID must not start with \"_\"')\n }\n\n const prefix = separatorIndex === -1 ? '' : id.slice(0, separatorIndex)\n const suffix = separatorIndex === -1 ? id : id.slice(separatorIndex + 1)\n\n assertValidPrefix(prefix)\n const bytes = uuidV7BytesFromSuffix(suffix)\n\n return { prefix, suffix, bytes }\n}\n\nfunction joinPrefixAndSuffix(prefix: string, suffix: string): string {\n return prefix === '' ? suffix : `${prefix}_${suffix}`\n}\n\nfunction formatTypeidFromUuidV7Bytes(prefix: string, bytes: Uint8Array): string {\n assertValidPrefix(prefix)\n assertUuidV7Bytes(bytes)\n return joinPrefixAndSuffix(prefix, encodeBytesToSuffix(bytes))\n}\n\nfunction timestampFromUuidV7Bytes(bytes: Uint8Array): number {\n let msecs = 0\n for (let i = 0; i < 6; i += 1) {\n msecs = msecs * 256 + bytes[i]\n }\n return msecs\n}\n\nfunction typeidFn(prefix: string, options?: TypeidOptions): string {\n const bytes = uuidv7(options, new Uint8Array(TYPEID_UUID_BYTE_LENGTH))\n return formatTypeidFromUuidV7Bytes(prefix, bytes)\n}\n\nfunction typeidToBytes(id: string): Uint8Array {\n return parseTypeid(id).bytes\n}\n\nfunction typeidFromBytes(prefix: string, bytes: Uint8Array): string {\n return formatTypeidFromUuidV7Bytes(prefix, bytes)\n}\n\nfunction typeidToUuid(id: string): string {\n return uuidv7.fromBytes(typeidToBytes(id))\n}\n\nfunction typeidFromUuid(prefix: string, uuid: string): string {\n return formatTypeidFromUuidV7Bytes(prefix, uuidV7BytesFromUuid(uuid))\n}\n\nfunction timestamp(id: string): number {\n return timestampFromUuidV7Bytes(parseTypeid(id).bytes)\n}\n\nfunction prefix(id: string): string {\n return parseTypeid(id).prefix\n}\n\nfunction suffix(id: string): string {\n return parseTypeid(id).suffix\n}\n\nfunction isValid(id: unknown): id is string {\n if (typeof id !== 'string') {\n return false\n }\n\n try {\n parseTypeid(id)\n } catch {\n return false\n }\n\n return true\n}\n\n/**\n * Generate a TypeID string backed by UUID v7.\n *\n * TypeID combines a lowercase snake_case type prefix with a 26-character\n * modified base32 encoding of a UUID v7, producing sortable domain identifiers\n * such as `user_01h2xcejqtf2nbrexx3vqjhp41`.\n *\n * @example\n * ```ts\n * import { typeid } from 'uniku/typeid'\n *\n * const id = typeid('user')\n * // => \"user_01h2xcejqtf2nbrexx3vqjhp41\"\n *\n * const uuid = typeid.toUuid(id)\n * const restored = typeid.fromUuid('user', uuid)\n * ```\n */\nexport const typeid: Typeid = Object.assign(typeidFn, {\n toBytes: typeidToBytes,\n fromBytes: typeidFromBytes,\n toUuid: typeidToUuid,\n fromUuid: typeidFromUuid,\n timestamp,\n prefix,\n suffix,\n isValid,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"8IAkBA,MAEM,EAAkB,mCAElB,EAA0B,MAAM,KAAK,CAAE,OAAQ,GAAI,MAAS,EAAE,EACpE,IAAK,IAAI,EAAI,EAAG,EAAI,GAAwB,GAAK,EAC/C,EAAc,EAAgB,WAAW,CAAC,GAAK,EASjD,SAAS,EAAkB,EAAsB,CAC3C,KAAO,SAAW,EAItB,IAAI,EAAO,OAAS,GAClB,MAAM,IAAI,EAAkB,yBAA0B,6CAA6C,EAGrG,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,GAAK,EAAG,CACzC,IAAM,EAAO,EAAO,WAAW,CAAC,EAC1B,EAAoB,GAAQ,IAAM,GAAQ,IAGhD,GAAI,CAAC,GAFgB,IAAS,GAG5B,MAAM,IAAI,EACR,kCACA,yEACF,EAGF,IAAK,IAAM,GAAK,IAAM,EAAO,OAAS,IAAM,CAAC,EAC3C,MAAM,IAAI,EAAkB,iCAAkC,2CAA2C,CAE7G,CAlBqG,CAmBvG,CAEA,SAAS,EAAkB,EAAgB,EAAuB,CAChE,IAAM,EAAO,EAAO,WAAW,CAAK,EAC9B,EAAQ,EAAO,EAAc,OAAS,EAAc,GAAQ,GAElE,GAAI,IAAU,GACZ,MAAM,IAAI,EACR,kCACA,wDAAwD,GAC1D,EAGF,OAAO,CACT,CAEA,SAAS,EAAuB,EAAsB,CACpD,GAAI,EAAO,SAAW,GACpB,MAAM,IAAI,EAAW,+BAAgC,4CAA4C,EAAO,QAAQ,EAGlH,GAAI,EAAO,GAAK,IACd,MAAM,IAAI,EAAW,yBAA0B,2CAA2C,CAE9F,CAEA,SAAS,EAAoB,EAA2B,CACtD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAkB,mCAAoC,oCAAoC,EAAM,QAAQ,EAGpH,OACE,GAAiB,EAAM,GAAK,MAAS,GACrC,EAAgB,EAAM,GAAK,IAC3B,GAAiB,EAAM,GAAK,MAAS,GACrC,GAAkB,EAAM,GAAK,IAAS,GAAO,EAAM,GAAK,MAAS,GACjE,GAAiB,EAAM,GAAK,KAAS,GACrC,GAAkB,EAAM,GAAK,IAAS,GAAO,EAAM,GAAK,MAAS,GACjE,GAAkB,EAAM,GAAK,KAAS,GAAO,EAAM,GAAK,MAAS,GACjE,GAAiB,EAAM,GAAK,MAAS,GACrC,GAAkB,EAAM,GAAK,IAAS,GAAO,EAAM,GAAK,MAAS,GACjE,EAAgB,EAAM,GAAK,IAC3B,GAAiB,EAAM,GAAK,MAAS,GACrC,GAAkB,EAAM,GAAK,IAAS,GAAO,EAAM,GAAK,MAAS,GACjE,GAAiB,EAAM,GAAK,KAAS,GACrC,GAAkB,EAAM,GAAK,IAAS,GAAO,EAAM,GAAK,MAAS,GACjE,GAAkB,EAAM,GAAK,KAAS,GAAO,EAAM,GAAK,MAAS,GACjE,GAAiB,EAAM,GAAK,MAAS,GACrC,GAAkB,EAAM,GAAK,IAAS,GAAO,EAAM,IAAM,MAAS,GAClE,EAAgB,EAAM,IAAM,IAC5B,GAAiB,EAAM,IAAM,MAAS,GACtC,GAAkB,EAAM,IAAM,IAAS,GAAO,EAAM,IAAM,MAAS,GACnE,GAAiB,EAAM,IAAM,KAAS,GACtC,GAAkB,EAAM,IAAM,IAAS,GAAO,EAAM,IAAM,MAAS,GACnE,GAAkB,EAAM,IAAM,KAAS,GAAO,EAAM,IAAM,MAAS,GACnE,GAAiB,EAAM,IAAM,MAAS,GACtC,GAAkB,EAAM,IAAM,IAAS,GAAO,EAAM,IAAM,MAAS,GACnE,EAAgB,EAAM,IAAM,GAEhC,CAEA,SAAS,EAAoB,EAA4B,CACvD,EAAuB,CAAM,EAE7B,IAAM,EAAa,MAAc,EAAoB,EACrD,IAAK,IAAI,EAAI,EAAG,EAAI,GAAsB,GAAK,EAC7C,EAAO,GAAK,EAAkB,EAAQ,CAAC,EAGzC,IAAM,EAAQ,IAAI,WAAW,EAAuB,EAkBpD,MAjBA,GAAM,GAAM,EAAO,IAAM,EAAK,EAAO,GACrC,EAAM,GAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EAC5C,EAAM,IAAO,EAAO,GAAK,IAAS,EAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EACxE,EAAM,IAAO,EAAO,GAAK,KAAS,EAAM,EAAO,IAAM,EACrD,EAAM,IAAO,EAAO,GAAK,IAAS,EAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EACxE,EAAM,IAAO,EAAO,GAAK,IAAS,EAAK,EAAO,GAC9C,EAAM,GAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EAC9C,EAAM,IAAO,EAAO,IAAM,IAAS,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EAC3E,EAAM,IAAO,EAAO,IAAM,KAAS,EAAM,EAAO,KAAO,EACvD,EAAM,IAAO,EAAO,IAAM,IAAS,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EAC3E,EAAM,KAAQ,EAAO,IAAM,IAAS,EAAK,EAAO,IAChD,EAAM,IAAO,EAAO,KAAO,EAAM,EAAO,KAAO,EAC/C,EAAM,KAAQ,EAAO,IAAM,IAAS,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EAC5E,EAAM,KAAQ,EAAO,IAAM,KAAS,EAAM,EAAO,KAAO,EACxD,EAAM,KAAQ,EAAO,IAAM,IAAS,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EAC5E,EAAM,KAAQ,EAAO,IAAM,IAAS,EAAK,EAAO,IAEzC,CACT,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAkB,mCAAoC,oCAAoC,EAAM,QAAQ,EAGpH,GAAI,EAAM,IAAM,GAAM,IAAM,EAAM,GAAK,MAAU,IAC/C,MAAM,IAAI,EAAkB,qBAAsB,+CAA+C,CAErG,CAEA,SAAS,EAAsB,EAA4B,CACzD,IAAM,EAAQ,EAAoB,CAAM,EAExC,OADA,EAAkB,CAAK,EAChB,CACT,CAEA,SAAS,EAAoB,EAA0B,CACrD,GAAI,CAAC,EAAO,QAAQ,CAAI,EACtB,MAAM,IAAI,EAAkB,qBAAsB,qCAAqC,EAGzF,OAAO,EAAO,QAAQ,CAAI,CAC5B,CAEA,SAAS,EAAY,EAA0B,CAC7C,IAAM,EAAiB,EAAG,YAAY,GAAG,EAEzC,GAAI,IAAmB,EACrB,MAAM,IAAI,EAAW,wBAAyB,gCAAgC,EAGhF,IAAM,EAAS,IAAmB,GAAK,GAAK,EAAG,MAAM,EAAG,CAAc,EAChE,EAAS,IAAmB,GAAK,EAAK,EAAG,MAAM,EAAiB,CAAC,EAKvE,OAHA,EAAkB,CAAM,EAGjB,CAAE,SAAQ,SAAQ,MAFX,EAAsB,CAEP,CAAE,CACjC,CAEA,SAAS,EAAoB,EAAgB,EAAwB,CACnE,OAAO,IAAW,GAAK,EAAS,GAAG,EAAO,GAAG,GAC/C,CAEA,SAAS,EAA4B,EAAgB,EAA2B,CAG9E,OAFA,EAAkB,CAAM,EACxB,EAAkB,CAAK,EAChB,EAAoB,EAAQ,EAAoB,CAAK,CAAC,CAC/D,CAEA,SAAS,EAAyB,EAA2B,CAC3D,IAAI,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,GAAK,EAC1B,EAAQ,EAAQ,IAAM,EAAM,GAE9B,OAAO,CACT,CAEA,SAAS,EAAS,EAAgB,EAAiC,CAEjE,OAAO,EAA4B,EADrB,EAAO,EAAS,IAAI,WAAW,EAAuB,CACrB,CAAC,CAClD,CAEA,SAAS,EAAc,EAAwB,CAC7C,OAAO,EAAY,CAAE,CAAC,CAAC,KACzB,CAEA,SAAS,EAAgB,EAAgB,EAA2B,CAClE,OAAO,EAA4B,EAAQ,CAAK,CAClD,CAEA,SAAS,EAAa,EAAoB,CACxC,OAAO,EAAO,UAAU,EAAc,CAAE,CAAC,CAC3C,CAEA,SAAS,EAAe,EAAgB,EAAsB,CAC5D,OAAO,EAA4B,EAAQ,EAAoB,CAAI,CAAC,CACtE,CAEA,SAAS,EAAU,EAAoB,CACrC,OAAO,EAAyB,EAAY,CAAE,CAAC,CAAC,KAAK,CACvD,CAEA,SAAS,EAAO,EAAoB,CAClC,OAAO,EAAY,CAAE,CAAC,CAAC,MACzB,CAEA,SAAS,EAAO,EAAoB,CAClC,OAAO,EAAY,CAAE,CAAC,CAAC,MACzB,CAEA,SAAS,EAAQ,EAA2B,CAC1C,GAAI,OAAO,GAAO,SAChB,MAAO,GAGT,GAAI,CACF,EAAY,CAAE,CAChB,MAAQ,CACN,MAAO,EACT,CAEA,MAAO,EACT,CAoBA,MAAa,EAAiB,OAAO,OAAO,EAAU,CACpD,QAAS,EACT,UAAW,EACX,OAAQ,EACR,SAAU,EACV,YACA,SACA,SACA,SACF,CAAC"} | ||
| {"version":3,"file":"typeid.mjs","names":[],"sources":["../../src/typeid/typeid.ts"],"sourcesContent":["import { InvalidInputError, ParseError } from '../errors'\nimport type { UuidV7Options } from '../uuid/v7'\nimport { uuidv7 } from '../uuid/v7'\n\nexport type TypeidOptions = UuidV7Options\n\nexport type Typeid = {\n /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */\n (prefix: string, options?: TypeidOptions): string\n /** Convert a TypeID's UUID v7 suffix to its canonical 16-byte representation. */\n toBytes(id: string): Uint8Array\n /** Build a TypeID from a prefix and canonical UUID v7 bytes. */\n fromBytes(prefix: string, bytes: Uint8Array): string\n /** Convert a TypeID to its UUID v7 string. */\n toUuid(id: string): string\n /** Build a TypeID from a prefix and UUID v7 string. */\n fromUuid(prefix: string, uuid: string): string\n /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. */\n timestamp(id: string): number\n /** Read a TypeID's entity prefix. */\n prefix(id: string): string\n /** Read a TypeID's UUID v7 suffix. */\n suffix(id: string): string\n /** Return whether a value is a syntactically valid TypeID. */\n isValid(id: unknown): id is string\n}\n\nconst TYPEID_SUFFIX_LENGTH = 26\nconst TYPEID_UUID_BYTE_LENGTH = 16\nconst TYPEID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'\n\nconst BASE32_DECODE: number[] = Array.from({ length: 128 }, () => -1)\nfor (let i = 0; i < TYPEID_ALPHABET.length; i += 1) {\n BASE32_DECODE[TYPEID_ALPHABET.charCodeAt(i)] = i\n}\n\ntype ParsedTypeid = {\n prefix: string\n suffix: string\n bytes: Uint8Array\n}\n\nfunction assertValidPrefix(prefix: string): void {\n if (prefix.length === 0) {\n return\n }\n\n if (prefix.length > 63) {\n throw new InvalidInputError('TYPEID_PREFIX_TOO_LONG', 'TypeID prefix must be at most 63 characters')\n }\n\n for (let i = 0; i < prefix.length; i += 1) {\n const code = prefix.charCodeAt(i)\n const isLowercaseLetter = code >= 97 && code <= 122\n const isUnderscore = code === 95\n\n if (!isLowercaseLetter && !isUnderscore) {\n throw new InvalidInputError(\n 'TYPEID_PREFIX_INVALID_CHARACTER',\n 'TypeID prefix must contain only lowercase ASCII letters and underscores',\n )\n }\n\n if ((i === 0 || i === prefix.length - 1) && !isLowercaseLetter) {\n throw new InvalidInputError('TYPEID_PREFIX_INVALID_BOUNDARY', 'TypeID prefix must start and end with a-z')\n }\n }\n}\n\nfunction decodeSuffixValue(suffix: string, index: number): number {\n const code = suffix.charCodeAt(index)\n const value = code < BASE32_DECODE.length ? BASE32_DECODE[code] : -1\n\n if (value === -1) {\n throw new ParseError(\n 'TYPEID_SUFFIX_INVALID_CHARACTER',\n `TypeID suffix contains invalid character at position ${index}`,\n )\n }\n\n return value\n}\n\nfunction assertValidSuffixShape(suffix: string): void {\n if (suffix.length !== TYPEID_SUFFIX_LENGTH) {\n throw new ParseError('TYPEID_SUFFIX_INVALID_LENGTH', `TypeID suffix must be 26 characters, got ${suffix.length}`)\n }\n\n if (suffix[0] > '7') {\n throw new ParseError('TYPEID_SUFFIX_OVERFLOW', 'TypeID suffix must encode a 128-bit value')\n }\n}\n\nfunction encodeBytesToSuffix(bytes: Uint8Array): string {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('TYPEID_UUID_BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`)\n }\n\n return (\n TYPEID_ALPHABET[(bytes[0] & 0xe0) >> 5] +\n TYPEID_ALPHABET[bytes[0] & 0x1f] +\n TYPEID_ALPHABET[(bytes[1] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[1] & 0x07) << 2) | ((bytes[2] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[2] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[2] & 0x01) << 4) | ((bytes[3] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[3] & 0x0f) << 1) | ((bytes[4] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[4] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[4] & 0x03) << 3) | ((bytes[5] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[5] & 0x1f] +\n TYPEID_ALPHABET[(bytes[6] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[6] & 0x07) << 2) | ((bytes[7] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[7] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[7] & 0x01) << 4) | ((bytes[8] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[8] & 0x0f) << 1) | ((bytes[9] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[9] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[9] & 0x03) << 3) | ((bytes[10] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[10] & 0x1f] +\n TYPEID_ALPHABET[(bytes[11] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[11] & 0x07) << 2) | ((bytes[12] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[12] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[12] & 0x01) << 4) | ((bytes[13] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[13] & 0x0f) << 1) | ((bytes[14] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[14] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[14] & 0x03) << 3) | ((bytes[15] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[15] & 0x1f]\n )\n}\n\nfunction decodeSuffixToBytes(suffix: string): Uint8Array {\n assertValidSuffixShape(suffix)\n\n const values = new Array<number>(TYPEID_SUFFIX_LENGTH)\n for (let i = 0; i < TYPEID_SUFFIX_LENGTH; i += 1) {\n values[i] = decodeSuffixValue(suffix, i)\n }\n\n const bytes = new Uint8Array(TYPEID_UUID_BYTE_LENGTH)\n bytes[0] = (values[0] << 5) | values[1]\n bytes[1] = (values[2] << 3) | (values[3] >> 2)\n bytes[2] = ((values[3] & 0x03) << 6) | (values[4] << 1) | (values[5] >> 4)\n bytes[3] = ((values[5] & 0x0f) << 4) | (values[6] >> 1)\n bytes[4] = ((values[6] & 0x01) << 7) | (values[7] << 2) | (values[8] >> 3)\n bytes[5] = ((values[8] & 0x07) << 5) | values[9]\n bytes[6] = (values[10] << 3) | (values[11] >> 2)\n bytes[7] = ((values[11] & 0x03) << 6) | (values[12] << 1) | (values[13] >> 4)\n bytes[8] = ((values[13] & 0x0f) << 4) | (values[14] >> 1)\n bytes[9] = ((values[14] & 0x01) << 7) | (values[15] << 2) | (values[16] >> 3)\n bytes[10] = ((values[16] & 0x07) << 5) | values[17]\n bytes[11] = (values[18] << 3) | (values[19] >> 2)\n bytes[12] = ((values[19] & 0x03) << 6) | (values[20] << 1) | (values[21] >> 4)\n bytes[13] = ((values[21] & 0x0f) << 4) | (values[22] >> 1)\n bytes[14] = ((values[22] & 0x01) << 7) | (values[23] << 2) | (values[24] >> 3)\n bytes[15] = ((values[24] & 0x07) << 5) | values[25]\n\n return bytes\n}\n\nfunction assertUuidV7Bytes(bytes: Uint8Array): void {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('TYPEID_UUID_BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`)\n }\n\n if (bytes[6] >> 4 !== 7 || (bytes[8] & 0xc0) !== 0x80) {\n throw new InvalidInputError('TYPEID_UUID_NOT_V7', 'TypeID UUID bytes must encode a UUID v7 value')\n }\n}\n\nfunction uuidV7BytesFromSuffix(suffix: string): Uint8Array {\n const bytes = decodeSuffixToBytes(suffix)\n assertUuidV7Bytes(bytes)\n return bytes\n}\n\nfunction uuidV7BytesFromUuid(uuid: string): Uint8Array {\n if (!uuidv7.isValid(uuid)) {\n throw new InvalidInputError('TYPEID_UUID_NOT_V7', 'TypeID can only wrap UUID v7 values')\n }\n\n return uuidv7.toBytes(uuid)\n}\n\nfunction parseTypeid(id: string): ParsedTypeid {\n const separatorIndex = id.lastIndexOf('_')\n\n if (separatorIndex === 0) {\n throw new ParseError('TYPEID_INVALID_FORMAT', 'TypeID must not start with \"_\"')\n }\n\n const prefix = separatorIndex === -1 ? '' : id.slice(0, separatorIndex)\n const suffix = separatorIndex === -1 ? id : id.slice(separatorIndex + 1)\n\n assertValidPrefix(prefix)\n const bytes = uuidV7BytesFromSuffix(suffix)\n\n return { prefix, suffix, bytes }\n}\n\nfunction joinPrefixAndSuffix(prefix: string, suffix: string): string {\n return prefix === '' ? suffix : `${prefix}_${suffix}`\n}\n\nfunction formatTypeidFromUuidV7Bytes(prefix: string, bytes: Uint8Array): string {\n assertValidPrefix(prefix)\n assertUuidV7Bytes(bytes)\n return joinPrefixAndSuffix(prefix, encodeBytesToSuffix(bytes))\n}\n\nfunction timestampFromUuidV7Bytes(bytes: Uint8Array): number {\n let msecs = 0\n for (let i = 0; i < 6; i += 1) {\n msecs = msecs * 256 + bytes[i]\n }\n return msecs\n}\n\nfunction typeidFn(prefix: string, options?: TypeidOptions): string {\n const bytes = uuidv7(options, new Uint8Array(TYPEID_UUID_BYTE_LENGTH))\n return formatTypeidFromUuidV7Bytes(prefix, bytes)\n}\n\nfunction typeidToBytes(id: string): Uint8Array {\n return parseTypeid(id).bytes\n}\n\nfunction typeidFromBytes(prefix: string, bytes: Uint8Array): string {\n return formatTypeidFromUuidV7Bytes(prefix, bytes)\n}\n\nfunction typeidToUuid(id: string): string {\n return uuidv7.fromBytes(typeidToBytes(id))\n}\n\nfunction typeidFromUuid(prefix: string, uuid: string): string {\n return formatTypeidFromUuidV7Bytes(prefix, uuidV7BytesFromUuid(uuid))\n}\n\nfunction timestamp(id: string): number {\n return timestampFromUuidV7Bytes(parseTypeid(id).bytes)\n}\n\nfunction prefix(id: string): string {\n return parseTypeid(id).prefix\n}\n\nfunction suffix(id: string): string {\n return parseTypeid(id).suffix\n}\n\nfunction isValid(id: unknown): id is string {\n if (typeof id !== 'string') {\n return false\n }\n\n try {\n parseTypeid(id)\n } catch {\n return false\n }\n\n return true\n}\n\n/**\n * Generate a TypeID string backed by UUID v7.\n *\n * TypeID combines a lowercase snake_case type prefix with a 26-character\n * modified base32 encoding of a UUID v7, producing sortable domain identifiers\n * such as `user_01h2xcejqtf2nbrexx3vqjhp41`.\n *\n * @example\n * ```ts\n * import { typeid } from 'uniku/typeid'\n *\n * const id = typeid('user')\n * // => \"user_01h2xcejqtf2nbrexx3vqjhp41\"\n *\n * const uuid = typeid.toUuid(id)\n * const restored = typeid.fromUuid('user', uuid)\n * ```\n */\nexport const typeid: Typeid = Object.assign(typeidFn, {\n toBytes: typeidToBytes,\n fromBytes: typeidFromBytes,\n toUuid: typeidToUuid,\n fromUuid: typeidFromUuid,\n timestamp,\n prefix,\n suffix,\n isValid,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
@@ -17,8 +17,8 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| type Ulid = { | ||
| (): string; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: UlidOptions, buf?: undefined, offset?: number): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): string; | ||
| timestamp(id: string): number; | ||
| /** Generate a time-ordered ULID string. */(): string; /** Generate a ULID with explicit options or write its 16 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf; /** Generate a ULID string with optional timestamp or random bytes. */ | ||
| (options?: UlidOptions, buf?: undefined, offset?: number): string; /** Convert a ULID string to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array; /** Convert 16 canonical ULID bytes to a ULID string. */ | ||
| fromBytes(bytes: Uint8Array): string; /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number; /** Return whether a value is a syntactically valid ULID string. */ | ||
| isValid(id: unknown): id is string; /** The nil ULID (all zeros) */ | ||
@@ -28,2 +28,6 @@ NIL: string; /** The max ULID (maximum valid value) */ | ||
| }; | ||
| /** | ||
| * Generate a ULID string or write its 16 canonical bytes into a buffer. | ||
| * ULIDs are URL-safe, carry a millisecond timestamp, and sort by creation time. | ||
| */ | ||
| declare const ulid: Ulid; | ||
@@ -30,0 +34,0 @@ //#endregion |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ulid.d.mts","names":[],"sources":["../../src/ulid/ulid.ts"],"mappings":";;;KAMY,WAAA;;;AAAZ;;EAKE,MAAA,GAAS,UAAA;EAAA;;;;EAKT,KAAA;AAAA;AAAA,KAGU,IAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,WAAA,cAAyB,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACrG,OAAA,GAAU,WAAA,EAAa,GAAA,cAAiB,MAAA;EACzC,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;AAAA,cA2JW,IAAA,EAAM,IAAA"} | ||
| {"version":3,"file":"ulid.d.mts","names":[],"sources":["../../src/ulid/ulid.ts"],"mappings":";;;KAMY,WAAA;;;AAAZ;;EAKE,MAAA,GAAS,UAAA;EAAA;;;;EAKT,KAAA;AAAA;AAAA,KAGU,IAAA;EAAA;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,WAAA,cAAyB,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAErG,OAAA,GAAU,WAAA,EAAa,GAAA,cAAiB,MAAA;EAEzC,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,KAAA,EAAO,UAAA,WAAA;EAEjB,SAAA,CAAU,EAAA;EAEV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;cA+JW,IAAA,EAAM,IAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ulid.mjs","names":[],"sources":["../../src/ulid/crockford.ts","../../src/ulid/ulid.ts"],"sourcesContent":["/**\n * Crockford's Base32 encoding/decoding for ULID.\n * Alphabet excludes I, L, O, U to avoid confusion with similar-looking characters.\n */\n\nimport { BufferError, ParseError } from '../errors'\n\nconst ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n// Pre-computed decoding table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\nfor (let i = 0; i < ENCODING.length; i += 1) {\n const upper = ENCODING.charCodeAt(i)\n const lower = ENCODING[i].toLowerCase().charCodeAt(0)\n DECODING[upper] = i\n DECODING[lower] = i\n}\n\nconst TIME_LEN = 10\nconst ULID_LEN = 26\n\nfunction invalidCharError(str: string, index: number): ParseError {\n return new ParseError('ULID_INVALID_CHAR', `Invalid ULID character: ${str[index]}`)\n}\n\nfunction timestampOverflowError(): ParseError {\n return new ParseError('ULID_TIMESTAMP_OVERFLOW', 'ULID timestamp exceeds 48 bits')\n}\n\nfunction decodeTimeChars(str: string): number {\n const firstValue = DECODING[str.charCodeAt(0)]\n if (firstValue === 255) {\n throw invalidCharError(str, 0)\n }\n\n if (firstValue > 7) {\n throw timestampOverflowError()\n }\n\n let time = firstValue\n for (let i = 1; i < TIME_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw invalidCharError(str, i)\n }\n\n time = time * 32 + value\n }\n return time\n}\n\n/**\n * Encode a 48-bit timestamp to a 10-character Crockford Base32 string.\n * Uses unrolled division for performance.\n */\nexport function encodeTime(time: number): string {\n // Unrolled encoding - each step divides by 32 and extracts 5 bits\n // Powers of 32: 32^9=0x200000000000, 32^8=0x10000000000, etc.\n return (\n ENCODING[Math.floor(time / 0x200000000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x10000000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x800000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x40000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x2000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x100000) & 0x1f] +\n ENCODING[Math.floor(time / 0x8000) & 0x1f] +\n ENCODING[Math.floor(time / 0x400) & 0x1f] +\n ENCODING[Math.floor(time / 0x20) & 0x1f] +\n ENCODING[time & 0x1f]\n )\n}\n\n/**\n * Encode 10 bytes (80 bits) of random data to a 16-character Crockford Base32 string.\n */\nexport function encodeRandom(bytes: Uint8Array): string {\n // Each character encodes 5 bits. 80 bits = 16 characters.\n // Single concatenation expression for optimal performance.\n return (\n ENCODING[(bytes[0] >> 3) & 0x1f] +\n ENCODING[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f] +\n ENCODING[(bytes[1] >> 1) & 0x1f] +\n ENCODING[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f] +\n ENCODING[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f] +\n ENCODING[(bytes[3] >> 2) & 0x1f] +\n ENCODING[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f] +\n ENCODING[bytes[4] & 0x1f] +\n ENCODING[(bytes[5] >> 3) & 0x1f] +\n ENCODING[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f] +\n ENCODING[(bytes[6] >> 1) & 0x1f] +\n ENCODING[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f] +\n ENCODING[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f] +\n ENCODING[(bytes[8] >> 2) & 0x1f] +\n ENCODING[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f] +\n ENCODING[bytes[9] & 0x1f]\n )\n}\n\n/**\n * Decode a 10-character ULID timestamp string to Unix epoch milliseconds.\n */\nexport function decodeTime(str: string): number {\n if (str.length !== TIME_LEN) {\n throw new ParseError('ULID_INVALID_LENGTH', `ULID timestamp must be ${TIME_LEN} characters`)\n }\n return decodeTimeChars(str)\n}\n\n/**\n * Decode the timestamp from a full 26-character ULID string.\n */\nexport function decodeUlidTime(str: string): number {\n if (str.length !== ULID_LEN) {\n throw new ParseError('ULID_INVALID_LENGTH', `ULID string must be ${ULID_LEN} characters`)\n }\n return decodeTimeChars(str)\n}\n\n/**\n * Decode a 26-character ULID string to 16 bytes.\n * Inlines all lookups to avoid intermediate array allocation.\n */\nexport function decodeToBytes(str: string): Uint8Array {\n if (str.length !== ULID_LEN) {\n throw new ParseError('ULID_INVALID_LENGTH', `ULID string must be ${ULID_LEN} characters`)\n }\n\n const bytes = new Uint8Array(16)\n\n // Inline all 26 character lookups\n const v0 = DECODING[str.charCodeAt(0)]\n const v1 = DECODING[str.charCodeAt(1)]\n const v2 = DECODING[str.charCodeAt(2)]\n const v3 = DECODING[str.charCodeAt(3)]\n const v4 = DECODING[str.charCodeAt(4)]\n const v5 = DECODING[str.charCodeAt(5)]\n const v6 = DECODING[str.charCodeAt(6)]\n const v7 = DECODING[str.charCodeAt(7)]\n const v8 = DECODING[str.charCodeAt(8)]\n const v9 = DECODING[str.charCodeAt(9)]\n const v10 = DECODING[str.charCodeAt(10)]\n const v11 = DECODING[str.charCodeAt(11)]\n const v12 = DECODING[str.charCodeAt(12)]\n const v13 = DECODING[str.charCodeAt(13)]\n const v14 = DECODING[str.charCodeAt(14)]\n const v15 = DECODING[str.charCodeAt(15)]\n const v16 = DECODING[str.charCodeAt(16)]\n const v17 = DECODING[str.charCodeAt(17)]\n const v18 = DECODING[str.charCodeAt(18)]\n const v19 = DECODING[str.charCodeAt(19)]\n const v20 = DECODING[str.charCodeAt(20)]\n const v21 = DECODING[str.charCodeAt(21)]\n const v22 = DECODING[str.charCodeAt(22)]\n const v23 = DECODING[str.charCodeAt(23)]\n const v24 = DECODING[str.charCodeAt(24)]\n const v25 = DECODING[str.charCodeAt(25)]\n\n // Validate all characters at once (255 = invalid marker, so any invalid\n // character sets the 0x80 bit in the OR of all values)\n if (\n ((v0 |\n v1 |\n v2 |\n v3 |\n v4 |\n v5 |\n v6 |\n v7 |\n v8 |\n v9 |\n v10 |\n v11 |\n v12 |\n v13 |\n v14 |\n v15 |\n v16 |\n v17 |\n v18 |\n v19 |\n v20 |\n v21 |\n v22 |\n v23 |\n v24 |\n v25) &\n 0x80) !==\n 0\n ) {\n // Find the invalid character for error message\n for (let i = 0; i < ULID_LEN; i += 1) {\n if (DECODING[str.charCodeAt(i)] === 255) {\n throw invalidCharError(str, i)\n }\n }\n }\n\n if (v0 > 7) {\n throw timestampOverflowError()\n }\n\n // Timestamp: first 10 characters -> bytes 0-5\n bytes[0] = (v0 << 5) | v1\n bytes[1] = (v2 << 3) | (v3 >> 2)\n bytes[2] = (v3 << 6) | (v4 << 1) | (v5 >> 4)\n bytes[3] = (v5 << 4) | (v6 >> 1)\n bytes[4] = (v6 << 7) | (v7 << 2) | (v8 >> 3)\n bytes[5] = (v8 << 5) | v9\n\n // Random: last 16 characters -> bytes 6-15\n bytes[6] = (v10 << 3) | (v11 >> 2)\n bytes[7] = (v11 << 6) | (v12 << 1) | (v13 >> 4)\n bytes[8] = (v13 << 4) | (v14 >> 1)\n bytes[9] = (v14 << 7) | (v15 << 2) | (v16 >> 3)\n bytes[10] = (v16 << 5) | v17\n bytes[11] = (v18 << 3) | (v19 >> 2)\n bytes[12] = (v19 << 6) | (v20 << 1) | (v21 >> 4)\n bytes[13] = (v21 << 4) | (v22 >> 1)\n bytes[14] = (v22 << 7) | (v23 << 2) | (v24 >> 3)\n bytes[15] = (v24 << 5) | v25\n\n return bytes\n}\n\n/**\n * Encode 16 bytes to a 26-character ULID string.\n */\nexport function bytesToUlid(bytes: Uint8Array): string {\n if (bytes.length !== 16) {\n throw new BufferError('ULID_BYTES_INVALID_LENGTH', `ULID bytes must be exactly 16 bytes, got ${bytes.length}`)\n }\n\n // Timestamp: bytes 0-5 -> 10 characters\n let time = 0\n for (let i = 0; i < 6; i += 1) {\n time = time * 256 + bytes[i]\n }\n const timeStr = encodeTime(time)\n\n // Random: bytes 6-15 -> 16 characters\n const randomStr = encodeRandom(bytes.subarray(6, 16))\n\n return timeStr + randomStr\n}\n","import { incrementBytesInPlace, writeTimestamp48 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { bytesToUlid, decodeToBytes, decodeUlidTime, encodeRandom, encodeTime } from './crockford'\n\nexport type UlidOptions = {\n /**\n * 16 bytes of random data to use for ULID generation.\n * Only the first 10 bytes are used.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since Unix epoch.\n * Defaults to Date.now().\n */\n msecs?: number\n}\n\nexport type Ulid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: UlidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil ULID (all zeros) */\n NIL: string\n /** The max ULID (maximum valid value) */\n MAX: string\n}\n\n// Validation regex: first char [0-7] to prevent overflow, rest from Crockford alphabet\nconst ULID_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i\nconst ULID_BYTES = 16\nconst RANDOM_BYTES = 10\nconst MAX_MSECS = 0xffffffffffff\n\ntype UlidState = {\n msecs: number\n lastRandom: Uint8Array\n}\n\n/**\n * Module-level state for maintaining monotonic ordering within the same millisecond.\n *\n * IMPORTANT: This state persists across all ulid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * - For isolated state, pass explicit `msecs` and `random` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: UlidState = {\n msecs: -Infinity,\n lastRandom: new Uint8Array(RANDOM_BYTES),\n}\n\nfunction writeUlidBytesUnchecked(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (48-bit big-endian milliseconds since Unix epoch) -> bytes 0-5\n writeTimestamp48(buf, offset, time)\n\n // Random (80 bits) -> bytes 6-15\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + 6 + i] = random[i]\n }\n}\n\nfunction writeUlidBytes(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void {\n if (!isWritableRange(buf, offset, ULID_BYTES)) {\n throw new BufferError(\n 'ULID_BUFFER_OUT_OF_BOUNDS',\n `ULID byte range ${offset}:${offset + ULID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeUlidBytesUnchecked(time, random, buf, offset)\n}\n\n/*\n * Overload: no buffer => return a ULID string.\n */\nfunction ulidFn(options?: UlidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ULID bytes.\n */\nfunction ulidFn<TBuf extends Uint8Array = Uint8Array>(\n options: UlidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ulidFn<TBuf extends Uint8Array = Uint8Array>(options?: UlidOptions, buf?: TBuf, offset = 0): string | TBuf {\n let time: number\n let random: Uint8Array\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ULIDs generated within a single request will have the same timestamp.\n * - the monotonic ordering will rely entirely on incrementing the random portion.\n */\n if (options) {\n // Explicit options provided - use them directly without monotonic state\n const optMsecs = options.msecs\n if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) {\n throw new InvalidInputError(\n 'ULID_TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between 0 and ${MAX_MSECS}`,\n )\n }\n time = optMsecs ?? Date.now()\n const optRandom = options.random\n if (optRandom) {\n if (optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'ULID_RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ULID`,\n )\n }\n random = optRandom\n } else {\n random = rng()\n }\n } else {\n time = Date.now()\n\n if (time > state.msecs) {\n // New millisecond: generate fresh random\n random = rng()\n state.msecs = time\n state.lastRandom.set(random.subarray(0, RANDOM_BYTES))\n } else {\n // Same millisecond or clock rollback: preserve last timestamp and increment random portion.\n time = state.msecs\n if (!incrementBytesInPlace(state.lastRandom)) {\n state.lastRandom.fill(0xff)\n throw new InvalidInputError(\n 'ULID_RANDOM_OVERFLOW',\n 'ULID random component overflowed while preserving monotonic order',\n )\n }\n random = state.lastRandom\n }\n }\n\n if (buf) {\n writeUlidBytes(time, random, buf, offset)\n return buf\n }\n\n // String mode: encode directly without buffer allocation\n return encodeTime(time) + encodeRandom(random)\n}\n\n/**\n * Generate a ULID string or write the bytes into a buffer.\n *\n * ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit\n * identifier with millisecond timestamp precision and 80 bits of randomness.\n * ULIDs are URL-safe, use Crockford's Base32 encoding, and sort lexicographically\n * by creation time.\n *\n * @example\n * ```ts\n * import { ulid } from 'uniku/ulid'\n *\n * const id = ulid()\n * // => \"01HW9T2W9W9YJ3JZ1H4P4M2T8Q\"\n *\n * // Extract timestamp\n * const ts = ulid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * ulid.isValid(id) // true\n *\n * // Convert to/from bytes (16 bytes)\n * const bytes = ulid.toBytes(id)\n * const restored = ulid.fromBytes(bytes)\n * ```\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && ULID_REGEX.test(id)\n}\n\nexport const ulid: Ulid = Object.assign(ulidFn, {\n toBytes: (id: string) => decodeToBytes(id),\n fromBytes: (bytes: Uint8Array) => bytesToUlid(bytes),\n timestamp: (id: string) => decodeUlidTime(id),\n isValid,\n NIL: '00000000000000000000000000',\n MAX: '7ZZZZZZZZZZZZZZZZZZZZZZZZZ',\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} | ||
| {"version":3,"file":"ulid.mjs","names":[],"sources":["../../src/ulid/crockford.ts","../../src/ulid/ulid.ts"],"sourcesContent":["/**\n * Crockford's Base32 encoding/decoding for ULID.\n * Alphabet excludes I, L, O, U to avoid confusion with similar-looking characters.\n */\n\nimport { BufferError, ParseError } from '../errors'\n\nconst ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n// Pre-computed decoding table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\nfor (let i = 0; i < ENCODING.length; i += 1) {\n const upper = ENCODING.charCodeAt(i)\n const lower = ENCODING[i].toLowerCase().charCodeAt(0)\n DECODING[upper] = i\n DECODING[lower] = i\n}\n\nconst TIME_LEN = 10\nconst ULID_LEN = 26\n\nfunction invalidCharError(str: string, index: number): ParseError {\n return new ParseError('ULID_INVALID_CHAR', `Invalid ULID character: ${str[index]}`)\n}\n\nfunction timestampOverflowError(): ParseError {\n return new ParseError('ULID_TIMESTAMP_OVERFLOW', 'ULID timestamp exceeds 48 bits')\n}\n\nfunction decodeTimeChars(str: string): number {\n const firstValue = DECODING[str.charCodeAt(0)]\n if (firstValue === 255) {\n throw invalidCharError(str, 0)\n }\n\n if (firstValue > 7) {\n throw timestampOverflowError()\n }\n\n let time = firstValue\n for (let i = 1; i < TIME_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw invalidCharError(str, i)\n }\n\n time = time * 32 + value\n }\n return time\n}\n\n/**\n * Encode a 48-bit timestamp to a 10-character Crockford Base32 string.\n * Uses unrolled division for performance.\n */\nexport function encodeTime(time: number): string {\n // Unrolled encoding - each step divides by 32 and extracts 5 bits\n // Powers of 32: 32^9=0x200000000000, 32^8=0x10000000000, etc.\n return (\n ENCODING[Math.floor(time / 0x200000000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x10000000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x800000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x40000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x2000000) & 0x1f] +\n ENCODING[Math.floor(time / 0x100000) & 0x1f] +\n ENCODING[Math.floor(time / 0x8000) & 0x1f] +\n ENCODING[Math.floor(time / 0x400) & 0x1f] +\n ENCODING[Math.floor(time / 0x20) & 0x1f] +\n ENCODING[time & 0x1f]\n )\n}\n\n/**\n * Encode 10 bytes (80 bits) of random data to a 16-character Crockford Base32 string.\n */\nexport function encodeRandom(bytes: Uint8Array): string {\n // Each character encodes 5 bits. 80 bits = 16 characters.\n // Single concatenation expression for optimal performance.\n return (\n ENCODING[(bytes[0] >> 3) & 0x1f] +\n ENCODING[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f] +\n ENCODING[(bytes[1] >> 1) & 0x1f] +\n ENCODING[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f] +\n ENCODING[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f] +\n ENCODING[(bytes[3] >> 2) & 0x1f] +\n ENCODING[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f] +\n ENCODING[bytes[4] & 0x1f] +\n ENCODING[(bytes[5] >> 3) & 0x1f] +\n ENCODING[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f] +\n ENCODING[(bytes[6] >> 1) & 0x1f] +\n ENCODING[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f] +\n ENCODING[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f] +\n ENCODING[(bytes[8] >> 2) & 0x1f] +\n ENCODING[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f] +\n ENCODING[bytes[9] & 0x1f]\n )\n}\n\n/**\n * Decode a 10-character ULID timestamp string to Unix epoch milliseconds.\n */\nexport function decodeTime(str: string): number {\n if (str.length !== TIME_LEN) {\n throw new ParseError('ULID_INVALID_LENGTH', `ULID timestamp must be ${TIME_LEN} characters`)\n }\n return decodeTimeChars(str)\n}\n\n/**\n * Decode the timestamp from a full 26-character ULID string.\n */\nexport function decodeUlidTime(str: string): number {\n if (str.length !== ULID_LEN) {\n throw new ParseError('ULID_INVALID_LENGTH', `ULID string must be ${ULID_LEN} characters`)\n }\n return decodeTimeChars(str)\n}\n\n/**\n * Decode a 26-character ULID string to 16 bytes.\n * Inlines all lookups to avoid intermediate array allocation.\n */\nexport function decodeToBytes(str: string): Uint8Array {\n if (str.length !== ULID_LEN) {\n throw new ParseError('ULID_INVALID_LENGTH', `ULID string must be ${ULID_LEN} characters`)\n }\n\n const bytes = new Uint8Array(16)\n\n // Inline all 26 character lookups\n const v0 = DECODING[str.charCodeAt(0)]\n const v1 = DECODING[str.charCodeAt(1)]\n const v2 = DECODING[str.charCodeAt(2)]\n const v3 = DECODING[str.charCodeAt(3)]\n const v4 = DECODING[str.charCodeAt(4)]\n const v5 = DECODING[str.charCodeAt(5)]\n const v6 = DECODING[str.charCodeAt(6)]\n const v7 = DECODING[str.charCodeAt(7)]\n const v8 = DECODING[str.charCodeAt(8)]\n const v9 = DECODING[str.charCodeAt(9)]\n const v10 = DECODING[str.charCodeAt(10)]\n const v11 = DECODING[str.charCodeAt(11)]\n const v12 = DECODING[str.charCodeAt(12)]\n const v13 = DECODING[str.charCodeAt(13)]\n const v14 = DECODING[str.charCodeAt(14)]\n const v15 = DECODING[str.charCodeAt(15)]\n const v16 = DECODING[str.charCodeAt(16)]\n const v17 = DECODING[str.charCodeAt(17)]\n const v18 = DECODING[str.charCodeAt(18)]\n const v19 = DECODING[str.charCodeAt(19)]\n const v20 = DECODING[str.charCodeAt(20)]\n const v21 = DECODING[str.charCodeAt(21)]\n const v22 = DECODING[str.charCodeAt(22)]\n const v23 = DECODING[str.charCodeAt(23)]\n const v24 = DECODING[str.charCodeAt(24)]\n const v25 = DECODING[str.charCodeAt(25)]\n\n // Validate all characters at once (255 = invalid marker, so any invalid\n // character sets the 0x80 bit in the OR of all values)\n if (\n ((v0 |\n v1 |\n v2 |\n v3 |\n v4 |\n v5 |\n v6 |\n v7 |\n v8 |\n v9 |\n v10 |\n v11 |\n v12 |\n v13 |\n v14 |\n v15 |\n v16 |\n v17 |\n v18 |\n v19 |\n v20 |\n v21 |\n v22 |\n v23 |\n v24 |\n v25) &\n 0x80) !==\n 0\n ) {\n // Find the invalid character for error message\n for (let i = 0; i < ULID_LEN; i += 1) {\n if (DECODING[str.charCodeAt(i)] === 255) {\n throw invalidCharError(str, i)\n }\n }\n }\n\n if (v0 > 7) {\n throw timestampOverflowError()\n }\n\n // Timestamp: first 10 characters -> bytes 0-5\n bytes[0] = (v0 << 5) | v1\n bytes[1] = (v2 << 3) | (v3 >> 2)\n bytes[2] = (v3 << 6) | (v4 << 1) | (v5 >> 4)\n bytes[3] = (v5 << 4) | (v6 >> 1)\n bytes[4] = (v6 << 7) | (v7 << 2) | (v8 >> 3)\n bytes[5] = (v8 << 5) | v9\n\n // Random: last 16 characters -> bytes 6-15\n bytes[6] = (v10 << 3) | (v11 >> 2)\n bytes[7] = (v11 << 6) | (v12 << 1) | (v13 >> 4)\n bytes[8] = (v13 << 4) | (v14 >> 1)\n bytes[9] = (v14 << 7) | (v15 << 2) | (v16 >> 3)\n bytes[10] = (v16 << 5) | v17\n bytes[11] = (v18 << 3) | (v19 >> 2)\n bytes[12] = (v19 << 6) | (v20 << 1) | (v21 >> 4)\n bytes[13] = (v21 << 4) | (v22 >> 1)\n bytes[14] = (v22 << 7) | (v23 << 2) | (v24 >> 3)\n bytes[15] = (v24 << 5) | v25\n\n return bytes\n}\n\n/**\n * Encode 16 bytes to a 26-character ULID string.\n */\nexport function bytesToUlid(bytes: Uint8Array): string {\n if (bytes.length !== 16) {\n throw new BufferError('ULID_BYTES_INVALID_LENGTH', `ULID bytes must be exactly 16 bytes, got ${bytes.length}`)\n }\n\n // Timestamp: bytes 0-5 -> 10 characters\n let time = 0\n for (let i = 0; i < 6; i += 1) {\n time = time * 256 + bytes[i]\n }\n const timeStr = encodeTime(time)\n\n // Random: bytes 6-15 -> 16 characters\n const randomStr = encodeRandom(bytes.subarray(6, 16))\n\n return timeStr + randomStr\n}\n","import { incrementBytesInPlace, writeTimestamp48 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { bytesToUlid, decodeToBytes, decodeUlidTime, encodeRandom, encodeTime } from './crockford'\n\nexport type UlidOptions = {\n /**\n * 16 bytes of random data to use for ULID generation.\n * Only the first 10 bytes are used.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since Unix epoch.\n * Defaults to Date.now().\n */\n msecs?: number\n}\n\nexport type Ulid = {\n /** Generate a time-ordered ULID string. */\n (): string\n /** Generate a ULID with explicit options or write its 16 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a ULID string with optional timestamp or random bytes. */\n (options?: UlidOptions, buf?: undefined, offset?: number): string\n /** Convert a ULID string to its canonical 16-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 16 canonical ULID bytes to a ULID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid ULID string. */\n isValid(id: unknown): id is string\n /** The nil ULID (all zeros) */\n NIL: string\n /** The max ULID (maximum valid value) */\n MAX: string\n}\n\n// Validation regex: first char [0-7] to prevent overflow, rest from Crockford alphabet\nconst ULID_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i\nconst ULID_BYTES = 16\nconst RANDOM_BYTES = 10\nconst MAX_MSECS = 0xffffffffffff\n\ntype UlidState = {\n msecs: number\n lastRandom: Uint8Array\n}\n\n/**\n * Module-level state for maintaining monotonic ordering within the same millisecond.\n *\n * IMPORTANT: This state persists across all ulid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * - For isolated state, pass explicit `msecs` and `random` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: UlidState = {\n msecs: -Infinity,\n lastRandom: new Uint8Array(RANDOM_BYTES),\n}\n\nfunction writeUlidBytesUnchecked(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (48-bit big-endian milliseconds since Unix epoch) -> bytes 0-5\n writeTimestamp48(buf, offset, time)\n\n // Random (80 bits) -> bytes 6-15\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + 6 + i] = random[i]\n }\n}\n\nfunction writeUlidBytes(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void {\n if (!isWritableRange(buf, offset, ULID_BYTES)) {\n throw new BufferError(\n 'ULID_BUFFER_OUT_OF_BOUNDS',\n `ULID byte range ${offset}:${offset + ULID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeUlidBytesUnchecked(time, random, buf, offset)\n}\n\n/*\n * Overload: no buffer => return a ULID string.\n */\nfunction ulidFn(options?: UlidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ULID bytes.\n */\nfunction ulidFn<TBuf extends Uint8Array = Uint8Array>(\n options: UlidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ulidFn<TBuf extends Uint8Array = Uint8Array>(options?: UlidOptions, buf?: TBuf, offset = 0): string | TBuf {\n let time: number\n let random: Uint8Array\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ULIDs generated within a single request will have the same timestamp.\n * - the monotonic ordering will rely entirely on incrementing the random portion.\n */\n if (options) {\n // Explicit options provided - use them directly without monotonic state\n const optMsecs = options.msecs\n if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) {\n throw new InvalidInputError(\n 'ULID_TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between 0 and ${MAX_MSECS}`,\n )\n }\n time = optMsecs ?? Date.now()\n const optRandom = options.random\n if (optRandom) {\n if (optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'ULID_RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ULID`,\n )\n }\n random = optRandom\n } else {\n random = rng()\n }\n } else {\n time = Date.now()\n\n if (time > state.msecs) {\n // New millisecond: generate fresh random\n random = rng()\n state.msecs = time\n state.lastRandom.set(random.subarray(0, RANDOM_BYTES))\n } else {\n // Same millisecond or clock rollback: preserve last timestamp and increment random portion.\n time = state.msecs\n if (!incrementBytesInPlace(state.lastRandom)) {\n state.lastRandom.fill(0xff)\n throw new InvalidInputError(\n 'ULID_RANDOM_OVERFLOW',\n 'ULID random component overflowed while preserving monotonic order',\n )\n }\n random = state.lastRandom\n }\n }\n\n if (buf) {\n writeUlidBytes(time, random, buf, offset)\n return buf\n }\n\n // String mode: encode directly without buffer allocation\n return encodeTime(time) + encodeRandom(random)\n}\n\n/**\n * Generate a ULID string or write the bytes into a buffer.\n *\n * ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit\n * identifier with millisecond timestamp precision and 80 bits of randomness.\n * ULIDs are URL-safe, use Crockford's Base32 encoding, and sort lexicographically\n * by creation time.\n *\n * @example\n * ```ts\n * import { ulid } from 'uniku/ulid'\n *\n * const id = ulid()\n * // => \"01HW9T2W9W9YJ3JZ1H4P4M2T8Q\"\n *\n * // Extract timestamp\n * const ts = ulid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * ulid.isValid(id) // true\n *\n * // Convert to/from bytes (16 bytes)\n * const bytes = ulid.toBytes(id)\n * const restored = ulid.fromBytes(bytes)\n * ```\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && ULID_REGEX.test(id)\n}\n\n/**\n * Generate a ULID string or write its 16 canonical bytes into a buffer.\n * ULIDs are URL-safe, carry a millisecond timestamp, and sort by creation time.\n */\nexport const ulid: Ulid = Object.assign(ulidFn, {\n toBytes: (id: string) => decodeToBytes(id),\n fromBytes: (bytes: Uint8Array) => bytesToUlid(bytes),\n timestamp: (id: string) => decodeUlidTime(id),\n isValid,\n NIL: '00000000000000000000000000',\n MAX: '7ZZZZZZZZZZZZZZZZZZZZZZZZZ',\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
@@ -11,7 +11,7 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| type UuidV4 = { | ||
| (): string; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: UuidV4Options, buf?: undefined, offset?: number): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): string; | ||
| /** Generate a random UUID v4 string. */(): string; /** Generate a UUID v4 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf; /** Generate a UUID v4 string with optional deterministic random bytes. */ | ||
| (options?: UuidV4Options, buf?: undefined, offset?: number): string; /** Convert a UUID v4 string to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array; /** Convert 16 canonical UUID bytes to a UUID v4 string. */ | ||
| fromBytes(bytes: Uint8Array): string; /** Return whether a value is a syntactically valid UUID v4 string. */ | ||
| isValid(id: unknown): id is string; /** The nil UUID (all zeros) */ | ||
@@ -18,0 +18,0 @@ NIL: string; /** The max UUID (all ones) */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v4.d.mts","names":[],"sources":["../../src/uuid/v4.ts"],"mappings":";;;KASY,aAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;AAAA;AAAA,KAGC,MAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,aAAA,cAA2B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACvG,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA;EAC3C,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;cAiFW,MAAA,EAAQ,MAAA"} | ||
| {"version":3,"file":"v4.d.mts","names":[],"sources":["../../src/uuid/v4.ts"],"mappings":";;;KASY,aAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;AAAA;AAAA,KAGC,MAAA;EAHD,oDAGC;EAAA,cAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,aAAA,cAA2B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAEvG,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA;EAE3C,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,KAAA,EAAO,UAAA;EAEjB,OAAA,CAAQ,EAAA,YAAc,EAAA,YAFL;EAIjB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;cAiFW,MAAA,EAAQ,MAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v4.mjs","names":[],"sources":["../../src/uuid/v4.ts"],"sourcesContent":["import { rng } from '../common/random'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid'\n\nconst randomUUID = /*@__PURE__*/ globalThis.crypto.randomUUID.bind(globalThis.crypto)\nconst UUID_BYTES = 16\nconst reusableBuf = new Uint8Array(UUID_BYTES)\n\nexport type UuidV4Options = {\n /**\n * 16 bytes of random data to use for UUID generation.\n */\n random?: Uint8Array\n}\n\nexport type UuidV4 = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf\n (options?: UuidV4Options, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n isValid(id: unknown): id is string\n /** The nil UUID (all zeros) */\n NIL: string\n /** The max UUID (all ones) */\n MAX: string\n}\n\nconst UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nfunction writeV4BytesUnchecked(rnds: Uint8Array, buf: Uint8Array, offset: number): void {\n // Copy 16 UUID bytes into the provided buffer slice.\n for (let i = 0; i < UUID_BYTES; i += 1) {\n buf[offset + i] = rnds[i]\n }\n\n // Set RFC 4122 version (4) and variant (10xx) bits on owned output only.\n buf[offset + 6] = (buf[offset + 6] & 0x0f) | 0x40\n buf[offset + 8] = (buf[offset + 8] & 0x3f) | 0x80\n}\n\n/*\n * Overload: no buffer => return a UUID string.\n */\nfunction v4(options?: UuidV4Options, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with UUID bytes.\n */\nfunction v4<TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf\nfunction v4<TBuf extends Uint8Array = Uint8Array>(options?: UuidV4Options, buf?: TBuf, offset?: number): string | TBuf {\n if (!buf && !options) {\n return randomUUID()\n }\n\n return _v4(options, buf, offset)\n}\n\nfunction _v4<TBuf extends Uint8Array = Uint8Array>(\n options?: UuidV4Options,\n buf?: TBuf,\n offset?: number,\n): string | TBuf {\n const random = options?.random\n if (random && random.length < UUID_BYTES) {\n throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${UUID_BYTES}`)\n }\n\n const outputOffset = buf ? (offset ?? 0) : 0\n if (buf && !isWritableRange(buf, outputOffset, UUID_BYTES)) {\n throw new BufferError(\n 'UUID_BUFFER_OUT_OF_BOUNDS',\n `UUID byte range ${outputOffset}:${outputOffset + UUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n\n const output = buf ?? reusableBuf\n writeV4BytesUnchecked(random ?? rng(), output, outputOffset)\n return buf ?? formatUuidUnchecked(output)\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && UUID_V4_REGEX.test(id)\n}\n\n/**\n * Generate a UUID v4 string or write the bytes into a buffer.\n *\n * UUID v4 is a purely random UUID with 122 bits of entropy. It's the most\n * widely compatible UUID format, supported by virtually all databases and systems.\n * Use when you need maximum compatibility and don't require time-ordering.\n *\n * @example\n * ```ts\n * import { uuidv4 } from 'uniku/uuid/v4'\n *\n * const id = uuidv4()\n * // => \"550e8400-e29b-41d4-a716-446655440000\"\n *\n * // Validate\n * uuidv4.isValid(id) // true\n *\n * // Convert to/from bytes (16 bytes)\n * const bytes = uuidv4.toBytes(id)\n * const restored = uuidv4.fromBytes(bytes)\n * ```\n */\nexport const uuidv4: UuidV4 = Object.assign(v4, {\n toBytes: parseUuid,\n fromBytes: formatUuid,\n isValid,\n NIL: '00000000-0000-0000-0000-000000000000',\n MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"uPAKA,MAAM,EAA2B,WAAW,OAAO,WAAW,KAAK,WAAW,MAAM,EAE9E,EAAc,IAAI,WAAW,EAAU,EAsBvC,EAAgB,yEAEtB,SAAS,EAAsB,EAAkB,EAAiB,EAAsB,CAEtF,IAAK,IAAI,EAAI,EAAG,EAAI,GAAY,GAAK,EACnC,EAAI,EAAS,GAAK,EAAK,GAIzB,EAAI,EAAS,GAAM,EAAI,EAAS,GAAK,GAAQ,GAC7C,EAAI,EAAS,GAAM,EAAI,EAAS,GAAK,GAAQ,GAC/C,CAUA,SAAS,EAAyC,EAAyB,EAAY,EAAgC,CAKrH,MAJI,CAAC,GAAO,CAAC,EACJ,EAAW,EAGb,EAAI,EAAS,EAAK,CAAM,CACjC,CAEA,SAAS,EACP,EACA,EACA,EACe,CACf,IAAM,EAAS,GAAS,OACxB,GAAI,GAAU,EAAO,OAAS,GAC5B,MAAM,IAAI,EAAkB,8BAA+B,mCAA8C,EAG3G,IAAM,EAAe,EAAO,GAAU,EAAK,EAC3C,GAAI,GAAO,CAAC,EAAgB,EAAK,EAAc,EAAU,EACvD,MAAM,IAAI,EACR,4BACA,mBAAmB,EAAa,GAAG,EAAe,GAAa,EAAE,yBACnE,EAGF,IAAM,EAAS,GAAO,EAEtB,OADA,EAAsB,GAAU,EAAI,EAAG,EAAQ,CAAY,EACpD,GAAO,EAAoB,CAAM,CAC1C,CAEA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAc,KAAK,CAAE,CACxD,CAwBA,MAAa,EAAiB,OAAO,OAAO,EAAI,CAC9C,QAAS,EACT,UAAW,EACX,UACA,IAAK,uCACL,IAAK,sCACP,CAAC"} | ||
| {"version":3,"file":"v4.mjs","names":[],"sources":["../../src/uuid/v4.ts"],"sourcesContent":["import { rng } from '../common/random'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid'\n\nconst randomUUID = /*@__PURE__*/ globalThis.crypto.randomUUID.bind(globalThis.crypto)\nconst UUID_BYTES = 16\nconst reusableBuf = new Uint8Array(UUID_BYTES)\n\nexport type UuidV4Options = {\n /**\n * 16 bytes of random data to use for UUID generation.\n */\n random?: Uint8Array\n}\n\nexport type UuidV4 = {\n /** Generate a random UUID v4 string. */\n (): string\n /** Generate a UUID v4 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a UUID v4 string with optional deterministic random bytes. */\n (options?: UuidV4Options, buf?: undefined, offset?: number): string\n /** Convert a UUID v4 string to its canonical 16-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 16 canonical UUID bytes to a UUID v4 string. */\n fromBytes(bytes: Uint8Array): string\n /** Return whether a value is a syntactically valid UUID v4 string. */\n isValid(id: unknown): id is string\n /** The nil UUID (all zeros) */\n NIL: string\n /** The max UUID (all ones) */\n MAX: string\n}\n\nconst UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nfunction writeV4BytesUnchecked(rnds: Uint8Array, buf: Uint8Array, offset: number): void {\n // Copy 16 UUID bytes into the provided buffer slice.\n for (let i = 0; i < UUID_BYTES; i += 1) {\n buf[offset + i] = rnds[i]\n }\n\n // Set RFC 4122 version (4) and variant (10xx) bits on owned output only.\n buf[offset + 6] = (buf[offset + 6] & 0x0f) | 0x40\n buf[offset + 8] = (buf[offset + 8] & 0x3f) | 0x80\n}\n\n/*\n * Overload: no buffer => return a UUID string.\n */\nfunction v4(options?: UuidV4Options, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with UUID bytes.\n */\nfunction v4<TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf\nfunction v4<TBuf extends Uint8Array = Uint8Array>(options?: UuidV4Options, buf?: TBuf, offset?: number): string | TBuf {\n if (!buf && !options) {\n return randomUUID()\n }\n\n return _v4(options, buf, offset)\n}\n\nfunction _v4<TBuf extends Uint8Array = Uint8Array>(\n options?: UuidV4Options,\n buf?: TBuf,\n offset?: number,\n): string | TBuf {\n const random = options?.random\n if (random && random.length < UUID_BYTES) {\n throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${UUID_BYTES}`)\n }\n\n const outputOffset = buf ? (offset ?? 0) : 0\n if (buf && !isWritableRange(buf, outputOffset, UUID_BYTES)) {\n throw new BufferError(\n 'UUID_BUFFER_OUT_OF_BOUNDS',\n `UUID byte range ${outputOffset}:${outputOffset + UUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n\n const output = buf ?? reusableBuf\n writeV4BytesUnchecked(random ?? rng(), output, outputOffset)\n return buf ?? formatUuidUnchecked(output)\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && UUID_V4_REGEX.test(id)\n}\n\n/**\n * Generate a UUID v4 string or write the bytes into a buffer.\n *\n * UUID v4 is a purely random UUID with 122 bits of entropy. It's the most\n * widely compatible UUID format, supported by virtually all databases and systems.\n * Use when you need maximum compatibility and don't require time-ordering.\n *\n * @example\n * ```ts\n * import { uuidv4 } from 'uniku/uuid/v4'\n *\n * const id = uuidv4()\n * // => \"550e8400-e29b-41d4-a716-446655440000\"\n *\n * // Validate\n * uuidv4.isValid(id) // true\n *\n * // Convert to/from bytes (16 bytes)\n * const bytes = uuidv4.toBytes(id)\n * const restored = uuidv4.fromBytes(bytes)\n * ```\n */\nexport const uuidv4: UuidV4 = Object.assign(v4, {\n toBytes: parseUuid,\n fromBytes: formatUuid,\n isValid,\n NIL: '00000000-0000-0000-0000-000000000000',\n MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"uPAKA,MAAM,EAA2B,WAAW,OAAO,WAAW,KAAK,WAAW,MAAM,EAE9E,EAAc,IAAI,WAAW,EAAU,EA4BvC,EAAgB,yEAEtB,SAAS,EAAsB,EAAkB,EAAiB,EAAsB,CAEtF,IAAK,IAAI,EAAI,EAAG,EAAI,GAAY,GAAK,EACnC,EAAI,EAAS,GAAK,EAAK,GAIzB,EAAI,EAAS,GAAM,EAAI,EAAS,GAAK,GAAQ,GAC7C,EAAI,EAAS,GAAM,EAAI,EAAS,GAAK,GAAQ,GAC/C,CAUA,SAAS,EAAyC,EAAyB,EAAY,EAAgC,CAKrH,MAJI,CAAC,GAAO,CAAC,EACJ,EAAW,EAGb,EAAI,EAAS,EAAK,CAAM,CACjC,CAEA,SAAS,EACP,EACA,EACA,EACe,CACf,IAAM,EAAS,GAAS,OACxB,GAAI,GAAU,EAAO,OAAS,GAC5B,MAAM,IAAI,EAAkB,8BAA+B,mCAA8C,EAG3G,IAAM,EAAe,EAAO,GAAU,EAAK,EAC3C,GAAI,GAAO,CAAC,EAAgB,EAAK,EAAc,EAAU,EACvD,MAAM,IAAI,EACR,4BACA,mBAAmB,EAAa,GAAG,EAAe,GAAa,EAAE,yBACnE,EAGF,IAAM,EAAS,GAAO,EAEtB,OADA,EAAsB,GAAU,EAAI,EAAG,EAAQ,CAAY,EACpD,GAAO,EAAoB,CAAM,CAC1C,CAEA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAc,KAAK,CAAE,CACxD,CAwBA,MAAa,EAAiB,OAAO,OAAO,EAAI,CAC9C,QAAS,EACT,UAAW,EACX,UACA,IAAK,uCACL,IAAK,sCACP,CAAC"} |
+10
-6
@@ -10,2 +10,6 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| random?: Uint8Array; | ||
| /** | ||
| * Timestamp in milliseconds since Unix epoch. | ||
| * Defaults to Date.now(). | ||
| */ | ||
| msecs?: number; | ||
@@ -18,8 +22,8 @@ /** | ||
| type UuidV7 = { | ||
| (): string; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: UuidV7Options, buf?: undefined, offset?: number): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): string; | ||
| timestamp(id: string): number; | ||
| /** Generate a time-ordered UUID v7 string. */(): string; /** Generate a UUID v7 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf; /** Generate a UUID v7 string with optional timestamp, sequence, or random bytes. */ | ||
| (options?: UuidV7Options, buf?: undefined, offset?: number): string; /** Convert a UUID v7 string to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array; /** Convert 16 canonical UUID bytes to a UUID v7 string. */ | ||
| fromBytes(bytes: Uint8Array): string; /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number; /** Return whether a value is a syntactically valid UUID v7 string. */ | ||
| isValid(id: unknown): id is string; /** The nil UUID (all zeros) */ | ||
@@ -26,0 +30,0 @@ NIL: string; /** The max UUID (all ones) */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v7.d.mts","names":[],"sources":["../../src/uuid/v7.ts"],"mappings":";;;KAKY,aAAA;;;AAAZ;;EAKE,MAAA,GAAS,UAAA;EACT,KAAA;;;;EAIA,GAAA;AAAA;AAAA,KAGU,MAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,aAAA,cAA2B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACvG,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA;EAC3C,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2KW,MAAA,EAAQ,MAAA"} | ||
| {"version":3,"file":"v7.d.mts","names":[],"sources":["../../src/uuid/v7.ts"],"mappings":";;;KAKY,aAAA;;;AAAZ;;EAKE,MAAA,GAAS,UAAA;EAAA;;;;EAKT,KAAA;EAIA;AAAA;AAGF;EAHE,GAAA;AAAA;AAAA,KAGU,MAAA;;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,aAAA,cAA2B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAEvG,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA;EAE3C,OAAA,CAAQ,EAAA,WAAa,UAAA,EAEJ;EAAjB,SAAA,CAAU,KAAA,EAAO,UAAA;EAEjB,SAAA,CAAU,EAAA;EAEV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;AAAA;AA2KF;;;;AAAqB;;;;cAAR,MAAA,EAAQ,MAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v7.mjs","names":[],"sources":["../../src/uuid/v7.ts"],"sourcesContent":["import { rng } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid'\n\nexport type UuidV7Options = {\n /**\n * 16 bytes of random data to use for UUID generation.\n * Note: Several bytes will be overwritten with timestamp, version, and variant data.\n */\n random?: Uint8Array\n msecs?: number\n /**\n * Unsigned 32-bit sequence value.\n */\n seq?: number\n}\n\nexport type UuidV7 = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf\n (options?: UuidV7Options, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil UUID (all zeros) */\n NIL: string\n /** The max UUID (all ones) */\n MAX: string\n}\n\nconst UUID_V7_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\nconst UUID_BYTES = 16\nconst MAX_MSECS = 0xffffffffffff\nconst MAX_SEQ = 0xffffffff\n\n// Reusable buffer for string output path - avoids allocation per call.\n// Safe because bytes are consumed synchronously by formatUuidUnchecked().\nconst reusableBuf = new Uint8Array(UUID_BYTES)\n\ntype V7State = {\n msecs: number\n seq: number\n}\n\n/**\n * Module-level state for maintaining monotonic ordering within the same millisecond.\n *\n * IMPORTANT: This state persists across all uuidv7() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * - For isolated state, pass explicit `msecs` and `seq` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: V7State = { msecs: -Infinity, seq: 0 }\n\nfunction writeV7BytesUnchecked(rnds: Uint8Array, msecs: number, seq: number, buf: Uint8Array, offset: number): void {\n // Keep this inline on the UUID v7 hot path. Using writeTimestamp48() here\n // benchmarked slower than the direct byte writes.\n buf[offset++] = (msecs / 0x10000000000) & 0xff\n buf[offset++] = (msecs / 0x100000000) & 0xff\n buf[offset++] = (msecs / 0x1000000) & 0xff\n buf[offset++] = (msecs / 0x10000) & 0xff\n buf[offset++] = (msecs / 0x100) & 0xff\n buf[offset++] = msecs & 0xff\n\n // Set version (7) and variant (10xx), then pack sequence and random tail bytes.\n buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f)\n buf[offset++] = (seq >>> 20) & 0xff\n buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f)\n buf[offset++] = (seq >>> 6) & 0xff\n // Lower seq bits plus 2 random bits to complete the 128-bit payload.\n buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03)\n buf[offset++] = rnds[11]\n buf[offset++] = rnds[12]\n buf[offset++] = rnds[13]\n buf[offset++] = rnds[14]\n buf[offset++] = rnds[15]\n}\n\nfunction writeV7Bytes(rnds: Uint8Array, msecs: number, seq: number, buf: Uint8Array, offset: number): void {\n if (!isWritableRange(buf, offset, UUID_BYTES)) {\n throw new BufferError(\n 'UUID_BUFFER_OUT_OF_BOUNDS',\n `UUID byte range ${offset}:${offset + UUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeV7BytesUnchecked(rnds, msecs, seq, buf, offset)\n}\n\nfunction v7WithOptions<TBuf extends Uint8Array = Uint8Array>(\n options: UuidV7Options,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n const optMsecs = options.msecs\n if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) {\n throw new InvalidInputError(\n 'UUID_TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between 0 and ${MAX_MSECS}`,\n )\n }\n const optSeq = options.seq\n if (optSeq !== undefined && !isIntegerInRange(optSeq, 0, MAX_SEQ)) {\n throw new InvalidInputError('UUID_SEQUENCE_OUT_OF_RANGE', `Sequence must be an integer between 0 and ${MAX_SEQ}`)\n }\n const optRandom = options.random\n if (optRandom && optRandom.length < UUID_BYTES) {\n throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${UUID_BYTES}`)\n }\n\n const rnds = optRandom ?? rng()\n const msecs = optMsecs ?? Date.now()\n // Derive a 31-bit sequence if not provided by the caller, matching the default hot path.\n const seq = optSeq ?? (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]\n\n if (buf) {\n writeV7Bytes(rnds, msecs, seq, buf, offset)\n return buf\n }\n writeV7BytesUnchecked(rnds, msecs, seq, reusableBuf, 0)\n return formatUuidUnchecked(reusableBuf)\n}\n\n/*\n * Overload: no buffer => return a UUID string.\n */\nfunction v7(options?: UuidV7Options, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with UUID bytes.\n */\nfunction v7<TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf\nfunction v7<TBuf extends Uint8Array = Uint8Array>(options?: UuidV7Options, buf?: TBuf, offset?: number): string | TBuf {\n if (options) {\n return v7WithOptions(options, buf, offset)\n }\n\n // HOT PATH: Inline state management and byte generation for best performance\n const now = Date.now()\n const rnds = rng()\n\n // Update state (inlined for performance)\n if (now > state.msecs) {\n state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]\n state.msecs = now\n } else {\n state.seq = (state.seq + 1) | 0\n if (state.seq < 0) {\n state.seq = 0\n state.msecs++\n }\n }\n\n if (buf) {\n writeV7Bytes(rnds, state.msecs, state.seq, buf, offset ?? 0)\n return buf\n }\n writeV7BytesUnchecked(rnds, state.msecs, state.seq, reusableBuf, 0)\n return formatUuidUnchecked(reusableBuf)\n}\n\nfunction timestamp(id: string): number {\n const bytes = parseUuid(id)\n let msecs = 0\n for (let i = 0; i < 6; i += 1) {\n msecs = msecs * 256 + bytes[i]\n }\n return msecs\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && UUID_V7_REGEX.test(id)\n}\n\n/**\n * Generate a UUID v7 string or write the bytes into a buffer.\n *\n * UUID v7 is a time-ordered UUID that embeds a Unix timestamp in milliseconds,\n * making IDs naturally sortable by creation time. Ideal for database primary keys\n * where chronological ordering improves index performance.\n *\n * @example\n * ```ts\n * import { uuidv7 } from 'uniku/uuid/v7'\n *\n * const id = uuidv7()\n * // => \"018e5e5c-7c8a-7000-8000-000000000000\"\n *\n * // Extract timestamp\n * const ts = uuidv7.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * uuidv7.isValid(id) // true\n *\n * // Convert to/from bytes\n * const bytes = uuidv7.toBytes(id)\n * const restored = uuidv7.fromBytes(bytes)\n * ```\n */\nexport const uuidv7: UuidV7 = Object.assign(v7, {\n toBytes: parseUuid,\n fromBytes: formatUuid,\n timestamp,\n isValid,\n NIL: '00000000-0000-0000-0000-000000000000',\n MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} | ||
| {"version":3,"file":"v7.mjs","names":[],"sources":["../../src/uuid/v7.ts"],"sourcesContent":["import { rng } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid'\n\nexport type UuidV7Options = {\n /**\n * 16 bytes of random data to use for UUID generation.\n * Note: Several bytes will be overwritten with timestamp, version, and variant data.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since Unix epoch.\n * Defaults to Date.now().\n */\n msecs?: number\n /**\n * Unsigned 32-bit sequence value.\n */\n seq?: number\n}\n\nexport type UuidV7 = {\n /** Generate a time-ordered UUID v7 string. */\n (): string\n /** Generate a UUID v7 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a UUID v7 string with optional timestamp, sequence, or random bytes. */\n (options?: UuidV7Options, buf?: undefined, offset?: number): string\n /** Convert a UUID v7 string to its canonical 16-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 16 canonical UUID bytes to a UUID v7 string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid UUID v7 string. */\n isValid(id: unknown): id is string\n /** The nil UUID (all zeros) */\n NIL: string\n /** The max UUID (all ones) */\n MAX: string\n}\n\nconst UUID_V7_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\nconst UUID_BYTES = 16\nconst MAX_MSECS = 0xffffffffffff\nconst MAX_SEQ = 0xffffffff\n\n// Reusable buffer for string output path - avoids allocation per call.\n// Safe because bytes are consumed synchronously by formatUuidUnchecked().\nconst reusableBuf = new Uint8Array(UUID_BYTES)\n\ntype V7State = {\n msecs: number\n seq: number\n}\n\n/**\n * Module-level state for maintaining monotonic ordering within the same millisecond.\n *\n * IMPORTANT: This state persists across all uuidv7() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * - For isolated state, pass explicit `msecs` and `seq` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: V7State = { msecs: -Infinity, seq: 0 }\n\nfunction writeV7BytesUnchecked(rnds: Uint8Array, msecs: number, seq: number, buf: Uint8Array, offset: number): void {\n // Keep this inline on the UUID v7 hot path. Using writeTimestamp48() here\n // benchmarked slower than the direct byte writes.\n buf[offset++] = (msecs / 0x10000000000) & 0xff\n buf[offset++] = (msecs / 0x100000000) & 0xff\n buf[offset++] = (msecs / 0x1000000) & 0xff\n buf[offset++] = (msecs / 0x10000) & 0xff\n buf[offset++] = (msecs / 0x100) & 0xff\n buf[offset++] = msecs & 0xff\n\n // Set version (7) and variant (10xx), then pack sequence and random tail bytes.\n buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f)\n buf[offset++] = (seq >>> 20) & 0xff\n buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f)\n buf[offset++] = (seq >>> 6) & 0xff\n // Lower seq bits plus 2 random bits to complete the 128-bit payload.\n buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03)\n buf[offset++] = rnds[11]\n buf[offset++] = rnds[12]\n buf[offset++] = rnds[13]\n buf[offset++] = rnds[14]\n buf[offset++] = rnds[15]\n}\n\nfunction writeV7Bytes(rnds: Uint8Array, msecs: number, seq: number, buf: Uint8Array, offset: number): void {\n if (!isWritableRange(buf, offset, UUID_BYTES)) {\n throw new BufferError(\n 'UUID_BUFFER_OUT_OF_BOUNDS',\n `UUID byte range ${offset}:${offset + UUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeV7BytesUnchecked(rnds, msecs, seq, buf, offset)\n}\n\nfunction v7WithOptions<TBuf extends Uint8Array = Uint8Array>(\n options: UuidV7Options,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n const optMsecs = options.msecs\n if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) {\n throw new InvalidInputError(\n 'UUID_TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between 0 and ${MAX_MSECS}`,\n )\n }\n const optSeq = options.seq\n if (optSeq !== undefined && !isIntegerInRange(optSeq, 0, MAX_SEQ)) {\n throw new InvalidInputError('UUID_SEQUENCE_OUT_OF_RANGE', `Sequence must be an integer between 0 and ${MAX_SEQ}`)\n }\n const optRandom = options.random\n if (optRandom && optRandom.length < UUID_BYTES) {\n throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${UUID_BYTES}`)\n }\n\n const rnds = optRandom ?? rng()\n const msecs = optMsecs ?? Date.now()\n // Derive a 31-bit sequence if not provided by the caller, matching the default hot path.\n const seq = optSeq ?? (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]\n\n if (buf) {\n writeV7Bytes(rnds, msecs, seq, buf, offset)\n return buf\n }\n writeV7BytesUnchecked(rnds, msecs, seq, reusableBuf, 0)\n return formatUuidUnchecked(reusableBuf)\n}\n\n/*\n * Overload: no buffer => return a UUID string.\n */\nfunction v7(options?: UuidV7Options, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with UUID bytes.\n */\nfunction v7<TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf\nfunction v7<TBuf extends Uint8Array = Uint8Array>(options?: UuidV7Options, buf?: TBuf, offset?: number): string | TBuf {\n if (options) {\n return v7WithOptions(options, buf, offset)\n }\n\n // HOT PATH: Inline state management and byte generation for best performance\n const now = Date.now()\n const rnds = rng()\n\n // Update state (inlined for performance)\n if (now > state.msecs) {\n state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]\n state.msecs = now\n } else {\n state.seq = (state.seq + 1) | 0\n if (state.seq < 0) {\n state.seq = 0\n state.msecs++\n }\n }\n\n if (buf) {\n writeV7Bytes(rnds, state.msecs, state.seq, buf, offset ?? 0)\n return buf\n }\n writeV7BytesUnchecked(rnds, state.msecs, state.seq, reusableBuf, 0)\n return formatUuidUnchecked(reusableBuf)\n}\n\nfunction timestamp(id: string): number {\n const bytes = parseUuid(id)\n let msecs = 0\n for (let i = 0; i < 6; i += 1) {\n msecs = msecs * 256 + bytes[i]\n }\n return msecs\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && UUID_V7_REGEX.test(id)\n}\n\n/**\n * Generate a UUID v7 string or write the bytes into a buffer.\n *\n * UUID v7 is a time-ordered UUID that embeds a Unix timestamp in milliseconds,\n * making IDs naturally sortable by creation time. Ideal for database primary keys\n * where chronological ordering improves index performance.\n *\n * @example\n * ```ts\n * import { uuidv7 } from 'uniku/uuid/v7'\n *\n * const id = uuidv7()\n * // => \"018e5e5c-7c8a-7000-8000-000000000000\"\n *\n * // Extract timestamp\n * const ts = uuidv7.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * uuidv7.isValid(id) // true\n *\n * // Convert to/from bytes\n * const bytes = uuidv7.toBytes(id)\n * const restored = uuidv7.fromBytes(bytes)\n * ```\n */\nexport const uuidv7: UuidV7 = Object.assign(v7, {\n toBytes: parseUuid,\n fromBytes: formatUuid,\n timestamp,\n isValid,\n NIL: '00000000-0000-0000-0000-000000000000',\n MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"} |
+1
-1
| { | ||
| "private": false, | ||
| "name": "uniku", | ||
| "version": "0.3.1", | ||
| "version": "0.3.2", | ||
| "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -21,3 +21,5 @@ import { sha3_512 } from '@noble/hashes/sha3.js' | ||
| export type Cuid2 = { | ||
| /** Generate a CUID v2 string. */ | ||
| (options?: Cuid2Options): string | ||
| /** Return whether a value is a syntactically valid CUID v2 string. */ | ||
| isValid(id: unknown): id is string | ||
@@ -185,9 +187,9 @@ } | ||
| /** | ||
| * Generate a CUID2 string. | ||
| * Generate a CUID v2 string. | ||
| * | ||
| * CUID2 is a secure, collision-resistant identifier that hashes multiple | ||
| * CUID v2 is a secure, collision-resistant identifier that hashes multiple | ||
| * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7), | ||
| * CUID2 prevents enumeration attacks by making IDs non-predictable. | ||
| * CUID v2 prevents enumeration attacks by making IDs non-predictable. | ||
| * | ||
| * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native | ||
| * Note: CUID v2 does not provide toBytes/fromBytes because it is a string-native | ||
| * format with no canonical binary representation (unlike UUID's 16-byte format). | ||
@@ -194,0 +196,0 @@ * |
@@ -48,8 +48,15 @@ import { writeTimestamp32 } from '../common/bytes' | ||
| export type Ksuid = { | ||
| /** Generate a time-ordered KSUID string. */ | ||
| (): string | ||
| /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf | ||
| /** Generate a KSUID string with optional timestamp or random payload bytes. */ | ||
| (options?: KsuidOptions, buf?: undefined, offset?: number): string | ||
| /** Convert a KSUID string to its canonical 20-byte representation. */ | ||
| toBytes(id: string): Uint8Array | ||
| /** Convert 20 canonical KSUID bytes to a KSUID string. */ | ||
| fromBytes(bytes: Uint8Array): string | ||
| /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number | ||
| /** Return whether a value is a syntactically valid KSUID string. */ | ||
| isValid(id: unknown): id is string | ||
@@ -56,0 +63,0 @@ /** The nil KSUID (all zeros) */ |
@@ -58,7 +58,7 @@ import { InvalidInputError } from '../errors' | ||
| export type Nanoid = { | ||
| /** Generate nanoid with default settings */ | ||
| /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */ | ||
| (): string | ||
| /** Generate nanoid with custom size */ | ||
| /** Generate a Nanoid with the default alphabet and a custom length. */ | ||
| (size: number): string | ||
| /** Generate nanoid with options */ | ||
| /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */ | ||
| (options: NanoidOptions): string | ||
@@ -65,0 +65,0 @@ /** |
@@ -45,8 +45,15 @@ import { writeTimestamp32 } from '../common/bytes' | ||
| export type ObjectId = { | ||
| /** Generate a MongoDB-compatible ObjectID string. */ | ||
| (): string | ||
| /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf | ||
| /** Generate an ObjectID string with optional timestamp, random field, or counter. */ | ||
| (options?: ObjectIdOptions, buf?: undefined, offset?: number): string | ||
| /** Convert an ObjectID string to its canonical 12-byte representation. */ | ||
| toBytes(id: string): Uint8Array | ||
| /** Convert 12 canonical ObjectID bytes to an ObjectID string. */ | ||
| fromBytes(bytes: Uint8Array): string | ||
| /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number | ||
| /** Return whether a value is a syntactically valid ObjectID string. */ | ||
| isValid(id: unknown): id is string | ||
@@ -53,0 +60,0 @@ /** The nil ObjectID (all zeros) */ |
+9
-0
@@ -65,10 +65,19 @@ import { randomUint32 } from '../common/random' | ||
| export type Tsid = { | ||
| /** Generate a time-sorted TSID bigint. */ | ||
| (): bigint | ||
| /** Generate a TSID with explicit options or write its 8 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf | ||
| /** Generate a TSID bigint with optional timestamp, epoch, node, or counter controls. */ | ||
| (options?: TsidOptions, buf?: undefined, offset?: number): bigint | ||
| /** Convert a TSID bigint to its canonical 8-byte representation. */ | ||
| toBytes(id: bigint): Uint8Array | ||
| /** Convert 8 canonical TSID bytes to a TSID bigint. */ | ||
| fromBytes(bytes: Uint8Array): bigint | ||
| /** Convert a TSID bigint to its canonical 13-character string. */ | ||
| toString(id: bigint): string | ||
| /** Convert a canonical TSID string to a bigint. */ | ||
| fromString(str: string): bigint | ||
| /** Read a TSID timestamp in milliseconds, using the supplied or default epoch. */ | ||
| timestamp(id: bigint, epoch?: number): number | ||
| /** Return whether a value is a valid 64-bit TSID bigint. */ | ||
| isValid(id: unknown): id is bigint | ||
@@ -75,0 +84,0 @@ /** The nil TSID (all zeros) */ |
@@ -8,10 +8,19 @@ import { InvalidInputError, ParseError } from '../errors' | ||
| export type Typeid = { | ||
| /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */ | ||
| (prefix: string, options?: TypeidOptions): string | ||
| /** Convert a TypeID's UUID v7 suffix to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array | ||
| /** Build a TypeID from a prefix and canonical UUID v7 bytes. */ | ||
| fromBytes(prefix: string, bytes: Uint8Array): string | ||
| /** Convert a TypeID to its UUID v7 string. */ | ||
| toUuid(id: string): string | ||
| /** Build a TypeID from a prefix and UUID v7 string. */ | ||
| fromUuid(prefix: string, uuid: string): string | ||
| /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. */ | ||
| timestamp(id: string): number | ||
| /** Read a TypeID's entity prefix. */ | ||
| prefix(id: string): string | ||
| /** Read a TypeID's UUID v7 suffix. */ | ||
| suffix(id: string): string | ||
| /** Return whether a value is a syntactically valid TypeID. */ | ||
| isValid(id: unknown): id is string | ||
@@ -18,0 +27,0 @@ } |
+11
-0
@@ -21,8 +21,15 @@ import { incrementBytesInPlace, writeTimestamp48 } from '../common/bytes' | ||
| export type Ulid = { | ||
| /** Generate a time-ordered ULID string. */ | ||
| (): string | ||
| /** Generate a ULID with explicit options or write its 16 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf | ||
| /** Generate a ULID string with optional timestamp or random bytes. */ | ||
| (options?: UlidOptions, buf?: undefined, offset?: number): string | ||
| /** Convert a ULID string to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array | ||
| /** Convert 16 canonical ULID bytes to a ULID string. */ | ||
| fromBytes(bytes: Uint8Array): string | ||
| /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number | ||
| /** Return whether a value is a syntactically valid ULID string. */ | ||
| isValid(id: unknown): id is string | ||
@@ -187,2 +194,6 @@ /** The nil ULID (all zeros) */ | ||
| /** | ||
| * Generate a ULID string or write its 16 canonical bytes into a buffer. | ||
| * ULIDs are URL-safe, carry a millisecond timestamp, and sort by creation time. | ||
| */ | ||
| export const ulid: Ulid = Object.assign(ulidFn, { | ||
@@ -189,0 +200,0 @@ toBytes: (id: string) => decodeToBytes(id), |
+6
-0
@@ -18,7 +18,13 @@ import { rng } from '../common/random' | ||
| export type UuidV4 = { | ||
| /** Generate a random UUID v4 string. */ | ||
| (): string | ||
| /** Generate a UUID v4 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf | ||
| /** Generate a UUID v4 string with optional deterministic random bytes. */ | ||
| (options?: UuidV4Options, buf?: undefined, offset?: number): string | ||
| /** Convert a UUID v4 string to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array | ||
| /** Convert 16 canonical UUID bytes to a UUID v4 string. */ | ||
| fromBytes(bytes: Uint8Array): string | ||
| /** Return whether a value is a syntactically valid UUID v4 string. */ | ||
| isValid(id: unknown): id is string | ||
@@ -25,0 +31,0 @@ /** The nil UUID (all zeros) */ |
+11
-0
@@ -12,2 +12,6 @@ import { rng } from '../common/random' | ||
| random?: Uint8Array | ||
| /** | ||
| * Timestamp in milliseconds since Unix epoch. | ||
| * Defaults to Date.now(). | ||
| */ | ||
| msecs?: number | ||
@@ -21,8 +25,15 @@ /** | ||
| export type UuidV7 = { | ||
| /** Generate a time-ordered UUID v7 string. */ | ||
| (): string | ||
| /** Generate a UUID v7 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */ | ||
| <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf | ||
| /** Generate a UUID v7 string with optional timestamp, sequence, or random bytes. */ | ||
| (options?: UuidV7Options, buf?: undefined, offset?: number): string | ||
| /** Convert a UUID v7 string to its canonical 16-byte representation. */ | ||
| toBytes(id: string): Uint8Array | ||
| /** Convert 16 canonical UUID bytes to a UUID v7 string. */ | ||
| fromBytes(bytes: Uint8Array): string | ||
| /** Read the embedded Unix timestamp in milliseconds. */ | ||
| timestamp(id: string): number | ||
| /** Return whether a value is a syntactically valid UUID v7 string. */ | ||
| isValid(id: unknown): id is string | ||
@@ -29,0 +40,0 @@ /** The nil UUID (all zeros) */ |
308940
4.21%2858
2.22%