| import{BufferError as e,ParseError as t}from"./errors.mjs";function n(e){return e>=48&&e<=57?e-48:e>=65&&e<=70?e-65+10:e>=97&&e<=102?e-97+10:-1}const r=[0,0,1,1,2,2,3,3,-1,4,4,5,5,-1,6,6,7,7,-1,8,8,9,9,-1,10,10,11,11,12,12,13,13,14,14,15,15],i=[!0,!1,!0,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1],a=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`));function o(t){if(t.length!==16)throw new e(`UUID_BYTES_INVALID_LENGTH`,`UUID bytes must be exactly 16 bytes, got ${t.length}`);return s(t)}function s(e){return a[e[0]]+a[e[1]]+a[e[2]]+a[e[3]]+`-`+a[e[4]]+a[e[5]]+`-`+a[e[6]]+a[e[7]]+`-`+a[e[8]]+a[e[9]]+`-`+a[e[10]]+a[e[11]]+a[e[12]]+a[e[13]]+a[e[14]]+a[e[15]]}function c(e){if(e.length!==36)throw new t(`UUID_INVALID_LENGTH`,`UUID string must be 36 characters, got ${e.length}`);if(e[8]!==`-`||e[13]!==`-`||e[18]!==`-`||e[23]!==`-`)throw new t(`UUID_INVALID_SEPARATORS`,`UUID string has invalid separators at positions 8, 13, 18, 23. Received: "${e}"`);let a=new Uint8Array(16);for(let o=0;o<36;o+=1){let s=r[o];if(s===-1)continue;let c=n(e.charCodeAt(o));if(c===-1)throw new t(`UUID_INVALID_HEX_CHAR`,`UUID string contains invalid hex character at position ${o}`);i[o]?a[s]=c<<4:a[s]|=c}return a}export{s as n,c as r,o as t}; | ||
| //# sourceMappingURL=uuid-CnQIYoQi.mjs.map |
| {"version":3,"file":"uuid-CnQIYoQi.mjs","names":[],"sources":["../src/uuid/common/hex.ts","../src/uuid/common/uuid.ts"],"sourcesContent":["// ASCII code ranges for hex character validation\nconst ASCII_0 = 48 // '0'\nconst ASCII_9 = 57 // '9'\nconst ASCII_A = 65 // 'A'\nconst ASCII_F = 70 // 'F'\nconst ASCII_a = 97 // 'a'\nconst ASCII_f = 102 // 'f'\n\n/**\n * Convert a hex character ASCII code to its numeric value (0-15).\n * Returns -1 for invalid hex characters.\n */\nexport function hexValue(code: number): number {\n if (code >= ASCII_0 && code <= ASCII_9) {\n return code - ASCII_0\n }\n if (code >= ASCII_A && code <= ASCII_F) {\n return code - ASCII_A + 10\n }\n if (code >= ASCII_a && code <= ASCII_f) {\n return code - ASCII_a + 10\n }\n return -1 // Invalid hex character\n}\n","import { BufferError, ParseError } from '../../errors'\nimport { hexValue } from './hex'\n\nconst UUID_BYTE_LENGTH = 16\nconst UUID_STRING_LENGTH = 36\n\n// Mapping from UUID string position to byte index.\n// -1 indicates a dash position that should be skipped.\n// This avoids intermediate string allocations during parsing.\nconst UUID_CHAR_TO_BYTE: number[] = [\n 0,\n 0,\n 1,\n 1,\n 2,\n 2,\n 3,\n 3, // chars 0-7 → bytes 0-3\n -1, // char 8 is '-'\n 4,\n 4,\n 5,\n 5, // chars 9-12 → bytes 4-5\n -1, // char 13 is '-'\n 6,\n 6,\n 7,\n 7, // chars 14-17 → bytes 6-7\n -1, // char 18 is '-'\n 8,\n 8,\n 9,\n 9, // chars 19-22 → bytes 8-9\n -1, // char 23 is '-'\n 10,\n 10,\n 11,\n 11,\n 12,\n 12,\n 13,\n 13,\n 14,\n 14,\n 15,\n 15, // chars 24-35 → bytes 10-15\n]\n\n// Whether each position is the high nibble (true) or low nibble (false)\nconst UUID_CHAR_IS_HIGH: boolean[] = [\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 0-7\n false, // dash (ignored)\n true,\n false,\n true,\n false, // chars 9-12\n false, // dash\n true,\n false,\n true,\n false, // chars 14-17\n false, // dash\n true,\n false,\n true,\n false, // chars 19-22\n false, // dash\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 24-35\n]\n\n// Pre-computed lookup table for byte-to-hex conversion (0x00 -> \"00\", 0xff -> \"ff\")\n//\n// Note: this table must remain defined in the same module as `formatUuid`, otherwise the v8 optimizer\n// will cause a performance drop of ~36%.\nconst HEX_TABLE: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\nexport function formatUuid(bytes: Uint8Array): string {\n if (bytes.length !== UUID_BYTE_LENGTH) {\n throw new BufferError(\n 'UUID_BYTES_INVALID_LENGTH',\n `UUID bytes must be exactly ${UUID_BYTE_LENGTH} bytes, got ${bytes.length}`,\n )\n }\n\n return formatUuidUnchecked(bytes)\n}\n\nexport function formatUuidUnchecked(bytes: Uint8Array): string {\n // Direct string concatenation - optimized for V8's string builder.\n // This approach avoids loop overhead and intermediate allocations.\n // See: https://github.com/uuidjs/uuid/pull/434\n return (\n HEX_TABLE[bytes[0]] +\n HEX_TABLE[bytes[1]] +\n HEX_TABLE[bytes[2]] +\n HEX_TABLE[bytes[3]] +\n '-' +\n HEX_TABLE[bytes[4]] +\n HEX_TABLE[bytes[5]] +\n '-' +\n HEX_TABLE[bytes[6]] +\n HEX_TABLE[bytes[7]] +\n '-' +\n HEX_TABLE[bytes[8]] +\n HEX_TABLE[bytes[9]] +\n '-' +\n HEX_TABLE[bytes[10]] +\n HEX_TABLE[bytes[11]] +\n HEX_TABLE[bytes[12]] +\n HEX_TABLE[bytes[13]] +\n HEX_TABLE[bytes[14]] +\n HEX_TABLE[bytes[15]]\n )\n}\n\nexport function parseUuid(value: string): Uint8Array {\n if (value.length !== UUID_STRING_LENGTH) {\n throw new ParseError('UUID_INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`)\n }\n\n // Validate separator positions directly (more efficient than full loop)\n if (value[8] !== '-' || value[13] !== '-' || value[18] !== '-' || value[23] !== '-') {\n throw new ParseError(\n 'UUID_INVALID_SEPARATORS',\n `UUID string has invalid separators at positions 8, 13, 18, 23. Received: \"${value}\"`,\n )\n }\n\n // Parse bytes directly from UUID string without intermediate string allocations.\n // This avoids the 9 allocations (5 slices + 4 concatenations) of the naive approach.\n const bytes = new Uint8Array(UUID_BYTE_LENGTH)\n\n for (let i = 0; i < UUID_STRING_LENGTH; i += 1) {\n const byteIdx = UUID_CHAR_TO_BYTE[i]\n if (byteIdx === -1) continue // Skip dash positions\n\n const nibble = hexValue(value.charCodeAt(i))\n if (nibble === -1) {\n throw new ParseError('UUID_INVALID_HEX_CHAR', `UUID string contains invalid hex character at position ${i}`)\n }\n\n if (UUID_CHAR_IS_HIGH[i]) {\n bytes[byteIdx] = nibble << 4\n } else {\n bytes[byteIdx] |= nibble\n }\n }\n\n return bytes\n}\n"],"mappings":"2DAYA,SAAgB,EAAS,EAAsB,CAU7C,OATI,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAEZ,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAAU,GAEtB,GAAQ,IAAW,GAAQ,IACtB,EAAO,GAAU,GAEnB,EACT,CCpBA,MAMM,EAA8B,CAClC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,EACF,EAGM,EAA+B,CACnC,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,EACF,EAMM,EAAsB,MAAM,KAAK,CAAE,OAAQ,GAAI,GAAI,EAAG,IAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,EAEjG,SAAgB,EAAW,EAA2B,CACpD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EACR,4BACA,4CAA6D,EAAM,QACrE,EAGF,OAAO,EAAoB,CAAK,CAClC,CAEA,SAAgB,EAAoB,EAA2B,CAI7D,OACE,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,IAEpB,CAEA,SAAgB,EAAU,EAA2B,CACnD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAW,sBAAuB,0CAA0C,EAAM,QAAQ,EAItG,GAAI,EAAM,KAAO,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,IAC9E,MAAM,IAAI,EACR,0BACA,6EAA6E,EAAM,EACrF,EAKF,IAAM,EAAQ,IAAI,WAAW,EAAgB,EAE7C,IAAK,IAAI,EAAI,EAAG,EAAI,GAAoB,GAAK,EAAG,CAC9C,IAAM,EAAU,EAAkB,GAClC,GAAI,IAAY,GAAI,SAEpB,IAAM,EAAS,EAAS,EAAM,WAAW,CAAC,CAAC,EAC3C,GAAI,IAAW,GACb,MAAM,IAAI,EAAW,wBAAyB,0DAA0D,GAAG,EAGzG,EAAkB,GACpB,EAAM,GAAW,GAAU,EAE3B,EAAM,IAAY,CAEtB,CAEA,OAAO,CACT"} |
| function e(e,t,n){return Number.isInteger(e)&&e>=t&&e<=n}function t(e,t,n){return Number.isInteger(t)&&t>=0&&t+n<=e.length}export{t as n,e as t}; | ||
| //# sourceMappingURL=validation-CTNpXm94.mjs.map |
| {"version":3,"file":"validation-CTNpXm94.mjs","names":[],"sources":["../src/common/validation.ts"],"sourcesContent":["export function isIntegerInRange(value: number, min: number, max: number): boolean {\n return Number.isInteger(value) && value >= min && value <= max\n}\n\nexport function isWritableRange(buffer: Uint8Array, offset: number, byteLength: number): boolean {\n return Number.isInteger(offset) && offset >= 0 && offset + byteLength <= buffer.length\n}\n"],"mappings":"AAAA,SAAgB,EAAiB,EAAe,EAAa,EAAsB,CACjF,OAAO,OAAO,UAAU,CAAK,GAAK,GAAS,GAAO,GAAS,CAC7D,CAEA,SAAgB,EAAgB,EAAoB,EAAgB,EAA6B,CAC/F,OAAO,OAAO,UAAU,CAAM,GAAK,GAAU,GAAK,EAAS,GAAc,EAAO,MAClF"} |
| export function isIntegerInRange(value: number, min: number, max: number): boolean { | ||
| return Number.isInteger(value) && value >= min && value <= max | ||
| } | ||
| export function isWritableRange(buffer: Uint8Array, offset: number, byteLength: number): boolean { | ||
| return Number.isInteger(offset) && offset >= 0 && offset + byteLength <= buffer.length | ||
| } |
@@ -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;AA6LxB;;;;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;EAAA,CACT,OAAA,GAAU,YAAA;EACX,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;AAAA;AAiMxB;;;;AAAoB;;;;;;;;;;;;;;;;;;;;;;;cAAP,KAAA,EAAO,KAAA"} |
@@ -1,2 +0,2 @@ | ||
| import{n as e}from"../random-Chp-Nkzi.mjs";import{InvalidInputError as t,UniqueIdError as n}from"../errors.mjs";import{sha3_512 as r}from"@noble/hashes/sha3.js";const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder,s={counter:void 0,fingerprint:void 0};function c(){return e()%476782368}function l(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function u(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function d(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function f(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function p(e){return r(o.encode(e))}function m(){let e=h;return u(l(p(Object.keys(globalThis).toString()+f(32,e)))).slice(1,33)}function h(){return e()/4294967296}function g(e){if(e){if(e.length===0)throw new t(`CUID2_RANDOM_BYTES_EMPTY`,`Random byte array cannot be empty`);let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return h}function _(e){let n=e?.length??24;if(n<2||n>32)throw new t(`CUID2_LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`);let r=g(e?.random);s.counter===void 0&&(s.counter=c()),s.fingerprint===void 0&&(s.fingerprint=m());let i=d(r),a=Date.now().toString(36);s.counter+=1;let o=s.counter.toString(36);return i+u(l(p(a+f(n,r)+o+s.fingerprint))).slice(1,n)}function v(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const y=Object.assign(_,{isValid:v});export{t as InvalidInputError,n as UniqueIdError,y as cuid2}; | ||
| import{n as e}from"../random-Chp-Nkzi.mjs";import{InvalidInputError as t,UniqueIdError as n}from"../errors.mjs";import{sha3_512 as r}from"@noble/hashes/sha3.js";const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder,s={counter:void 0,fingerprint:void 0};function c(){return e()%476782368}function l(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function u(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function d(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function f(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function p(e){return r(o.encode(e))}function m(){let e=h;return u(l(p(Object.keys(globalThis).toString()+f(32,e)))).slice(1,33)}function h(){return e()/4294967296}function g(e){if(e){if(e.length===0)throw new t(`CUID2_RANDOM_BYTES_EMPTY`,`Random byte array cannot be empty`);let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return h}function _(e){let n=e?.length;if(n!==void 0&&(!Number.isInteger(n)||n<2||n>32))throw new t(`CUID2_LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`);let r=n??24,i=g(e?.random);s.counter===void 0&&(s.counter=c()),s.fingerprint===void 0&&(s.fingerprint=m());let a=d(i),o=Date.now().toString(36);s.counter+=1;let h=s.counter.toString(36);return a+u(l(p(o+f(r,i)+h+s.fingerprint))).slice(1,r)}function v(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const y=Object.assign(_,{isValid:v});export{t as InvalidInputError,n as UniqueIdError,y as cuid2}; | ||
| //# sourceMappingURL=cuid2.mjs.map |
@@ -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 length = options?.length ?? DEFAULT_LENGTH\n\n if (length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new InvalidInputError(\n 'CUID2_LENGTH_OUT_OF_RANGE',\n `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`,\n )\n }\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,EAAS,GAAS,QAAU,GAElC,GAAI,EAAS,GAAc,EAAS,GAClC,MAAM,IAAI,EACR,4BACA,oDAA2E,GAC7E,EAGF,IAAM,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 (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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ksuid.d.mts","names":[],"sources":["../../src/ksuid/ksuid.ts"],"mappings":";;;KAgCY,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;;;;;cAiIW,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;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"} |
@@ -1,2 +0,2 @@ | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a}from"../bytes-xqWxFYsM.mjs";const o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,s=62n,c=(1n<<160n)-1n,l=new Uint8Array(65536);l.fill(255);for(let e=0;e<62;e+=1)l[o.charCodeAt(e)]=e;function u(e){if(e.length<20)throw new t(`KSUID_BYTES_TOO_SHORT`,`KSUID bytes must be at least 20 bytes, got ${e.length}`);let n=0n;for(let t=0;t<20;t+=1)n=n<<8n|BigInt(e[t]);let r=``;for(;n>0n;){let e=n%s;n/=s,r=o[Number(e)]+r}return r.padStart(27,`0`)}function d(e){if(e.length!==27)throw new r(`KSUID_INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`);let t=0n;for(let n=0;n<27;n+=1){let i=l[e.charCodeAt(n)];if(i===255)throw new r(`KSUID_INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`);t=t*s+BigInt(i)}if(t>c)throw new r(`KSUID_OVERFLOW`,`KSUID string exceeds 160-bit range`);let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const f=14e8,p=u(new Uint8Array(20).fill(255)),m=/^[0-9A-Za-z]{27}$/;function h(e,n,r,i=0){if(!r)r=new Uint8Array(20),i=0;else if(i<0||i+20>r.length)throw new t(`KSUID_BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${i}:${i+20-1} is out of buffer bounds`);a(r,i,e);for(let e=0;e<16;e+=1)r[i+4+e]=n[e];return r}function g(t,r,i=0){let a=t?.random;if(a&&a.length<16)throw new n(`KSUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`);let o,s=t?.secs;if(s!==void 0){if(s<f)throw new n(`KSUID_TIMESTAMP_TOO_LOW`,`Timestamp must be >= KSUID epoch`);if(s>5694967295)throw new n(`KSUID_TIMESTAMP_TOO_HIGH`,`Timestamp must be <= maximum KSUID timestamp`);o=s-f}else o=Math.floor(Date.now()/1e3)-f;let c=a??e();return r?(h(o,c,r,i),r):u(h(o,c))}function _(e){return d(e)}function v(e){return u(e)}function y(e){let t=d(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+f)*1e3}function b(e){return typeof e==`string`&&e.length===27&&m.test(e)&&e<=p}const x=Object.assign(g,{toBytes:_,fromBytes:v,timestamp:y,isValid:b,NIL:`000000000000000000000000000`,MAX:p});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,x as ksuid}; | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o}from"../bytes-xqWxFYsM.mjs";const s=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,c=62n,l=(1n<<160n)-1n,u=new Uint8Array(65536);u.fill(255);for(let e=0;e<62;e+=1)u[s.charCodeAt(e)]=e;function d(e){if(e.length<20)throw new n(`KSUID_BYTES_TOO_SHORT`,`KSUID bytes must be at least 20 bytes, got ${e.length}`);let t=0n;for(let n=0;n<20;n+=1)t=t<<8n|BigInt(e[n]);let r=``;for(;t>0n;){let e=t%c;t/=c,r=s[Number(e)]+r}return r.padStart(27,`0`)}function f(e){if(e.length!==27)throw new i(`KSUID_INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`);let t=0n;for(let n=0;n<27;n+=1){let r=u[e.charCodeAt(n)];if(r===255)throw new i(`KSUID_INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`);t=t*c+BigInt(r)}if(t>l)throw new i(`KSUID_OVERFLOW`,`KSUID string exceeds 160-bit range`);let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const p=14e8,m=d(new Uint8Array(20).fill(255)),h=/^[0-9A-Za-z]{27}$/;function g(e,t,n,r){o(n,r,e);for(let e=0;e<16;e+=1)n[r+4+e]=t[e]}function _(i,a,o=0){let s=i?.random;if(s&&s.length<16)throw new r(`KSUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`);let c,l=i?.secs;if(l!==void 0){if(!Number.isInteger(l)||l<p)throw new r(`KSUID_TIMESTAMP_TOO_LOW`,`Timestamp must be >= KSUID epoch`);if(l>5694967295)throw new r(`KSUID_TIMESTAMP_TOO_HIGH`,`Timestamp must be <= maximum KSUID timestamp`);c=l-p}else c=Math.floor(Date.now()/1e3)-p;let u=s??e();if(a){if(!t(a,o,20))throw new n(`KSUID_BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${o}:${o+20-1} is out of buffer bounds`);return g(c,u,a,o),a}let f=new Uint8Array(20);return g(c,u,f,0),d(f)}function v(e){return f(e)}function y(e){if(e.length!==20)throw new n(`KSUID_BYTES_INVALID_LENGTH`,`KSUID bytes must be exactly 20 bytes, got ${e.length}`);return d(e)}function b(e){let t=f(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+p)*1e3}function x(e){return typeof e==`string`&&e.length===27&&h.test(e)&&e<=m}const S=Object.assign(_,{toBytes:v,fromBytes:y,timestamp:b,isValid:x,NIL:`000000000000000000000000000`,MAX:m});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,S as ksuid}; | ||
| //# sourceMappingURL=ksuid.mjs.map |
@@ -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 { 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 KSUID bytes to a buffer.\n */\nfunction ksuidBytes(timestamp: number, payload: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {\n if (!buf) {\n buf = new Uint8Array(KSUID_BYTES)\n offset = 0\n } else if (offset < 0 || offset + KSUID_BYTES > buf.length) {\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\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 return buf\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 (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 ksuidBytes(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = ksuidBytes(timestamp, payload)\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 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 (): 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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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"objectid.d.mts","names":[],"sources":["../../src/objectid/objectid.ts"],"mappings":";;;KA0BY,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;AA2MF;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;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"} |
@@ -1,2 +0,2 @@ | ||
| import{n as e,t}from"../random-Chp-Nkzi.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o}from"../bytes-xqWxFYsM.mjs";const s=`0123456789abcdef`,c=Array.from({length:256},(e,t)=>s[t>>4&15]+s[t&15]),l=new Uint8Array(65536);l.fill(255);for(let e=0;e<=9;e+=1)l[48+e]=e;for(let e=0;e<6;e+=1)l[97+e]=10+e,l[65+e]=10+e;function u(e){if(e.length<12)throw new n(`OBJECTID_BYTES_TOO_SHORT`,`ObjectID bytes must be at least 12 bytes, got ${e.length}`);let t=``;for(let n=0;n<12;n+=1)t+=c[e[n]];return t}function d(e){if(e.length!==24)throw new i(`OBJECTID_INVALID_LENGTH`,`ObjectID string must be 24 characters, got ${e.length}`);let t=new Uint8Array(12);for(let n=0;n<12;n+=1){let r=n*2,a=r+1,o=l[e.charCodeAt(r)],s=l[e.charCodeAt(a)];if(o===255)throw new i(`OBJECTID_INVALID_CHAR`,`Invalid ObjectID character: ${e[r]}`);if(s===255)throw new i(`OBJECTID_INVALID_CHAR`,`Invalid ObjectID character: ${e[a]}`);t[n]=o<<4|s}return t}const f=4294967295,p=16777215,m=/^[0-9a-f]{24}$/i,h={random:void 0,counter:void 0};function g(){let e=new Uint8Array(5);return e.set(t(5)),e}function _(e,t,r,i,a=0){if(!i)i=new Uint8Array(12),a=0;else if(a<0||a+12>i.length)throw new n(`OBJECTID_BUFFER_OUT_OF_BOUNDS`,`ObjectID byte range ${a}:${a+12-1} is out of buffer bounds`);o(i,a,e);for(let e=0;e<5;e+=1)i[a+4+e]=t[e];return i[a+4+5]=r>>>16&255,i[a+4+5+1]=r>>>8&255,i[a+4+5+3-1]=r&255,i}function v(t,n,i=0){let a,o,s;if(t){let n=t.random;if(n&&n.length<5)throw new r(`OBJECTID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 5 for ObjectID`);let i=t.secs;if(i!==void 0&&(i<0||i>f))throw new r(`OBJECTID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be between 0 and ${f}`);let c=t.counter;if(c!==void 0&&(c<0||c>p))throw new r(`OBJECTID_COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${p}`);a=i??Math.floor(Date.now()/1e3),o=n??g(),s=c??e()&p}else h.random===void 0&&(h.random=g()),h.counter===void 0&&(h.counter=e()&p),a=Math.floor(Date.now()/1e3),o=h.random,h.counter=h.counter+1&p,s=h.counter;return n?(_(a,o,s,n,i),n):u(_(a,o,s))}function y(e){return d(e)}function b(e){return u(e)}function x(e){let t=d(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function S(e){return typeof e==`string`&&m.test(e)}const C=Object.assign(v,{toBytes:y,fromBytes:b,timestamp:x,isValid:S,NIL:`0`.repeat(24),MAX:`f`.repeat(24)});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,C as objectid}; | ||
| import{n as e,t}from"../random-Chp-Nkzi.mjs";import{n,t as r}from"../validation-CTNpXm94.mjs";import{BufferError as i,InvalidInputError as a,ParseError as o,UniqueIdError as s}from"../errors.mjs";import{n as c}from"../bytes-xqWxFYsM.mjs";const l=`0123456789abcdef`,u=Array.from({length:256},(e,t)=>l[t>>4&15]+l[t&15]),d=new Uint8Array(65536);d.fill(255);for(let e=0;e<=9;e+=1)d[48+e]=e;for(let e=0;e<6;e+=1)d[97+e]=10+e,d[65+e]=10+e;function f(e){if(e.length<12)throw new i(`OBJECTID_BYTES_TOO_SHORT`,`ObjectID bytes must be at least 12 bytes, got ${e.length}`);let t=``;for(let n=0;n<12;n+=1)t+=u[e[n]];return t}function p(e){if(e.length!==24)throw new o(`OBJECTID_INVALID_LENGTH`,`ObjectID string must be 24 characters, got ${e.length}`);let t=new Uint8Array(12);for(let n=0;n<12;n+=1){let r=n*2,i=r+1,a=d[e.charCodeAt(r)],s=d[e.charCodeAt(i)];if(a===255)throw new o(`OBJECTID_INVALID_CHAR`,`Invalid ObjectID character: ${e[r]}`);if(s===255)throw new o(`OBJECTID_INVALID_CHAR`,`Invalid ObjectID character: ${e[i]}`);t[n]=a<<4|s}return t}const m=4294967295,h=16777215,g=/^[0-9a-f]{24}$/i,_={random:void 0,counter:void 0};function v(){let e=new Uint8Array(5);return e.set(t(5)),e}function y(e,t,n,r,i){c(r,i,e);for(let e=0;e<5;e+=1)r[i+4+e]=t[e];r[i+4+5]=n>>>16&255,r[i+4+5+1]=n>>>8&255,r[i+4+5+3-1]=n&255}function b(t,o,s=0){let c,l,u;if(t){let n=t.random;if(n&&n.length<5)throw new a(`OBJECTID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 5 for ObjectID`);let i=t.secs;if(i!==void 0&&!r(i,0,m))throw new a(`OBJECTID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be between 0 and ${m}`);let o=t.counter;if(o!==void 0&&!r(o,0,h))throw new a(`OBJECTID_COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${h}`);c=i??Math.floor(Date.now()/1e3),l=n??v(),u=o??e()&h}else _.random===void 0&&(_.random=v()),_.counter===void 0&&(_.counter=e()&h),c=Math.floor(Date.now()/1e3),l=_.random,_.counter=_.counter+1&h,u=_.counter;if(o){if(!n(o,s,12))throw new i(`OBJECTID_BUFFER_OUT_OF_BOUNDS`,`ObjectID byte range ${s}:${s+12-1} is out of buffer bounds`);return y(c,l,u,o,s),o}let d=new Uint8Array(12);return y(c,l,u,d,0),f(d)}function x(e){return p(e)}function S(e){if(e.length!==12)throw new i(`OBJECTID_BYTES_INVALID_LENGTH`,`ObjectID bytes must be exactly 12 bytes, got ${e.length}`);return f(e)}function C(e){let t=p(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function w(e){return typeof e==`string`&&g.test(e)}const T=Object.assign(b,{toBytes:x,fromBytes:S,timestamp:C,isValid:w,NIL:`0`.repeat(24),MAX:`f`.repeat(24)});export{i as BufferError,a as InvalidInputError,o as ParseError,s as UniqueIdError,T as objectid}; | ||
| //# sourceMappingURL=objectid.mjs.map |
@@ -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 { 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 objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?: Uint8Array, offset = 0): Uint8Array {\n if (!buf) {\n buf = new Uint8Array(OBJECTID_BYTES)\n offset = 0\n } else if (offset < 0 || offset + OBJECTID_BYTES > buf.length) {\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\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 return buf\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 && (optSecs < 0 || optSecs > 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 && (optCounter < 0 || optCounter > 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 objectIdBytes(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = objectIdBytes(secs, random, counter)\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 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 (): 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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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"tsid.d.mts","names":[],"sources":["../../src/tsid/tsid.ts"],"mappings":";;;KAgCY,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;AA+NF;;;;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;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"} |
@@ -1,2 +0,2 @@ | ||
| import{n as e}from"../random-Chp-Nkzi.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";const a=`0123456789ABCDEFGHJKMNPQRSTVWXYZ`,o=new Uint8Array(128).fill(255);for(let e=0;e<32;e+=1){let t=a[e],n=a.charCodeAt(e);o[n]=e,t>=`A`&&t<=`Z`&&(o[n+32]=e)}function s(e){let t=``;for(let n=0;n<13;n+=1){let r=BigInt(60-n*5),i=Number(e>>r&31n);t+=a[i]}return t}function c(e){if(e.length!==13)throw new r(`TSID_INVALID_LENGTH`,`TSID string must be 13 characters, got ${e.length}`);let t=0n;for(let n=0;n<13;n+=1){let i=e.charCodeAt(n),a=i<128?o[i]:255;if(a===255)throw new r(`TSID_INVALID_CHAR`,`Invalid TSID character: ${e[n]}`);if(n===0&&a>15)throw new r(`TSID_LEADING_CHAR_OUT_OF_RANGE`,`TSID leading character must be one of 0-9, A-F, got: ${e[n]}`);t=t<<5n|BigInt(a)}return t}const l=15778368e5,u=4095,d=(1n<<42n)-1n,f={node:void 0,msecs:-1/0,counter:0};function p(e,t,n,r){return e<<BigInt(22)|BigInt(t)<<BigInt(n)|BigInt(r)}function m(e,n,r=0){if(!n)n=new Uint8Array(8),r=0;else if(r<0||r+8>n.length)throw new t(`TSID_BUFFER_OUT_OF_BOUNDS`,`TSID byte range ${r}:${r+8-1} is out of buffer bounds`);for(let t=0;t<8;t+=1)n[r+t]=Number(e>>BigInt((7-t)*8)&255n);return n}function h(t,r,i=0){let a,o,s,c;if(t){let r=t.nodeBits??10;if(r<0||r>20)throw new n(`TSID_NODE_BITS_OUT_OF_RANGE`,`nodeBits must be between 0 and 20`);s=22-r;let i=(1<<r)-1,u=(1<<s)-1,f=t.node;if(f!==void 0){if(f<0||f>i)throw new n(`TSID_NODE_OUT_OF_RANGE`,`node must be between 0 and ${i}`);o=f}else o=e()&i;let p=t.counter;if(p!==void 0){if(p<0||p>u)throw new n(`TSID_COUNTER_OUT_OF_RANGE`,`counter must be between 0 and ${u}`);c=p}else c=e()&u;let m=t.epoch??l,h=t.msecs??Date.now(),g=BigInt(h)-BigInt(m);if(g<0n||g>d)throw new n(`TSID_TIMESTAMP_OUT_OF_RANGE`,`msecs - epoch must be between 0 and ${d}`);a=g}else{f.node===void 0&&(f.node=e()&1023),o=f.node,s=12;let t=Date.now();t>f.msecs?(f.msecs=t,f.counter=e()&u):(f.counter+=1,f.counter>u&&(f.msecs+=1,f.counter=0)),c=f.counter,a=BigInt(f.msecs)-BigInt(l)}let h=p(a,o,s,c);return r?(m(h,r,i),r):h}function g(e){return m(e)}function _(e){if(e.length!==8)throw new t(`TSID_BYTES_INVALID_LENGTH`,`TSID bytes must be exactly 8 bytes, got ${e.length}`);let n=0n;for(let t=0;t<8;t+=1)n=n<<8n|BigInt(e[t]);return n}function v(e,t=l){return t+Number(e>>BigInt(22))}function y(e){return typeof e==`bigint`&&e>=0n&&e<=b}const b=(1n<<64n)-1n,x=Object.assign(h,{toBytes:g,fromBytes:_,toString:s,fromString:c,timestamp:v,isValid:y,NIL:0n,MAX:b});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,x as tsid}; | ||
| import{n as e}from"../random-Chp-Nkzi.mjs";import{n as t,t as n}from"../validation-CTNpXm94.mjs";import{BufferError as r,InvalidInputError as i,ParseError as a,UniqueIdError as o}from"../errors.mjs";const s=`0123456789ABCDEFGHJKMNPQRSTVWXYZ`,c=new Uint8Array(128).fill(255);for(let e=0;e<32;e+=1){let t=s[e],n=s.charCodeAt(e);c[n]=e,t>=`A`&&t<=`Z`&&(c[n+32]=e)}function l(e){let t=``;for(let n=0;n<13;n+=1){let r=BigInt(60-n*5),i=Number(e>>r&31n);t+=s[i]}return t}function u(e){if(e.length!==13)throw new a(`TSID_INVALID_LENGTH`,`TSID string must be 13 characters, got ${e.length}`);let t=0n;for(let n=0;n<13;n+=1){let r=e.charCodeAt(n),i=r<128?c[r]:255;if(i===255)throw new a(`TSID_INVALID_CHAR`,`Invalid TSID character: ${e[n]}`);if(n===0&&i>15)throw new a(`TSID_LEADING_CHAR_OUT_OF_RANGE`,`TSID leading character must be one of 0-9, A-F, got: ${e[n]}`);t=t<<5n|BigInt(i)}return t}const d=15778368e5,f=4095,p=(1n<<42n)-1n,m={node:void 0,msecs:-1/0,counter:0};function h(e,t,n,r){return e<<BigInt(22)|BigInt(t)<<BigInt(n)|BigInt(r)}function g(e,t,n){for(let r=0;r<8;r+=1)t[n+r]=Number(e>>BigInt((7-r)*8)&255n)}function _(t){let r=t.nodeBits??10;if(!n(r,0,20))throw new i(`TSID_NODE_BITS_OUT_OF_RANGE`,`nodeBits must be between 0 and 20`);let a=22-r,o=(1<<r)-1,s=(1<<a)-1,c=t.node;if(c!==void 0&&!n(c,0,o))throw new i(`TSID_NODE_OUT_OF_RANGE`,`node must be between 0 and ${o}`);let l=t.counter;if(l!==void 0&&!n(l,0,s))throw new i(`TSID_COUNTER_OUT_OF_RANGE`,`counter must be between 0 and ${s}`);let u=t.epoch??d;if(!Number.isInteger(u))throw new i(`TSID_EPOCH_INVALID`,`epoch must be a finite integer`);let f=t.msecs??Date.now();if(!Number.isInteger(f))throw new i(`TSID_TIMESTAMP_INVALID`,`msecs must be a finite integer`);let m=BigInt(f)-BigInt(u);if(m<0n||m>p)throw new i(`TSID_TIMESTAMP_OUT_OF_RANGE`,`msecs - epoch must be between 0 and ${p}`);return{msecsDiff:m,node:c??e()&o,counterBits:a,counter:l??e()&s}}function v(n,i,a=0){let o,s,c,l;if(n){let e=_(n);o=e.msecsDiff,s=e.node,c=e.counterBits,l=e.counter}else{m.node===void 0&&(m.node=e()&1023),s=m.node,c=12;let t=Date.now();t>m.msecs?(m.msecs=t,m.counter=e()&f):(m.counter+=1,m.counter>f&&(m.msecs+=1,m.counter=0)),l=m.counter,o=BigInt(m.msecs)-BigInt(d)}let u=h(o,s,c,l);if(i){if(!t(i,a,8))throw new r(`TSID_BUFFER_OUT_OF_BOUNDS`,`TSID byte range ${a}:${a+8-1} is out of buffer bounds`);return g(u,i,a),i}return u}function y(e){T(e);let t=new Uint8Array(8);return g(e,t,0),t}function b(e){if(e.length!==8)throw new r(`TSID_BYTES_INVALID_LENGTH`,`TSID bytes must be exactly 8 bytes, got ${e.length}`);let t=0n;for(let n=0;n<8;n+=1)t=t<<8n|BigInt(e[n]);return t}function x(e,t=d){if(T(e),!Number.isInteger(t))throw new i(`TSID_EPOCH_INVALID`,`epoch must be a finite integer`);return t+Number(e>>BigInt(22))}function S(e){return typeof e==`bigint`&&e>=0n&&e<=w}const C=0n,w=(1n<<64n)-1n;function T(e){if(e<C||e>w)throw new i(`TSID_VALUE_OUT_OF_RANGE`,`TSID value must be between ${C} and ${w}`)}function E(e){return T(e),l(e)}const D=Object.assign(v,{toBytes:y,fromBytes:b,toString:E,fromString:u,timestamp:x,isValid:S,NIL:C,MAX:w});export{r as BufferError,i as InvalidInputError,a as ParseError,o as UniqueIdError,D as tsid}; | ||
| //# sourceMappingURL=tsid.mjs.map |
@@ -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 { 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 writeTsidBytes(value: bigint, buf?: Uint8Array, offset = 0): Uint8Array {\n if (!buf) {\n buf = new Uint8Array(TSID_BYTES)\n offset = 0\n } else if (offset < 0 || offset + TSID_BYTES > buf.length) {\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\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 return buf\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 nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS\n if (nodeBits < 0 || nodeBits > MAX_NODE_BITS) {\n throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`)\n }\n counterBits = RANDOM_BITS - nodeBits\n const nodeMask = (1 << nodeBits) - 1\n const counterMask = (1 << counterBits) - 1\n\n const optNode = options.node\n if (optNode !== undefined) {\n if (optNode < 0 || optNode > nodeMask) {\n throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`)\n }\n node = optNode\n } else {\n node = randomUint32() & nodeMask\n }\n\n const optCounter = options.counter\n if (optCounter !== undefined) {\n if (optCounter < 0 || optCounter > counterMask) {\n throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`)\n }\n counter = optCounter\n } else {\n counter = randomUint32() & counterMask\n }\n\n const epoch = options.epoch ?? TSID_EPOCH\n const msecs = options.msecs ?? Date.now()\n const diff = BigInt(msecs) - BigInt(epoch)\n if (diff < 0n || diff > 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 msecsDiff = diff\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 writeTsidBytes(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 return writeTsidBytes(id)\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 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\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: encodeTsidString,\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 (): 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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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ulid.d.mts","names":[],"sources":["../../src/ulid/ulid.ts"],"mappings":";;;KAKY,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,cAiJW,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;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"} |
@@ -1,2 +0,2 @@ | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{r as a,t as o}from"../bytes-xqWxFYsM.mjs";const s=`0123456789ABCDEFGHJKMNPQRSTVWXYZ`,c=new Uint8Array(65536);c.fill(255);for(let e=0;e<32;e+=1){let t=s.charCodeAt(e),n=s[e].toLowerCase().charCodeAt(0);c[t]=e,c[n]=e}function l(e,t){return new r(`ULID_INVALID_CHAR`,`Invalid ULID character: ${e[t]}`)}function u(){return new r(`ULID_TIMESTAMP_OVERFLOW`,`ULID timestamp exceeds 48 bits`)}function d(e){let t=c[e.charCodeAt(0)];if(t===255)throw l(e,0);if(t>7)throw u();let n=t;for(let t=1;t<10;t+=1){let r=c[e.charCodeAt(t)];if(r===255)throw l(e,t);n=n*32+r}return n}function f(e){return s[Math.floor(e/35184372088832)&31]+s[Math.floor(e/1099511627776)&31]+s[Math.floor(e/34359738368)&31]+s[Math.floor(e/1073741824)&31]+s[Math.floor(e/33554432)&31]+s[Math.floor(e/1048576)&31]+s[Math.floor(e/32768)&31]+s[Math.floor(e/1024)&31]+s[Math.floor(e/32)&31]+s[e&31]}function p(e){return s[e[0]>>3&31]+s[(e[0]<<2|e[1]>>6)&31]+s[e[1]>>1&31]+s[(e[1]<<4|e[2]>>4)&31]+s[(e[2]<<1|e[3]>>7)&31]+s[e[3]>>2&31]+s[(e[3]<<3|e[4]>>5)&31]+s[e[4]&31]+s[e[5]>>3&31]+s[(e[5]<<2|e[6]>>6)&31]+s[e[6]>>1&31]+s[(e[6]<<4|e[7]>>4)&31]+s[(e[7]<<1|e[8]>>7)&31]+s[e[8]>>2&31]+s[(e[8]<<3|e[9]>>5)&31]+s[e[9]&31]}function m(e){if(e.length!==26)throw new r(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);return d(e)}function h(e){if(e.length!==26)throw new r(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);let t=new Uint8Array(16),n=c[e.charCodeAt(0)],i=c[e.charCodeAt(1)],a=c[e.charCodeAt(2)],o=c[e.charCodeAt(3)],s=c[e.charCodeAt(4)],d=c[e.charCodeAt(5)],f=c[e.charCodeAt(6)],p=c[e.charCodeAt(7)],m=c[e.charCodeAt(8)],h=c[e.charCodeAt(9)],g=c[e.charCodeAt(10)],_=c[e.charCodeAt(11)],v=c[e.charCodeAt(12)],y=c[e.charCodeAt(13)],b=c[e.charCodeAt(14)],x=c[e.charCodeAt(15)],S=c[e.charCodeAt(16)],C=c[e.charCodeAt(17)],w=c[e.charCodeAt(18)],T=c[e.charCodeAt(19)],E=c[e.charCodeAt(20)],D=c[e.charCodeAt(21)],O=c[e.charCodeAt(22)],k=c[e.charCodeAt(23)],A=c[e.charCodeAt(24)],j=c[e.charCodeAt(25)];if((n|i|a|o|s|d|f|p|m|h|g|_|v|y|b|x|S|C|w|T|E|D|O|k|A|j)&128){for(let t=0;t<26;t+=1)if(c[e.charCodeAt(t)]===255)throw l(e,t)}if(n>7)throw u();return t[0]=n<<5|i,t[1]=a<<3|o>>2,t[2]=o<<6|s<<1|d>>4,t[3]=d<<4|f>>1,t[4]=f<<7|p<<2|m>>3,t[5]=m<<5|h,t[6]=g<<3|_>>2,t[7]=_<<6|v<<1|y>>4,t[8]=y<<4|b>>1,t[9]=b<<7|x<<2|S>>3,t[10]=S<<5|C,t[11]=w<<3|T>>2,t[12]=T<<6|E<<1|D>>4,t[13]=D<<4|O>>1,t[14]=O<<7|k<<2|A>>3,t[15]=A<<5|j,t}function g(e){if(e.length<16)throw new t(`ULID_BYTES_TOO_SHORT`,`Byte array must be at least 16 bytes`);let n=0;for(let t=0;t<6;t+=1)n=n*256+e[t];return f(n)+p(e.subarray(6,16))}const _=/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i,v={msecs:-1/0,lastRandom:new Uint8Array(10)};function y(e,n,r,i=0){if(!r)r=new Uint8Array(16),i=0;else if(i<0||i+16>r.length)throw new t(`ULID_BUFFER_OUT_OF_BOUNDS`,`ULID byte range ${i}:${i+15} is out of buffer bounds`);a(r,i,e);for(let e=0;e<10;e+=1)r[i+6+e]=n[e];return r}function b(t,r,i=0){let a,s,c=Date.now();if(t)if(a=t.msecs??c,t.random){if(t.random.length<10)throw new n(`ULID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 10 for ULID`);s=t.random}else s=e();else if(a=c,a>v.msecs)s=e(),v.msecs=a,v.lastRandom.set(s.subarray(0,10));else{if(a=v.msecs,!o(v.lastRandom))throw v.lastRandom.fill(255),new n(`ULID_RANDOM_OVERFLOW`,`ULID random component overflowed while preserving monotonic order`);s=v.lastRandom}return r?(y(a,s,r,i),r):f(a)+p(s)}function x(e){return typeof e==`string`&&_.test(e)}const S=Object.assign(b,{toBytes:e=>h(e),fromBytes:e=>g(e),timestamp:e=>m(e),isValid:x,NIL:`00000000000000000000000000`,MAX:`7ZZZZZZZZZZZZZZZZZZZZZZZZZ`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,S as ulid}; | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t,t as n}from"../validation-CTNpXm94.mjs";import{BufferError as r,InvalidInputError as i,ParseError as a,UniqueIdError as o}from"../errors.mjs";import{r as s,t as c}from"../bytes-xqWxFYsM.mjs";const l=`0123456789ABCDEFGHJKMNPQRSTVWXYZ`,u=new Uint8Array(65536);u.fill(255);for(let e=0;e<32;e+=1){let t=l.charCodeAt(e),n=l[e].toLowerCase().charCodeAt(0);u[t]=e,u[n]=e}function d(e,t){return new a(`ULID_INVALID_CHAR`,`Invalid ULID character: ${e[t]}`)}function f(){return new a(`ULID_TIMESTAMP_OVERFLOW`,`ULID timestamp exceeds 48 bits`)}function p(e){let t=u[e.charCodeAt(0)];if(t===255)throw d(e,0);if(t>7)throw f();let n=t;for(let t=1;t<10;t+=1){let r=u[e.charCodeAt(t)];if(r===255)throw d(e,t);n=n*32+r}return n}function m(e){return l[Math.floor(e/35184372088832)&31]+l[Math.floor(e/1099511627776)&31]+l[Math.floor(e/34359738368)&31]+l[Math.floor(e/1073741824)&31]+l[Math.floor(e/33554432)&31]+l[Math.floor(e/1048576)&31]+l[Math.floor(e/32768)&31]+l[Math.floor(e/1024)&31]+l[Math.floor(e/32)&31]+l[e&31]}function h(e){return l[e[0]>>3&31]+l[(e[0]<<2|e[1]>>6)&31]+l[e[1]>>1&31]+l[(e[1]<<4|e[2]>>4)&31]+l[(e[2]<<1|e[3]>>7)&31]+l[e[3]>>2&31]+l[(e[3]<<3|e[4]>>5)&31]+l[e[4]&31]+l[e[5]>>3&31]+l[(e[5]<<2|e[6]>>6)&31]+l[e[6]>>1&31]+l[(e[6]<<4|e[7]>>4)&31]+l[(e[7]<<1|e[8]>>7)&31]+l[e[8]>>2&31]+l[(e[8]<<3|e[9]>>5)&31]+l[e[9]&31]}function g(e){if(e.length!==26)throw new a(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);return p(e)}function _(e){if(e.length!==26)throw new a(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);let t=new Uint8Array(16),n=u[e.charCodeAt(0)],r=u[e.charCodeAt(1)],i=u[e.charCodeAt(2)],o=u[e.charCodeAt(3)],s=u[e.charCodeAt(4)],c=u[e.charCodeAt(5)],l=u[e.charCodeAt(6)],p=u[e.charCodeAt(7)],m=u[e.charCodeAt(8)],h=u[e.charCodeAt(9)],g=u[e.charCodeAt(10)],_=u[e.charCodeAt(11)],v=u[e.charCodeAt(12)],y=u[e.charCodeAt(13)],b=u[e.charCodeAt(14)],x=u[e.charCodeAt(15)],S=u[e.charCodeAt(16)],C=u[e.charCodeAt(17)],w=u[e.charCodeAt(18)],T=u[e.charCodeAt(19)],E=u[e.charCodeAt(20)],D=u[e.charCodeAt(21)],O=u[e.charCodeAt(22)],k=u[e.charCodeAt(23)],A=u[e.charCodeAt(24)],j=u[e.charCodeAt(25)];if((n|r|i|o|s|c|l|p|m|h|g|_|v|y|b|x|S|C|w|T|E|D|O|k|A|j)&128){for(let t=0;t<26;t+=1)if(u[e.charCodeAt(t)]===255)throw d(e,t)}if(n>7)throw f();return t[0]=n<<5|r,t[1]=i<<3|o>>2,t[2]=o<<6|s<<1|c>>4,t[3]=c<<4|l>>1,t[4]=l<<7|p<<2|m>>3,t[5]=m<<5|h,t[6]=g<<3|_>>2,t[7]=_<<6|v<<1|y>>4,t[8]=y<<4|b>>1,t[9]=b<<7|x<<2|S>>3,t[10]=S<<5|C,t[11]=w<<3|T>>2,t[12]=T<<6|E<<1|D>>4,t[13]=D<<4|O>>1,t[14]=O<<7|k<<2|A>>3,t[15]=A<<5|j,t}function v(e){if(e.length!==16)throw new r(`ULID_BYTES_INVALID_LENGTH`,`ULID bytes must be exactly 16 bytes, got ${e.length}`);let t=0;for(let n=0;n<6;n+=1)t=t*256+e[n];return m(t)+h(e.subarray(6,16))}const y=/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i,b=0xffffffffffff,x={msecs:-1/0,lastRandom:new Uint8Array(10)};function S(e,t,n,r){s(n,r,e);for(let e=0;e<10;e+=1)n[r+6+e]=t[e]}function C(e,n,i,a){if(!t(i,a,16))throw new r(`ULID_BUFFER_OUT_OF_BOUNDS`,`ULID byte range ${a}:${a+16-1} is out of buffer bounds`);S(e,n,i,a)}function w(t,r,a=0){let o,s;if(t){let r=t.msecs;if(r!==void 0&&!n(r,0,b))throw new i(`ULID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between 0 and ${b}`);o=r??Date.now();let a=t.random;if(a){if(a.length<10)throw new i(`ULID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 10 for ULID`);s=a}else s=e()}else if(o=Date.now(),o>x.msecs)s=e(),x.msecs=o,x.lastRandom.set(s.subarray(0,10));else{if(o=x.msecs,!c(x.lastRandom))throw x.lastRandom.fill(255),new i(`ULID_RANDOM_OVERFLOW`,`ULID random component overflowed while preserving monotonic order`);s=x.lastRandom}return r?(C(o,s,r,a),r):m(o)+h(s)}function T(e){return typeof e==`string`&&y.test(e)}const E=Object.assign(w,{toBytes:e=>_(e),fromBytes:e=>v(e),timestamp:e=>g(e),isValid:T,NIL:`00000000000000000000000000`,MAX:`7ZZZZZZZZZZZZZZZZZZZZZZZZZ`});export{r as BufferError,i as InvalidInputError,a as ParseError,o as UniqueIdError,E as ulid}; | ||
| //# sourceMappingURL=ulid.mjs.map |
@@ -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_TOO_SHORT', 'Byte array must be at least 16 bytes')\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 { 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\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(10),\n}\n\nfunction ulidBytes(time: number, random: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {\n if (!buf) {\n buf = new Uint8Array(16)\n offset = 0\n } else if (offset < 0 || offset + 16 > buf.length) {\n throw new BufferError(\n 'ULID_BUFFER_OUT_OF_BOUNDS',\n `ULID byte range ${offset}:${offset + 15} is out of buffer bounds`,\n )\n }\n\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 < 10; i += 1) {\n buf[offset + 6 + i] = random[i]\n }\n\n return buf\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 const defaultTime = Date.now()\n\n if (options) {\n // Explicit options provided - use them directly without monotonic state\n time = options.msecs ?? defaultTime\n if (options.random) {\n if (options.random.length < 10) {\n throw new InvalidInputError('ULID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 10 for ULID')\n }\n random = options.random\n } else {\n random = rng()\n }\n } else {\n time = defaultTime\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, 10))\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 ulidBytes(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 (): 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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"} |
@@ -7,3 +7,2 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| * 16 bytes of random data to use for UUID generation. | ||
| * Note: Bytes at index 6 and 8 will be modified in-place to set version/variant bits. | ||
| */ | ||
@@ -10,0 +9,0 @@ random?: Uint8Array; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v4.d.mts","names":[],"sources":["../../src/uuid/v4.ts"],"mappings":";;;KAMY,aAAA;;;AAAZ;;EAKE,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;;;;;;;;;;;;;;;;;;;;;;;cAsFW,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;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"} |
@@ -1,2 +0,2 @@ | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a,t as o}from"../uuid-BBjwHv7J.mjs";const s=globalThis.crypto.randomUUID.bind(globalThis.crypto),c=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function l(e,r,i=0){if(e.length<16)throw new n(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);if(e[6]=e[6]&15|64,e[8]=e[8]&63|128,!r)return e;if(i<0||i+16>r.length)throw new t(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${i}:${i+15} is out of buffer bounds`);for(let t=0;t<16;t+=1)r[i+t]=e[t];return r}function u(e,t,n){return!t&&!e?s():d(e,t,n)}function d(t,n,r){let i=l(t?.random??e(),n,r);return n??o(i)}function f(e){return typeof e==`string`&&c.test(e)}const p=Object.assign(u,{toBytes:a,fromBytes:o,isValid:f,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,p as uuidv4}; | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o,r as s,t as c}from"../uuid-CnQIYoQi.mjs";const l=globalThis.crypto.randomUUID.bind(globalThis.crypto),u=new Uint8Array(16),d=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function f(e,t,n){for(let r=0;r<16;r+=1)t[n+r]=e[r];t[n+6]=t[n+6]&15|64,t[n+8]=t[n+8]&63|128}function p(e,t,n){return!t&&!e?l():m(e,t,n)}function m(i,a,s){let c=i?.random;if(c&&c.length<16)throw new r(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);let l=a?s??0:0;if(a&&!t(a,l,16))throw new n(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${l}:${l+16-1} is out of buffer bounds`);let d=a??u;return f(c??e(),d,l),a??o(d)}function h(e){return typeof e==`string`&&d.test(e)}const g=Object.assign(p,{toBytes:s,fromBytes:c,isValid:h,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,g as uuidv4}; | ||
| //# sourceMappingURL=v4.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v4.mjs","names":[],"sources":["../../src/uuid/v4.ts"],"sourcesContent":["import { rng } from '../common/random'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { formatUuid, parseUuid } from './common/uuid'\n\nconst randomUUID = /*@__PURE__*/ globalThis.crypto.randomUUID.bind(globalThis.crypto)\n\nexport type UuidV4Options = {\n /**\n * 16 bytes of random data to use for UUID generation.\n * Note: Bytes at index 6 and 8 will be modified in-place to set version/variant bits.\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 v4Bytes(rnds: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {\n if (rnds.length < 16) {\n throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16')\n }\n\n // Set RFC 4122 version (4) and variant (10xx) bits.\n // Note: This modifies the input array in-place.\n rnds[6] = (rnds[6] & 0x0f) | 0x40\n rnds[8] = (rnds[8] & 0x3f) | 0x80\n\n if (!buf) {\n // No output buffer provided - return the modified random bytes directly\n return rnds\n }\n\n if (offset < 0 || offset + 16 > buf.length) {\n throw new BufferError(\n 'UUID_BUFFER_OUT_OF_BOUNDS',\n `UUID byte range ${offset}:${offset + 15} is out of buffer bounds`,\n )\n }\n\n // Copy 16 UUID bytes into the provided buffer slice.\n for (let i = 0; i < 16; i += 1) {\n buf[offset + i] = rnds[i]\n }\n\n return buf\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 bytes = v4Bytes(options?.random ?? rng(), buf, offset)\n return buf ?? formatUuid(bytes)\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":"iMAIA,MAAM,EAA2B,WAAW,OAAO,WAAW,KAAK,WAAW,MAAM,EAuB9E,EAAgB,yEAEtB,SAAS,EAAQ,EAAkB,EAAkB,EAAS,EAAe,CAC3E,GAAI,EAAK,OAAS,GAChB,MAAM,IAAI,EAAkB,8BAA+B,mCAAmC,EAQhG,GAHA,EAAK,GAAM,EAAK,GAAK,GAAQ,GAC7B,EAAK,GAAM,EAAK,GAAK,GAAQ,IAEzB,CAAC,EAEH,OAAO,EAGT,GAAI,EAAS,GAAK,EAAS,GAAK,EAAI,OAClC,MAAM,IAAI,EACR,4BACA,mBAAmB,EAAO,GAAG,EAAS,GAAG,yBAC3C,EAIF,IAAK,IAAI,EAAI,EAAG,EAAI,GAAI,GAAK,EAC3B,EAAI,EAAS,GAAK,EAAK,GAGzB,OAAO,CACT,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,EAAQ,EAAQ,GAAS,QAAU,EAAI,EAAG,EAAK,CAAM,EAC3D,OAAO,GAAO,EAAW,CAAK,CAChC,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 (): 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"} |
@@ -11,2 +11,5 @@ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs"; | ||
| msecs?: number; | ||
| /** | ||
| * Unsigned 32-bit sequence value. | ||
| */ | ||
| seq?: number; | ||
@@ -13,0 +16,0 @@ }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v7.d.mts","names":[],"sources":["../../src/uuid/v7.ts"],"mappings":";;;KAIY,aAAA;;;AAAZ;;EAKE,MAAA,GAAS,UAAA;EACT,KAAA;EACA,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;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmJW,MAAA,EAAQ,MAAA"} | ||
| {"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"} |
@@ -1,2 +0,2 @@ | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a,t as o}from"../uuid-BBjwHv7J.mjs";const s=/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,c=new Uint8Array(16),l={msecs:-1/0,seq:0};function u(e,r,i,a,o=0){if(e.length<16)throw new n(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);if(o<0||o+16>a.length)throw new t(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${o}:${o+15} is out of buffer bounds`);return r??=Date.now(),i??=e[6]<<23|e[7]<<16|e[8]<<8|e[9],a[o++]=r/1099511627776&255,a[o++]=r/4294967296&255,a[o++]=r/16777216&255,a[o++]=r/65536&255,a[o++]=r/256&255,a[o++]=r&255,a[o++]=112|i>>>28&15,a[o++]=i>>>20&255,a[o++]=128|i>>>14&63,a[o++]=i>>>6&255,a[o++]=i<<2&255|e[10]&3,a[o++]=e[11],a[o++]=e[12],a[o++]=e[13],a[o++]=e[14],a[o++]=e[15],a}function d(t,n,r){let i;if(t)i=u(t.random??e(),t.msecs,t.seq,n??c,n?r:0);else{let t=Date.now(),a=e();t>l.msecs?(l.seq=a[6]<<23|a[7]<<16|a[8]<<8|a[9],l.msecs=t):(l.seq=l.seq+1|0,l.seq<0&&(l.seq=0,l.msecs++)),i=u(a,l.msecs,l.seq,n??c,n?r:0)}return n?i:o(i)}function f(e){let t=a(e),n=0;for(let e=0;e<6;e+=1)n=n*256+t[e];return n}function p(e){return typeof e==`string`&&s.test(e)}const m=Object.assign(d,{toBytes:a,fromBytes:o,timestamp:f,isValid:p,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,m as uuidv7}; | ||
| import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t,t as n}from"../validation-CTNpXm94.mjs";import{BufferError as r,InvalidInputError as i,ParseError as a,UniqueIdError as o}from"../errors.mjs";import{n as s,r as c,t as l}from"../uuid-CnQIYoQi.mjs";const u=/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,d=0xffffffffffff,f=4294967295,p=new Uint8Array(16),m={msecs:-1/0,seq:0};function h(e,t,n,r,i){r[i++]=t/1099511627776&255,r[i++]=t/4294967296&255,r[i++]=t/16777216&255,r[i++]=t/65536&255,r[i++]=t/256&255,r[i++]=t&255,r[i++]=112|n>>>28&15,r[i++]=n>>>20&255,r[i++]=128|n>>>14&63,r[i++]=n>>>6&255,r[i++]=n<<2&255|e[10]&3,r[i++]=e[11],r[i++]=e[12],r[i++]=e[13],r[i++]=e[14],r[i++]=e[15]}function g(e,n,i,a,o){if(!t(a,o,16))throw new r(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${o}:${o+16-1} is out of buffer bounds`);h(e,n,i,a,o)}function _(t,r,a=0){let o=t.msecs;if(o!==void 0&&!n(o,0,d))throw new i(`UUID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between 0 and ${d}`);let c=t.seq;if(c!==void 0&&!n(c,0,f))throw new i(`UUID_SEQUENCE_OUT_OF_RANGE`,`Sequence must be an integer between 0 and ${f}`);let l=t.random;if(l&&l.length<16)throw new i(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);let u=l??e(),m=o??Date.now(),_=c??u[6]<<23|u[7]<<16|u[8]<<8|u[9];return r?(g(u,m,_,r,a),r):(h(u,m,_,p,0),s(p))}function v(t,n,r){if(t)return _(t,n,r);let i=Date.now(),a=e();return i>m.msecs?(m.seq=a[6]<<23|a[7]<<16|a[8]<<8|a[9],m.msecs=i):(m.seq=m.seq+1|0,m.seq<0&&(m.seq=0,m.msecs++)),n?(g(a,m.msecs,m.seq,n,r??0),n):(h(a,m.msecs,m.seq,p,0),s(p))}function y(e){let t=c(e),n=0;for(let e=0;e<6;e+=1)n=n*256+t[e];return n}function b(e){return typeof e==`string`&&u.test(e)}const x=Object.assign(v,{toBytes:c,fromBytes:l,timestamp:y,isValid:b,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{r as BufferError,i as InvalidInputError,a as ParseError,o as UniqueIdError,x as uuidv7}; | ||
| //# sourceMappingURL=v7.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"v7.mjs","names":[],"sources":["../../src/uuid/v7.ts"],"sourcesContent":["import { rng } from '../common/random'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { formatUuid, 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 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\n\n// Reusable buffer for string output path - avoids allocation per call.\n// Safe because bytes are consumed synchronously by formatUuid().\nconst reusableBuf = new Uint8Array(16)\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 v7Bytes(\n rnds: Uint8Array,\n msecs: number | undefined,\n seq: number | undefined,\n buf: Uint8Array,\n offset = 0,\n): Uint8Array {\n if (rnds.length < 16) {\n throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16')\n }\n\n if (offset < 0 || offset + 16 > buf.length) {\n throw new BufferError(\n 'UUID_BUFFER_OUT_OF_BOUNDS',\n `UUID byte range ${offset}:${offset + 15} is out of buffer bounds`,\n )\n }\n\n msecs ??= Date.now()\n // Derive a 31-bit sequence if not provided by the caller.\n // Uses the same formula as the hot path for consistency.\n seq ??= (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]\n\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 return buf\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 let bytes: Uint8Array\n\n if (options) {\n bytes = v7Bytes(options.random ?? rng(), options.msecs, options.seq, buf ?? reusableBuf, buf ? offset : 0)\n } else {\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 bytes = v7Bytes(rnds, state.msecs, state.seq, buf ?? reusableBuf, buf ? offset : 0)\n }\n\n return buf ? (bytes as TBuf) : formatUuid(bytes)\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":"iMA4BA,MAAM,EAAgB,yEAIhB,EAAc,IAAI,WAAW,EAAE,EAe/B,EAAiB,CAAE,MAAO,KAAW,IAAK,CAAE,EAElD,SAAS,EACP,EACA,EACA,EACA,EACA,EAAS,EACG,CACZ,GAAI,EAAK,OAAS,GAChB,MAAM,IAAI,EAAkB,8BAA+B,mCAAmC,EAGhG,GAAI,EAAS,GAAK,EAAS,GAAK,EAAI,OAClC,MAAM,IAAI,EACR,4BACA,mBAAmB,EAAO,GAAG,EAAS,GAAG,yBAC3C,EA8BF,MA3BA,KAAU,KAAK,IAAI,EAGnB,IAAS,EAAK,IAAM,GAAO,EAAK,IAAM,GAAO,EAAK,IAAM,EAAK,EAAK,GAIlE,EAAI,KAAa,EAAQ,cAAiB,IAC1C,EAAI,KAAa,EAAQ,WAAe,IACxC,EAAI,KAAa,EAAQ,SAAa,IACtC,EAAI,KAAa,EAAQ,MAAW,IACpC,EAAI,KAAa,EAAQ,IAAS,IAClC,EAAI,KAAY,EAAQ,IAGxB,EAAI,KAAY,IAAS,IAAQ,GAAM,GACvC,EAAI,KAAa,IAAQ,GAAM,IAC/B,EAAI,KAAY,IAAS,IAAQ,GAAM,GACvC,EAAI,KAAa,IAAQ,EAAK,IAE9B,EAAI,KAAc,GAAO,EAAK,IAAS,EAAK,IAAM,EAClD,EAAI,KAAY,EAAK,IACrB,EAAI,KAAY,EAAK,IACrB,EAAI,KAAY,EAAK,IACrB,EAAI,KAAY,EAAK,IACrB,EAAI,KAAY,EAAK,IAEd,CACT,CAUA,SAAS,EAAyC,EAAyB,EAAY,EAAgC,CACrH,IAAI,EAEJ,GAAI,EACF,EAAQ,EAAQ,EAAQ,QAAU,EAAI,EAAG,EAAQ,MAAO,EAAQ,IAAK,GAAO,EAAa,EAAM,EAAS,CAAC,MACpG,CAEL,IAAM,EAAM,KAAK,IAAI,EACf,EAAO,EAAI,EAGb,EAAM,EAAM,OACd,EAAM,IAAO,EAAK,IAAM,GAAO,EAAK,IAAM,GAAO,EAAK,IAAM,EAAK,EAAK,GACtE,EAAM,MAAQ,IAEd,EAAM,IAAO,EAAM,IAAM,EAAK,EAC1B,EAAM,IAAM,IACd,EAAM,IAAM,EACZ,EAAM,UAIV,EAAQ,EAAQ,EAAM,EAAM,MAAO,EAAM,IAAK,GAAO,EAAa,EAAM,EAAS,CAAC,CACpF,CAEA,OAAO,EAAO,EAAiB,EAAW,CAAK,CACjD,CAEA,SAAS,EAAU,EAAoB,CACrC,IAAM,EAAQ,EAAU,CAAE,EACtB,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,GAAK,EAC1B,EAAQ,EAAQ,IAAM,EAAM,GAE9B,OAAO,CACT,CAEA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAc,KAAK,CAAE,CACxD,CA4BA,MAAa,EAAiB,OAAO,OAAO,EAAI,CAC9C,QAAS,EACT,UAAW,EACX,YACA,UACA,IAAK,uCACL,IAAK,sCACP,CAAC"} | ||
| {"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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"} |
+1
-1
| { | ||
| "private": false, | ||
| "name": "uniku", | ||
| "version": "0.3.0", | ||
| "version": "0.3.1", | ||
| "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -138,10 +138,14 @@ import { sha3_512 } from '@noble/hashes/sha3.js' | ||
| function cuid2Fn(options?: Cuid2Options): string { | ||
| const length = options?.length ?? DEFAULT_LENGTH | ||
| const requestedLength = options?.length | ||
| if (length < MIN_LENGTH || length > MAX_LENGTH) { | ||
| if ( | ||
| requestedLength !== undefined && | ||
| (!Number.isInteger(requestedLength) || requestedLength < MIN_LENGTH || requestedLength > MAX_LENGTH) | ||
| ) { | ||
| throw new InvalidInputError( | ||
| 'CUID2_LENGTH_OUT_OF_RANGE', | ||
| `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`, | ||
| `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${requestedLength}`, | ||
| ) | ||
| } | ||
| const length = requestedLength ?? DEFAULT_LENGTH | ||
@@ -148,0 +152,0 @@ const random = getRandomFn(options?.random) |
+19
-17
| import { writeTimestamp32 } from '../common/bytes' | ||
| import { rng } from '../common/random' | ||
| import { isWritableRange } from '../common/validation' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
@@ -61,15 +62,5 @@ import { decodeBase62, encodeBase62 } from './base62' | ||
| /** | ||
| * Write KSUID bytes to a buffer. | ||
| * Write already-validated KSUID fields to a buffer. | ||
| */ | ||
| function ksuidBytes(timestamp: number, payload: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array { | ||
| if (!buf) { | ||
| buf = new Uint8Array(KSUID_BYTES) | ||
| offset = 0 | ||
| } else if (offset < 0 || offset + KSUID_BYTES > buf.length) { | ||
| throw new BufferError( | ||
| 'KSUID_BUFFER_OUT_OF_BOUNDS', | ||
| `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| function writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void { | ||
| // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3 | ||
@@ -82,4 +73,2 @@ writeTimestamp32(buf, offset, timestamp) | ||
| } | ||
| return buf | ||
| } | ||
@@ -108,3 +97,3 @@ | ||
| if (secs !== undefined) { | ||
| if (secs < KSUID_EPOCH) { | ||
| if (!Number.isInteger(secs) || secs < KSUID_EPOCH) { | ||
| throw new InvalidInputError('KSUID_TIMESTAMP_TOO_LOW', 'Timestamp must be >= KSUID epoch') | ||
@@ -132,7 +121,14 @@ } | ||
| if (buf) { | ||
| ksuidBytes(timestamp, payload, buf, offset) | ||
| if (!isWritableRange(buf, offset, KSUID_BYTES)) { | ||
| throw new BufferError( | ||
| 'KSUID_BUFFER_OUT_OF_BOUNDS', | ||
| `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| writeKsuidBytesUnchecked(timestamp, payload, buf, offset) | ||
| return buf | ||
| } | ||
| const bytes = ksuidBytes(timestamp, payload) | ||
| const bytes = new Uint8Array(KSUID_BYTES) | ||
| writeKsuidBytesUnchecked(timestamp, payload, bytes, 0) | ||
@@ -157,2 +153,8 @@ // String mode: create bytes then encode to Base62 | ||
| function fromBytes(bytes: Uint8Array): string { | ||
| if (bytes.length !== KSUID_BYTES) { | ||
| throw new BufferError( | ||
| 'KSUID_BYTES_INVALID_LENGTH', | ||
| `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`, | ||
| ) | ||
| } | ||
| return encodeBase62(bytes) | ||
@@ -159,0 +161,0 @@ } |
+25
-17
| import { writeTimestamp32 } from '../common/bytes' | ||
| import { randomBytes, randomUint32 } from '../common/random' | ||
| import { isIntegerInRange, isWritableRange } from '../common/validation' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
@@ -88,13 +89,9 @@ import { decodeObjectIdHex, encodeObjectIdHex } from './hex' | ||
| function objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?: Uint8Array, offset = 0): Uint8Array { | ||
| if (!buf) { | ||
| buf = new Uint8Array(OBJECTID_BYTES) | ||
| offset = 0 | ||
| } else if (offset < 0 || offset + OBJECTID_BYTES > buf.length) { | ||
| throw new BufferError( | ||
| 'OBJECTID_BUFFER_OUT_OF_BOUNDS', | ||
| `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| function writeObjectIdBytesUnchecked( | ||
| secs: number, | ||
| random: Uint8Array, | ||
| counter: number, | ||
| buf: Uint8Array, | ||
| offset: number, | ||
| ): void { | ||
| // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3 | ||
@@ -112,4 +109,2 @@ writeTimestamp32(buf, offset, secs) | ||
| buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff | ||
| return buf | ||
| } | ||
@@ -148,3 +143,3 @@ | ||
| const optSecs = options.secs | ||
| if (optSecs !== undefined && (optSecs < 0 || optSecs > MAX_SECS)) { | ||
| if (optSecs !== undefined && !isIntegerInRange(optSecs, 0, MAX_SECS)) { | ||
| throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`) | ||
@@ -154,3 +149,3 @@ } | ||
| const optCounter = options.counter | ||
| if (optCounter !== undefined && (optCounter < 0 || optCounter > MAX_COUNTER)) { | ||
| if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) { | ||
| throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`) | ||
@@ -190,7 +185,14 @@ } | ||
| if (buf) { | ||
| objectIdBytes(secs, random, counter, buf, offset) | ||
| if (!isWritableRange(buf, offset, OBJECTID_BYTES)) { | ||
| throw new BufferError( | ||
| 'OBJECTID_BUFFER_OUT_OF_BOUNDS', | ||
| `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| writeObjectIdBytesUnchecked(secs, random, counter, buf, offset) | ||
| return buf | ||
| } | ||
| const bytes = objectIdBytes(secs, random, counter) | ||
| const bytes = new Uint8Array(OBJECTID_BYTES) | ||
| writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0) | ||
| return encodeObjectIdHex(bytes) | ||
@@ -210,2 +212,8 @@ } | ||
| function fromBytes(bytes: Uint8Array): string { | ||
| if (bytes.length !== OBJECTID_BYTES) { | ||
| throw new BufferError( | ||
| 'OBJECTID_BYTES_INVALID_LENGTH', | ||
| `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`, | ||
| ) | ||
| } | ||
| return encodeObjectIdHex(bytes) | ||
@@ -212,0 +220,0 @@ } |
+83
-53
| import { randomUint32 } from '../common/random' | ||
| import { isIntegerInRange, isWritableRange } from '../common/validation' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
@@ -105,20 +106,58 @@ import { decodeTsidString, encodeTsidString } from './crockford64' | ||
| function writeTsidBytes(value: bigint, buf?: Uint8Array, offset = 0): Uint8Array { | ||
| if (!buf) { | ||
| buf = new Uint8Array(TSID_BYTES) | ||
| offset = 0 | ||
| } else if (offset < 0 || offset + TSID_BYTES > buf.length) { | ||
| throw new BufferError( | ||
| 'TSID_BUFFER_OUT_OF_BOUNDS', | ||
| `TSID byte range ${offset}:${offset + TSID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| function writeTsidBytesUnchecked(value: bigint, buf: Uint8Array, offset: number): void { | ||
| for (let i = 0; i < TSID_BYTES; i += 1) { | ||
| buf[offset + i] = Number((value >> BigInt((TSID_BYTES - 1 - i) * 8)) & 0xffn) | ||
| } | ||
| } | ||
| return buf | ||
| type ResolvedTsidOptions = { | ||
| msecsDiff: bigint | ||
| node: number | ||
| counterBits: number | ||
| counter: number | ||
| } | ||
| function resolveTsidOptions(options: TsidOptions): ResolvedTsidOptions { | ||
| const nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS | ||
| if (!isIntegerInRange(nodeBits, 0, MAX_NODE_BITS)) { | ||
| throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`) | ||
| } | ||
| const counterBits = RANDOM_BITS - nodeBits | ||
| const nodeMask = (1 << nodeBits) - 1 | ||
| const counterMask = (1 << counterBits) - 1 | ||
| const optNode = options.node | ||
| if (optNode !== undefined && !isIntegerInRange(optNode, 0, nodeMask)) { | ||
| throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`) | ||
| } | ||
| const optCounter = options.counter | ||
| if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, counterMask)) { | ||
| throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`) | ||
| } | ||
| const epoch = options.epoch ?? TSID_EPOCH | ||
| if (!Number.isInteger(epoch)) { | ||
| throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer') | ||
| } | ||
| const msecs = options.msecs ?? Date.now() | ||
| if (!Number.isInteger(msecs)) { | ||
| throw new InvalidInputError('TSID_TIMESTAMP_INVALID', 'msecs must be a finite integer') | ||
| } | ||
| const msecsDiff = BigInt(msecs) - BigInt(epoch) | ||
| if (msecsDiff < 0n || msecsDiff > MAX_TIMESTAMP_DIFF) { | ||
| throw new InvalidInputError( | ||
| 'TSID_TIMESTAMP_OUT_OF_RANGE', | ||
| `msecs - epoch must be between 0 and ${MAX_TIMESTAMP_DIFF}`, | ||
| ) | ||
| } | ||
| return { | ||
| msecsDiff, | ||
| node: optNode ?? randomUint32() & nodeMask, | ||
| counterBits, | ||
| counter: optCounter ?? randomUint32() & counterMask, | ||
| } | ||
| } | ||
| /* | ||
@@ -143,40 +182,7 @@ * Overload: no buffer => return a TSID bigint. | ||
| if (options) { | ||
| const nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS | ||
| if (nodeBits < 0 || nodeBits > MAX_NODE_BITS) { | ||
| throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`) | ||
| } | ||
| counterBits = RANDOM_BITS - nodeBits | ||
| const nodeMask = (1 << nodeBits) - 1 | ||
| const counterMask = (1 << counterBits) - 1 | ||
| const optNode = options.node | ||
| if (optNode !== undefined) { | ||
| if (optNode < 0 || optNode > nodeMask) { | ||
| throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`) | ||
| } | ||
| node = optNode | ||
| } else { | ||
| node = randomUint32() & nodeMask | ||
| } | ||
| const optCounter = options.counter | ||
| if (optCounter !== undefined) { | ||
| if (optCounter < 0 || optCounter > counterMask) { | ||
| throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`) | ||
| } | ||
| counter = optCounter | ||
| } else { | ||
| counter = randomUint32() & counterMask | ||
| } | ||
| const epoch = options.epoch ?? TSID_EPOCH | ||
| const msecs = options.msecs ?? Date.now() | ||
| const diff = BigInt(msecs) - BigInt(epoch) | ||
| if (diff < 0n || diff > MAX_TIMESTAMP_DIFF) { | ||
| throw new InvalidInputError( | ||
| 'TSID_TIMESTAMP_OUT_OF_RANGE', | ||
| `msecs - epoch must be between 0 and ${MAX_TIMESTAMP_DIFF}`, | ||
| ) | ||
| } | ||
| msecsDiff = diff | ||
| const resolved = resolveTsidOptions(options) | ||
| msecsDiff = resolved.msecsDiff | ||
| node = resolved.node | ||
| counterBits = resolved.counterBits | ||
| counter = resolved.counter | ||
| } else { | ||
@@ -217,3 +223,9 @@ // Lazily initialize the persistent node ID on first no-option call. | ||
| if (buf) { | ||
| writeTsidBytes(packed, buf, offset) | ||
| if (!isWritableRange(buf, offset, TSID_BYTES)) { | ||
| throw new BufferError( | ||
| 'TSID_BUFFER_OUT_OF_BOUNDS', | ||
| `TSID byte range ${offset}:${offset + TSID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| writeTsidBytesUnchecked(packed, buf, offset) | ||
| return buf | ||
@@ -229,3 +241,6 @@ } | ||
| function toBytes(id: bigint): Uint8Array { | ||
| return writeTsidBytes(id) | ||
| assertValidTsid(id) | ||
| const bytes = new Uint8Array(TSID_BYTES) | ||
| writeTsidBytesUnchecked(id, bytes, 0) | ||
| return bytes | ||
| } | ||
@@ -257,2 +272,6 @@ | ||
| function timestamp(id: bigint, epoch: number = TSID_EPOCH): number { | ||
| assertValidTsid(id) | ||
| if (!Number.isInteger(epoch)) { | ||
| throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer') | ||
| } | ||
| return epoch + Number(id >> BigInt(RANDOM_BITS)) | ||
@@ -271,2 +290,13 @@ } | ||
| function assertValidTsid(id: bigint): void { | ||
| if (id < NIL || id > MAX) { | ||
| throw new InvalidInputError('TSID_VALUE_OUT_OF_RANGE', `TSID value must be between ${NIL} and ${MAX}`) | ||
| } | ||
| } | ||
| function toString(id: bigint): string { | ||
| assertValidTsid(id) | ||
| return encodeTsidString(id) | ||
| } | ||
| /** | ||
@@ -308,3 +338,3 @@ * Generate a TSID bigint or write the bytes into a buffer. | ||
| fromBytes, | ||
| toString: encodeTsidString, | ||
| toString, | ||
| fromString: decodeTsidString, | ||
@@ -311,0 +341,0 @@ timestamp, |
@@ -234,4 +234,4 @@ /** | ||
| export function bytesToUlid(bytes: Uint8Array): string { | ||
| if (bytes.length < 16) { | ||
| throw new BufferError('ULID_BYTES_TOO_SHORT', 'Byte array must be at least 16 bytes') | ||
| if (bytes.length !== 16) { | ||
| throw new BufferError('ULID_BYTES_INVALID_LENGTH', `ULID bytes must be exactly 16 bytes, got ${bytes.length}`) | ||
| } | ||
@@ -238,0 +238,0 @@ |
+35
-24
| import { incrementBytesInPlace, writeTimestamp48 } from '../common/bytes' | ||
| import { rng } from '../common/random' | ||
| import { isIntegerInRange, isWritableRange } from '../common/validation' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
@@ -35,2 +36,5 @@ import { bytesToUlid, decodeToBytes, decodeUlidTime, encodeRandom, encodeTime } from './crockford' | ||
| const ULID_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i | ||
| const ULID_BYTES = 16 | ||
| const RANDOM_BYTES = 10 | ||
| const MAX_MSECS = 0xffffffffffff | ||
@@ -52,16 +56,6 @@ type UlidState = { | ||
| msecs: -Infinity, | ||
| lastRandom: new Uint8Array(10), | ||
| lastRandom: new Uint8Array(RANDOM_BYTES), | ||
| } | ||
| function ulidBytes(time: number, random: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array { | ||
| if (!buf) { | ||
| buf = new Uint8Array(16) | ||
| offset = 0 | ||
| } else if (offset < 0 || offset + 16 > buf.length) { | ||
| throw new BufferError( | ||
| 'ULID_BUFFER_OUT_OF_BOUNDS', | ||
| `ULID byte range ${offset}:${offset + 15} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| function writeUlidBytesUnchecked(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void { | ||
| // Timestamp (48-bit big-endian milliseconds since Unix epoch) -> bytes 0-5 | ||
@@ -71,7 +65,15 @@ writeTimestamp48(buf, offset, time) | ||
| // Random (80 bits) -> bytes 6-15 | ||
| for (let i = 0; i < 10; i += 1) { | ||
| for (let i = 0; i < RANDOM_BYTES; i += 1) { | ||
| buf[offset + 6 + i] = random[i] | ||
| } | ||
| } | ||
| return buf | ||
| function writeUlidBytes(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void { | ||
| if (!isWritableRange(buf, offset, ULID_BYTES)) { | ||
| throw new BufferError( | ||
| 'ULID_BUFFER_OUT_OF_BOUNDS', | ||
| `ULID byte range ${offset}:${offset + ULID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| writeUlidBytesUnchecked(time, random, buf, offset) | ||
| } | ||
@@ -103,12 +105,21 @@ | ||
| */ | ||
| const defaultTime = Date.now() | ||
| if (options) { | ||
| // Explicit options provided - use them directly without monotonic state | ||
| time = options.msecs ?? defaultTime | ||
| if (options.random) { | ||
| if (options.random.length < 10) { | ||
| throw new InvalidInputError('ULID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 10 for ULID') | ||
| const optMsecs = options.msecs | ||
| if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) { | ||
| throw new InvalidInputError( | ||
| 'ULID_TIMESTAMP_OUT_OF_RANGE', | ||
| `Timestamp must be an integer between 0 and ${MAX_MSECS}`, | ||
| ) | ||
| } | ||
| time = optMsecs ?? Date.now() | ||
| const optRandom = options.random | ||
| if (optRandom) { | ||
| if (optRandom.length < RANDOM_BYTES) { | ||
| throw new InvalidInputError( | ||
| 'ULID_RANDOM_BYTES_TOO_SHORT', | ||
| `Random bytes length must be >= ${RANDOM_BYTES} for ULID`, | ||
| ) | ||
| } | ||
| random = options.random | ||
| random = optRandom | ||
| } else { | ||
@@ -118,3 +129,3 @@ random = rng() | ||
| } else { | ||
| time = defaultTime | ||
| time = Date.now() | ||
@@ -125,3 +136,3 @@ if (time > state.msecs) { | ||
| state.msecs = time | ||
| state.lastRandom.set(random.subarray(0, 10)) | ||
| state.lastRandom.set(random.subarray(0, RANDOM_BYTES)) | ||
| } else { | ||
@@ -142,3 +153,3 @@ // Same millisecond or clock rollback: preserve last timestamp and increment random portion. | ||
| if (buf) { | ||
| ulidBytes(time, random, buf, offset) | ||
| writeUlidBytes(time, random, buf, offset) | ||
| return buf | ||
@@ -145,0 +156,0 @@ } |
@@ -1,2 +0,2 @@ | ||
| import { ParseError } from '../../errors' | ||
| import { BufferError, ParseError } from '../../errors' | ||
| import { hexValue } from './hex' | ||
@@ -96,2 +96,13 @@ | ||
| export function formatUuid(bytes: Uint8Array): string { | ||
| if (bytes.length !== UUID_BYTE_LENGTH) { | ||
| throw new BufferError( | ||
| 'UUID_BYTES_INVALID_LENGTH', | ||
| `UUID bytes must be exactly ${UUID_BYTE_LENGTH} bytes, got ${bytes.length}`, | ||
| ) | ||
| } | ||
| return formatUuidUnchecked(bytes) | ||
| } | ||
| export function formatUuidUnchecked(bytes: Uint8Array): string { | ||
| // Direct string concatenation - optimized for V8's string builder. | ||
@@ -98,0 +109,0 @@ // This approach avoids loop overhead and intermediate allocations. |
+25
-28
| import { rng } from '../common/random' | ||
| import { isWritableRange } from '../common/validation' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
| import { formatUuid, parseUuid } from './common/uuid' | ||
| import { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid' | ||
| const randomUUID = /*@__PURE__*/ globalThis.crypto.randomUUID.bind(globalThis.crypto) | ||
| const UUID_BYTES = 16 | ||
| const reusableBuf = new Uint8Array(UUID_BYTES) | ||
@@ -10,3 +13,2 @@ export type UuidV4Options = { | ||
| * 16 bytes of random data to use for UUID generation. | ||
| * Note: Bytes at index 6 and 8 will be modified in-place to set version/variant bits. | ||
| */ | ||
@@ -31,30 +33,11 @@ random?: Uint8Array | ||
| function v4Bytes(rnds: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array { | ||
| if (rnds.length < 16) { | ||
| throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16') | ||
| } | ||
| // Set RFC 4122 version (4) and variant (10xx) bits. | ||
| // Note: This modifies the input array in-place. | ||
| rnds[6] = (rnds[6] & 0x0f) | 0x40 | ||
| rnds[8] = (rnds[8] & 0x3f) | 0x80 | ||
| if (!buf) { | ||
| // No output buffer provided - return the modified random bytes directly | ||
| return rnds | ||
| } | ||
| if (offset < 0 || offset + 16 > buf.length) { | ||
| throw new BufferError( | ||
| 'UUID_BUFFER_OUT_OF_BOUNDS', | ||
| `UUID byte range ${offset}:${offset + 15} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| function writeV4BytesUnchecked(rnds: Uint8Array, buf: Uint8Array, offset: number): void { | ||
| // Copy 16 UUID bytes into the provided buffer slice. | ||
| for (let i = 0; i < 16; i += 1) { | ||
| for (let i = 0; i < UUID_BYTES; i += 1) { | ||
| buf[offset + i] = rnds[i] | ||
| } | ||
| return buf | ||
| // Set RFC 4122 version (4) and variant (10xx) bits on owned output only. | ||
| buf[offset + 6] = (buf[offset + 6] & 0x0f) | 0x40 | ||
| buf[offset + 8] = (buf[offset + 8] & 0x3f) | 0x80 | ||
| } | ||
@@ -83,4 +66,18 @@ | ||
| ): string | TBuf { | ||
| const bytes = v4Bytes(options?.random ?? rng(), buf, offset) | ||
| return buf ?? formatUuid(bytes) | ||
| const random = options?.random | ||
| if (random && random.length < UUID_BYTES) { | ||
| throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${UUID_BYTES}`) | ||
| } | ||
| const outputOffset = buf ? (offset ?? 0) : 0 | ||
| if (buf && !isWritableRange(buf, outputOffset, UUID_BYTES)) { | ||
| throw new BufferError( | ||
| 'UUID_BUFFER_OUT_OF_BOUNDS', | ||
| `UUID byte range ${outputOffset}:${outputOffset + UUID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| const output = buf ?? reusableBuf | ||
| writeV4BytesUnchecked(random ?? rng(), output, outputOffset) | ||
| return buf ?? formatUuidUnchecked(output) | ||
| } | ||
@@ -87,0 +84,0 @@ |
+75
-47
| import { rng } from '../common/random' | ||
| import { isIntegerInRange, isWritableRange } from '../common/validation' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
| import { formatUuid, parseUuid } from './common/uuid' | ||
| import { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid' | ||
@@ -12,2 +13,5 @@ export type UuidV7Options = { | ||
| msecs?: number | ||
| /** | ||
| * Unsigned 32-bit sequence value. | ||
| */ | ||
| seq?: number | ||
@@ -31,6 +35,9 @@ } | ||
| const 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 | ||
| const UUID_BYTES = 16 | ||
| const MAX_MSECS = 0xffffffffffff | ||
| const MAX_SEQ = 0xffffffff | ||
| // Reusable buffer for string output path - avoids allocation per call. | ||
| // Safe because bytes are consumed synchronously by formatUuid(). | ||
| const reusableBuf = new Uint8Array(16) | ||
| // Safe because bytes are consumed synchronously by formatUuidUnchecked(). | ||
| const reusableBuf = new Uint8Array(UUID_BYTES) | ||
@@ -52,25 +59,3 @@ type V7State = { | ||
| function v7Bytes( | ||
| rnds: Uint8Array, | ||
| msecs: number | undefined, | ||
| seq: number | undefined, | ||
| buf: Uint8Array, | ||
| offset = 0, | ||
| ): Uint8Array { | ||
| if (rnds.length < 16) { | ||
| throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16') | ||
| } | ||
| if (offset < 0 || offset + 16 > buf.length) { | ||
| throw new BufferError( | ||
| 'UUID_BUFFER_OUT_OF_BOUNDS', | ||
| `UUID byte range ${offset}:${offset + 15} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| msecs ??= Date.now() | ||
| // Derive a 31-bit sequence if not provided by the caller. | ||
| // Uses the same formula as the hot path for consistency. | ||
| seq ??= (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9] | ||
| function writeV7BytesUnchecked(rnds: Uint8Array, msecs: number, seq: number, buf: Uint8Array, offset: number): void { | ||
| // Keep this inline on the UUID v7 hot path. Using writeTimestamp48() here | ||
@@ -97,6 +82,48 @@ // benchmarked slower than the direct byte writes. | ||
| buf[offset++] = rnds[15] | ||
| } | ||
| return buf | ||
| function writeV7Bytes(rnds: Uint8Array, msecs: number, seq: number, buf: Uint8Array, offset: number): void { | ||
| if (!isWritableRange(buf, offset, UUID_BYTES)) { | ||
| throw new BufferError( | ||
| 'UUID_BUFFER_OUT_OF_BOUNDS', | ||
| `UUID byte range ${offset}:${offset + UUID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| writeV7BytesUnchecked(rnds, msecs, seq, buf, offset) | ||
| } | ||
| function v7WithOptions<TBuf extends Uint8Array = Uint8Array>( | ||
| options: UuidV7Options, | ||
| buf?: TBuf, | ||
| offset = 0, | ||
| ): string | TBuf { | ||
| const optMsecs = options.msecs | ||
| if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) { | ||
| throw new InvalidInputError( | ||
| 'UUID_TIMESTAMP_OUT_OF_RANGE', | ||
| `Timestamp must be an integer between 0 and ${MAX_MSECS}`, | ||
| ) | ||
| } | ||
| const optSeq = options.seq | ||
| if (optSeq !== undefined && !isIntegerInRange(optSeq, 0, MAX_SEQ)) { | ||
| throw new InvalidInputError('UUID_SEQUENCE_OUT_OF_RANGE', `Sequence must be an integer between 0 and ${MAX_SEQ}`) | ||
| } | ||
| const optRandom = options.random | ||
| if (optRandom && optRandom.length < UUID_BYTES) { | ||
| throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${UUID_BYTES}`) | ||
| } | ||
| const rnds = optRandom ?? rng() | ||
| const msecs = optMsecs ?? Date.now() | ||
| // Derive a 31-bit sequence if not provided by the caller, matching the default hot path. | ||
| const seq = optSeq ?? (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9] | ||
| if (buf) { | ||
| writeV7Bytes(rnds, msecs, seq, buf, offset) | ||
| return buf | ||
| } | ||
| writeV7BytesUnchecked(rnds, msecs, seq, reusableBuf, 0) | ||
| return formatUuidUnchecked(reusableBuf) | ||
| } | ||
| /* | ||
@@ -111,27 +138,28 @@ * Overload: no buffer => return a UUID string. | ||
| function v7<TBuf extends Uint8Array = Uint8Array>(options?: UuidV7Options, buf?: TBuf, offset?: number): string | TBuf { | ||
| let bytes: Uint8Array | ||
| if (options) { | ||
| return v7WithOptions(options, buf, offset) | ||
| } | ||
| if (options) { | ||
| bytes = v7Bytes(options.random ?? rng(), options.msecs, options.seq, buf ?? reusableBuf, buf ? offset : 0) | ||
| // HOT PATH: Inline state management and byte generation for best performance | ||
| const now = Date.now() | ||
| const rnds = rng() | ||
| // Update state (inlined for performance) | ||
| if (now > state.msecs) { | ||
| state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9] | ||
| state.msecs = now | ||
| } else { | ||
| // HOT PATH: Inline state management and byte generation for best performance | ||
| const now = Date.now() | ||
| const rnds = rng() | ||
| // Update state (inlined for performance) | ||
| if (now > state.msecs) { | ||
| state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9] | ||
| state.msecs = now | ||
| } else { | ||
| state.seq = (state.seq + 1) | 0 | ||
| if (state.seq < 0) { | ||
| state.seq = 0 | ||
| state.msecs++ | ||
| } | ||
| state.seq = (state.seq + 1) | 0 | ||
| if (state.seq < 0) { | ||
| state.seq = 0 | ||
| state.msecs++ | ||
| } | ||
| } | ||
| bytes = v7Bytes(rnds, state.msecs, state.seq, buf ?? reusableBuf, buf ? offset : 0) | ||
| if (buf) { | ||
| writeV7Bytes(rnds, state.msecs, state.seq, buf, offset ?? 0) | ||
| return buf | ||
| } | ||
| return buf ? (bytes as TBuf) : formatUuid(bytes) | ||
| writeV7BytesUnchecked(rnds, state.msecs, state.seq, reusableBuf, 0) | ||
| return formatUuidUnchecked(reusableBuf) | ||
| } | ||
@@ -138,0 +166,0 @@ |
| import{ParseError as e}from"./errors.mjs";function t(e){return e>=48&&e<=57?e-48:e>=65&&e<=70?e-65+10:e>=97&&e<=102?e-97+10:-1}const n=[0,0,1,1,2,2,3,3,-1,4,4,5,5,-1,6,6,7,7,-1,8,8,9,9,-1,10,10,11,11,12,12,13,13,14,14,15,15],r=[!0,!1,!0,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1],i=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`));function a(e){return i[e[0]]+i[e[1]]+i[e[2]]+i[e[3]]+`-`+i[e[4]]+i[e[5]]+`-`+i[e[6]]+i[e[7]]+`-`+i[e[8]]+i[e[9]]+`-`+i[e[10]]+i[e[11]]+i[e[12]]+i[e[13]]+i[e[14]]+i[e[15]]}function o(i){if(i.length!==36)throw new e(`UUID_INVALID_LENGTH`,`UUID string must be 36 characters, got ${i.length}`);if(i[8]!==`-`||i[13]!==`-`||i[18]!==`-`||i[23]!==`-`)throw new e(`UUID_INVALID_SEPARATORS`,`UUID string has invalid separators at positions 8, 13, 18, 23. Received: "${i}"`);let a=new Uint8Array(16);for(let o=0;o<36;o+=1){let s=n[o];if(s===-1)continue;let c=t(i.charCodeAt(o));if(c===-1)throw new e(`UUID_INVALID_HEX_CHAR`,`UUID string contains invalid hex character at position ${o}`);r[o]?a[s]=c<<4:a[s]|=c}return a}export{o as n,a as t}; | ||
| //# sourceMappingURL=uuid-BBjwHv7J.mjs.map |
| {"version":3,"file":"uuid-BBjwHv7J.mjs","names":[],"sources":["../src/uuid/common/hex.ts","../src/uuid/common/uuid.ts"],"sourcesContent":["// ASCII code ranges for hex character validation\nconst ASCII_0 = 48 // '0'\nconst ASCII_9 = 57 // '9'\nconst ASCII_A = 65 // 'A'\nconst ASCII_F = 70 // 'F'\nconst ASCII_a = 97 // 'a'\nconst ASCII_f = 102 // 'f'\n\n/**\n * Convert a hex character ASCII code to its numeric value (0-15).\n * Returns -1 for invalid hex characters.\n */\nexport function hexValue(code: number): number {\n if (code >= ASCII_0 && code <= ASCII_9) {\n return code - ASCII_0\n }\n if (code >= ASCII_A && code <= ASCII_F) {\n return code - ASCII_A + 10\n }\n if (code >= ASCII_a && code <= ASCII_f) {\n return code - ASCII_a + 10\n }\n return -1 // Invalid hex character\n}\n","import { ParseError } from '../../errors'\nimport { hexValue } from './hex'\n\nconst UUID_BYTE_LENGTH = 16\nconst UUID_STRING_LENGTH = 36\n\n// Mapping from UUID string position to byte index.\n// -1 indicates a dash position that should be skipped.\n// This avoids intermediate string allocations during parsing.\nconst UUID_CHAR_TO_BYTE: number[] = [\n 0,\n 0,\n 1,\n 1,\n 2,\n 2,\n 3,\n 3, // chars 0-7 → bytes 0-3\n -1, // char 8 is '-'\n 4,\n 4,\n 5,\n 5, // chars 9-12 → bytes 4-5\n -1, // char 13 is '-'\n 6,\n 6,\n 7,\n 7, // chars 14-17 → bytes 6-7\n -1, // char 18 is '-'\n 8,\n 8,\n 9,\n 9, // chars 19-22 → bytes 8-9\n -1, // char 23 is '-'\n 10,\n 10,\n 11,\n 11,\n 12,\n 12,\n 13,\n 13,\n 14,\n 14,\n 15,\n 15, // chars 24-35 → bytes 10-15\n]\n\n// Whether each position is the high nibble (true) or low nibble (false)\nconst UUID_CHAR_IS_HIGH: boolean[] = [\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 0-7\n false, // dash (ignored)\n true,\n false,\n true,\n false, // chars 9-12\n false, // dash\n true,\n false,\n true,\n false, // chars 14-17\n false, // dash\n true,\n false,\n true,\n false, // chars 19-22\n false, // dash\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 24-35\n]\n\n// Pre-computed lookup table for byte-to-hex conversion (0x00 -> \"00\", 0xff -> \"ff\")\n//\n// Note: this table must remain defined in the same module as `formatUuid`, otherwise the v8 optimizer\n// will cause a performance drop of ~36%.\nconst HEX_TABLE: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\nexport function formatUuid(bytes: Uint8Array): string {\n // Direct string concatenation - optimized for V8's string builder.\n // This approach avoids loop overhead and intermediate allocations.\n // See: https://github.com/uuidjs/uuid/pull/434\n return (\n HEX_TABLE[bytes[0]] +\n HEX_TABLE[bytes[1]] +\n HEX_TABLE[bytes[2]] +\n HEX_TABLE[bytes[3]] +\n '-' +\n HEX_TABLE[bytes[4]] +\n HEX_TABLE[bytes[5]] +\n '-' +\n HEX_TABLE[bytes[6]] +\n HEX_TABLE[bytes[7]] +\n '-' +\n HEX_TABLE[bytes[8]] +\n HEX_TABLE[bytes[9]] +\n '-' +\n HEX_TABLE[bytes[10]] +\n HEX_TABLE[bytes[11]] +\n HEX_TABLE[bytes[12]] +\n HEX_TABLE[bytes[13]] +\n HEX_TABLE[bytes[14]] +\n HEX_TABLE[bytes[15]]\n )\n}\n\nexport function parseUuid(value: string): Uint8Array {\n if (value.length !== UUID_STRING_LENGTH) {\n throw new ParseError('UUID_INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`)\n }\n\n // Validate separator positions directly (more efficient than full loop)\n if (value[8] !== '-' || value[13] !== '-' || value[18] !== '-' || value[23] !== '-') {\n throw new ParseError(\n 'UUID_INVALID_SEPARATORS',\n `UUID string has invalid separators at positions 8, 13, 18, 23. Received: \"${value}\"`,\n )\n }\n\n // Parse bytes directly from UUID string without intermediate string allocations.\n // This avoids the 9 allocations (5 slices + 4 concatenations) of the naive approach.\n const bytes = new Uint8Array(UUID_BYTE_LENGTH)\n\n for (let i = 0; i < UUID_STRING_LENGTH; i += 1) {\n const byteIdx = UUID_CHAR_TO_BYTE[i]\n if (byteIdx === -1) continue // Skip dash positions\n\n const nibble = hexValue(value.charCodeAt(i))\n if (nibble === -1) {\n throw new ParseError('UUID_INVALID_HEX_CHAR', `UUID string contains invalid hex character at position ${i}`)\n }\n\n if (UUID_CHAR_IS_HIGH[i]) {\n bytes[byteIdx] = nibble << 4\n } else {\n bytes[byteIdx] |= nibble\n }\n }\n\n return bytes\n}\n"],"mappings":"0CAYA,SAAgB,EAAS,EAAsB,CAU7C,OATI,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAEZ,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAAU,GAEtB,GAAQ,IAAW,GAAQ,IACtB,EAAO,GAAU,GAEnB,EACT,CCpBA,MAMM,EAA8B,CAClC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,EACF,EAGM,EAA+B,CACnC,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,EACF,EAMM,EAAsB,MAAM,KAAK,CAAE,OAAQ,GAAI,GAAI,EAAG,IAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,EAEjG,SAAgB,EAAW,EAA2B,CAIpD,OACE,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,IAEpB,CAEA,SAAgB,EAAU,EAA2B,CACnD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAW,sBAAuB,0CAA0C,EAAM,QAAQ,EAItG,GAAI,EAAM,KAAO,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,IAC9E,MAAM,IAAI,EACR,0BACA,6EAA6E,EAAM,EACrF,EAKF,IAAM,EAAQ,IAAI,WAAW,EAAgB,EAE7C,IAAK,IAAI,EAAI,EAAG,EAAI,GAAoB,GAAK,EAAG,CAC9C,IAAM,EAAU,EAAkB,GAClC,GAAI,IAAY,GAAI,SAEpB,IAAM,EAAS,EAAS,EAAM,WAAW,CAAC,CAAC,EAC3C,GAAI,IAAW,GACb,MAAM,IAAI,EAAW,wBAAyB,0DAA0D,GAAG,EAGzG,EAAkB,GACpB,EAAM,GAAW,GAAU,EAE3B,EAAM,IAAY,CAEtB,CAEA,OAAO,CACT"} |
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
296471
4.56%79
3.95%2796
4.02%