uniku 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +87 -22
  2. package/build/{bytes-CbbFzz-v.mjs → bytes-xqWxFYsM.mjs} +1 -1
  3. package/build/{bytes-CbbFzz-v.mjs.map → bytes-xqWxFYsM.mjs.map} +1 -1
  4. package/build/cuid/v2.d.mts +2 -0
  5. package/build/cuid/v2.mjs +1 -0
  6. package/build/cuid2/cuid2.d.mts +5 -1
  7. package/build/cuid2/cuid2.d.mts.map +1 -1
  8. package/build/cuid2/cuid2.mjs +1 -1
  9. package/build/cuid2/cuid2.mjs.map +1 -1
  10. package/build/errors-Dgoyi-ci.d.mts +38 -0
  11. package/build/errors-Dgoyi-ci.d.mts.map +1 -0
  12. package/build/errors.d.mts +2 -38
  13. package/build/errors.mjs +1 -1
  14. package/build/errors.mjs.map +1 -1
  15. package/build/generators.d.mts +17 -0
  16. package/build/generators.d.mts.map +1 -0
  17. package/build/generators.mjs +2 -0
  18. package/build/generators.mjs.map +1 -0
  19. package/build/ksuid/ksuid.d.mts +3 -5
  20. package/build/ksuid/ksuid.d.mts.map +1 -1
  21. package/build/ksuid/ksuid.mjs +1 -1
  22. package/build/ksuid/ksuid.mjs.map +1 -1
  23. package/build/nanoid/nanoid.d.mts +3 -6
  24. package/build/nanoid/nanoid.d.mts.map +1 -1
  25. package/build/nanoid/nanoid.mjs +1 -1
  26. package/build/nanoid/nanoid.mjs.map +1 -1
  27. package/build/objectid/objectid.d.mts +3 -5
  28. package/build/objectid/objectid.d.mts.map +1 -1
  29. package/build/objectid/objectid.mjs +1 -1
  30. package/build/objectid/objectid.mjs.map +1 -1
  31. package/build/{random-C_nRoaUe.mjs → random-Chp-Nkzi.mjs} +1 -1
  32. package/build/{random-C_nRoaUe.mjs.map → random-Chp-Nkzi.mjs.map} +1 -1
  33. package/build/tsid/tsid.d.mts +82 -0
  34. package/build/tsid/tsid.d.mts.map +1 -0
  35. package/build/tsid/tsid.mjs +2 -0
  36. package/build/tsid/tsid.mjs.map +1 -0
  37. package/build/typeid/typeid.d.mts +1 -1
  38. package/build/typeid/typeid.d.mts.map +1 -1
  39. package/build/typeid/typeid.mjs.map +1 -1
  40. package/build/ulid/ulid.d.mts +3 -5
  41. package/build/ulid/ulid.d.mts.map +1 -1
  42. package/build/ulid/ulid.mjs +1 -1
  43. package/build/ulid/ulid.mjs.map +1 -1
  44. package/build/uuid/v4.d.mts +3 -5
  45. package/build/uuid/v4.d.mts.map +1 -1
  46. package/build/uuid/v4.mjs +1 -1
  47. package/build/uuid/v4.mjs.map +1 -1
  48. package/build/uuid/v7.d.mts +3 -5
  49. package/build/uuid/v7.d.mts.map +1 -1
  50. package/build/uuid/v7.mjs +1 -1
  51. package/build/uuid/v7.mjs.map +1 -1
  52. package/build/{uuid-C9VcXh5N.mjs → uuid-BBjwHv7J.mjs} +1 -1
  53. package/build/uuid-BBjwHv7J.mjs.map +1 -0
  54. package/package.json +27 -5
  55. package/src/cuid/v2.ts +19 -0
  56. package/src/cuid2/cuid2.ts +4 -0
  57. package/src/generators.ts +17 -0
  58. package/src/tsid/crockford64.ts +75 -0
  59. package/src/tsid/tsid.ts +310 -0
  60. package/build/errors.d.mts.map +0 -1
  61. package/build/uuid-C9VcXh5N.mjs.map +0 -1
package/build/uuid/v4.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{r as e}from"../random-C_nRoaUe.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-C9VcXh5N.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};
1
+ 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};
2
2
  //# sourceMappingURL=v4.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"v4.mjs","names":["uuidv4: UuidV4"],"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,OAAO,CAuB/E,EAAgB,yEAEtB,SAAS,EAAQ,EAAkB,EAAkB,EAAS,EAAe,CAC3E,GAAI,EAAK,OAAS,GAChB,MAAM,IAAI,EAAkB,8BAA+B,oCAAoC,CAQjG,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,0BAC1C,CAIH,IAAK,IAAI,EAAI,EAAG,EAAI,GAAI,GAAK,EAC3B,EAAI,EAAS,GAAK,EAAK,GAGzB,OAAO,EAWT,SAAS,EAAyC,EAAyB,EAAY,EAAgC,CAKrH,MAJI,CAAC,GAAO,CAAC,EACJ,GAAY,CAGd,EAAI,EAAS,EAAK,EAAO,CAGlC,SAAS,EACP,EACA,EACA,EACe,CACf,IAAM,EAAQ,EAAQ,GAAS,QAAU,GAAK,CAAE,EAAK,EAAO,CAC5D,OAAO,GAAO,EAAW,EAAM,CAGjC,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAc,KAAK,EAAG,CAyBzD,MAAaA,EAAiB,OAAO,OAAO,EAAI,CAC9C,QAAS,EACT,UAAW,EACX,UACA,IAAK,uCACL,IAAK,uCACN,CAAC"}
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"}
@@ -1,4 +1,4 @@
1
- import { BufferError, InvalidInputError, ParseError, UniqueIdError } from "../errors.mjs";
1
+ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs";
2
2
 
3
3
  //#region src/uuid/v7.d.ts
4
4
  type UuidV7Options = {
@@ -17,10 +17,8 @@ type UuidV7 = {
17
17
  toBytes(id: string): Uint8Array;
18
18
  fromBytes(bytes: Uint8Array): string;
19
19
  timestamp(id: string): number;
20
- isValid(id: unknown): id is string;
21
- /** The nil UUID (all zeros) */
22
- NIL: string;
23
- /** The max UUID (all ones) */
20
+ isValid(id: unknown): id is string; /** The nil UUID (all zeros) */
21
+ NIL: string; /** The max UUID (all ones) */
24
22
  MAX: string;
25
23
  };
26
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"v7.d.mts","names":["uuidv7: UuidV7"],"sources":["../../src/uuid/v7.ts"],"sourcesContent":[],"mappings":";;;KAIY,aAAA;;;AAAZ;AAUA;EAEgB,MAAA,CAAA,EAPL,UAOK;EAAa,KAAA,CAAA,EAAA,MAAA;EAAqB,GAAA,CAAA,EAAA,MAAA;CAAgC;AAAwB,KAF9F,MAAA,GAE8F;EAC7F,EAAA,EAAA,MAAA;EACU,CAAA,aAFP,UAEO,GAFM,UAEN,CAAA,CAAA,OAAA,EAF2B,aAE3B,GAAA,SAAA,EAAA,GAAA,EAF2D,IAE3D,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAFmF,IAEnF;EACJ,CAAA,OAAA,CAAA,EAFN,aAEM,EAAA,GAAA,CAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;EAAA,OAAA,CAAA,EAAA,EAAA,MAAA,CAAA,EADI,UACJ;EAyJnB,SAAaA,CAAAA,KAAQ,EAzJF,UAyJE,CAAA,EAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAARA,QAAQ"}
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"}
package/build/uuid/v7.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{r as e}from"../random-C_nRoaUe.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-C9VcXh5N.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};
1
+ 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};
2
2
  //# sourceMappingURL=v7.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"v7.mjs","names":["state: V7State","bytes: Uint8Array","uuidv7: UuidV7"],"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,GAAG,CAehCA,EAAiB,CAAE,MAAO,KAAW,IAAK,EAAG,CAEnD,SAAS,EACP,EACA,EACA,EACA,EACA,EAAS,EACG,CACZ,GAAI,EAAK,OAAS,GAChB,MAAM,IAAI,EAAkB,8BAA+B,oCAAoC,CAGjG,GAAI,EAAS,GAAK,EAAS,GAAK,EAAI,OAClC,MAAM,IAAI,EACR,4BACA,mBAAmB,EAAO,GAAG,EAAS,GAAG,0BAC1C,CA8BH,MA3BA,KAAU,KAAK,KAAK,CAGpB,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,EAWT,SAAS,EAAyC,EAAyB,EAAY,EAAgC,CACrH,IAAIC,EAEJ,GAAI,EACF,EAAQ,EAAQ,EAAQ,QAAU,GAAK,CAAE,EAAQ,MAAO,EAAQ,IAAK,GAAO,EAAa,EAAM,EAAS,EAAE,KACrG,CAEL,IAAM,EAAM,KAAK,KAAK,CAChB,EAAO,GAAK,CAGd,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,EAAE,CAGrF,OAAO,EAAO,EAAiB,EAAW,EAAM,CAGlD,SAAS,EAAU,EAAoB,CACrC,IAAM,EAAQ,EAAU,EAAG,CACvB,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,GAAK,EAC1B,EAAQ,EAAQ,IAAM,EAAM,GAE9B,OAAO,EAGT,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAc,KAAK,EAAG,CA6BzD,MAAaC,EAAiB,OAAO,OAAO,EAAI,CAC9C,QAAS,EACT,UAAW,EACX,YACA,UACA,IAAK,uCACL,IAAK,uCACN,CAAC"}
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"}
@@ -1,2 +1,2 @@
1
1
  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};
2
- //# sourceMappingURL=uuid-C9VcXh5N.mjs.map
2
+ //# sourceMappingURL=uuid-BBjwHv7J.mjs.map
@@ -0,0 +1 @@
1
+ {"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "uniku",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime",
6
6
  "author": {
7
7
  "name": "Alberto Schiabel",
@@ -29,6 +29,8 @@
29
29
  "ksuid",
30
30
  "objectid",
31
31
  "mongodb",
32
+ "tsid",
33
+ "snowflake",
32
34
  "unique",
33
35
  "id",
34
36
  "identifier",
@@ -83,6 +85,11 @@
83
85
  "import": "./build/cuid2/cuid2.mjs",
84
86
  "default": "./build/cuid2/cuid2.mjs"
85
87
  },
88
+ "./cuid/v2": {
89
+ "types": "./build/cuid/v2.d.mts",
90
+ "import": "./build/cuid/v2.mjs",
91
+ "default": "./build/cuid/v2.mjs"
92
+ },
86
93
  "./nanoid": {
87
94
  "types": "./build/nanoid/nanoid.d.mts",
88
95
  "import": "./build/nanoid/nanoid.mjs",
@@ -98,25 +105,38 @@
98
105
  "import": "./build/objectid/objectid.mjs",
99
106
  "default": "./build/objectid/objectid.mjs"
100
107
  },
108
+ "./tsid": {
109
+ "types": "./build/tsid/tsid.d.mts",
110
+ "import": "./build/tsid/tsid.mjs",
111
+ "default": "./build/tsid/tsid.mjs"
112
+ },
101
113
  "./errors": {
102
114
  "types": "./build/errors.d.mts",
103
115
  "import": "./build/errors.mjs",
104
116
  "default": "./build/errors.mjs"
105
117
  },
118
+ "./generators": {
119
+ "types": "./build/generators.d.mts",
120
+ "import": "./build/generators.mjs",
121
+ "default": "./build/generators.mjs"
122
+ },
106
123
  "./package.json": "./package.json"
107
124
  },
108
125
  "dependencies": {
109
- "@noble/hashes": "^2.0.1"
126
+ "@noble/hashes": "^2.2.0"
110
127
  },
111
128
  "devDependencies": {
129
+ "@effect/platform-bun": "4.0.0-beta.93",
112
130
  "@owpz/ksuid": "^25.7.20",
113
131
  "@paralleldrive/cuid2": "^3.3.0",
114
- "@types/bun": "^1.3.8",
132
+ "@types/bun": "^1.3.14",
133
+ "effect": "4.0.0-beta.93",
115
134
  "bson": "^7.3.1",
116
- "nanoid": "^5.1.6",
135
+ "nanoid": "^5.1.16",
136
+ "tsid-ts": "^0.0.9",
117
137
  "typeid-js": "1.2.0",
118
138
  "ulid": "3.0.2",
119
- "uuid": "13.0.0"
139
+ "uuid": "14.0.1"
120
140
  },
121
141
  "scripts": {
122
142
  "clean": "rimraf build .turbo",
@@ -125,9 +145,11 @@
125
145
  "test": "vitest run",
126
146
  "test:unit": "vitest --project unit run",
127
147
  "test:integration": "vitest --project integration run",
148
+ "test:coverage": "vitest run --coverage",
128
149
  "test:e2e": "pnpm --filter @uniku/e2e-cloudflare test:e2e",
129
150
  "test:e2e:cloudflare": "pnpm --filter @uniku/e2e-cloudflare test:e2e:cloudflare",
130
151
  "smoke:publish": "node scripts/publish-smoke.mjs",
152
+ "smoke:runtime:browser": "bun scripts/browser-smoke.ts",
131
153
  "smoke:runtime:bun": "bun scripts/runtime-smoke.mjs",
132
154
  "smoke:runtime:deno": "deno run --node-modules-dir=manual scripts/runtime-smoke.mjs",
133
155
  "bench": "vitest bench --run --project unit",
package/src/cuid/v2.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `uniku/cuid/v2` — the canonical, versioned entry point for the CUID2 generator.
3
+ *
4
+ * This mirrors the versioned-subpath convention used by `uniku/uuid/v4` and
5
+ * `uniku/uuid/v7`, and supersedes the now-`@deprecated` `uniku/cuid2` entry
6
+ * point. It re-exports the single existing implementation under the `cuidv2`
7
+ * name — there is no second implementation.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { cuidv2 } from 'uniku/cuid/v2'
12
+ *
13
+ * const id = cuidv2()
14
+ * // => "pfh0haxfpzowht3oi213cqos"
15
+ *
16
+ * cuidv2.isValid(id) // true
17
+ * ```
18
+ */
19
+ export { type Cuid2 as CuidV2, type Cuid2Options as CuidV2Options, cuid2 as cuidv2 } from '../cuid2/cuid2'
@@ -203,6 +203,10 @@ function isValid(id: unknown): id is string {
203
203
  * console.log(maybeId.length) // TypeScript knows maybeId is string
204
204
  * }
205
205
  * ```
206
+ *
207
+ * @deprecated Use `cuidv2` from `uniku/cuid/v2` instead. This entry point keeps
208
+ * working unchanged, but `uniku/cuid/v2` is the canonical versioned subpath
209
+ * (mirroring `uniku/uuid/v4` / `uniku/uuid/v7`).
206
210
  */
207
211
  export const cuid2: Cuid2 = Object.assign(cuid2Fn, {
208
212
  isValid,
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The canonical, ordered list of ID generators supported by uniku.
3
+ *
4
+ * This is the single source of truth for the set of supported ID generators.
5
+ * Tools that need to enumerate or validate ID generators (for example,
6
+ * `@uniku/cli`'s `--type` flag) should derive from this array rather than
7
+ * hand-copying the union.
8
+ */
9
+ // Plain `as const` (not `as const satisfies readonly string[]`): the `satisfies`
10
+ // wrapper defeats isolatedDeclarations' trivial declaration emit for this export.
11
+ // All members are string literals, so the shape guard adds nothing here anyway.
12
+ export const ID_GENERATORS = ['uuid', 'ulid', 'typeid', 'nanoid', 'cuid', 'ksuid', 'objectid', 'tsid'] as const
13
+
14
+ /**
15
+ * The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.
16
+ */
17
+ export type IdGenerator = (typeof ID_GENERATORS)[number]
@@ -0,0 +1,75 @@
1
+ import { ParseError } from '../errors'
2
+
3
+ /**
4
+ * Crockford Base32 codec for TSID, operating directly on the primary `bigint`
5
+ * value rather than an intermediate byte array (unlike ulid/crockford.ts) -
6
+ * tsid's primary type never routes through a `Uint8Array` during string
7
+ * conversion.
8
+ *
9
+ * TSID binary format: 64 bits (8 bytes), encoded as 13 Crockford Base32 characters.
10
+ * 13 chars * 5 bits = 65 bits of capacity for a 64-bit value, leaving exactly 1 bit
11
+ * of headroom on the leading character. That headroom bit is always 0, so the
12
+ * leading character only carries 4 significant real bits (values 0-15, i.e. the
13
+ * alphabet's first 16 symbols "0"-"9"/"A"-"F") rather than the full 32-symbol
14
+ * range - the same overflow-prevention technique ULID uses for its own leading
15
+ * `[0-7]` restriction (2 headroom bits there vs 1 here). Confirmed against
16
+ * `tsid-ts`'s own `toCanonicalString`: `(number >> BigInt(60 - i * 5)) & 0x1f`.
17
+ */
18
+
19
+ const TSID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
20
+ const TSID_STRING_LEN = 13
21
+ const TSID_LEADING_MAX = 15 // 4 significant bits on the leading character (see above)
22
+
23
+ // Pre-computed decode table (case-insensitive) covering ASCII, so lookups never
24
+ // go out of bounds and a single `=== 255` check rejects invalid input.
25
+ const DECODING = new Uint8Array(128).fill(255)
26
+ for (let i = 0; i < TSID_ALPHABET.length; i += 1) {
27
+ const char = TSID_ALPHABET[i]
28
+ const upperCode = TSID_ALPHABET.charCodeAt(i)
29
+ DECODING[upperCode] = i
30
+ // Only letters have a lowercase counterpart - applying +32 to digit codes
31
+ // would land on unrelated uppercase letters (e.g. '5' + 32 -> 'U', which is
32
+ // excluded from this alphabet and would never get overwritten back to 255).
33
+ if (char >= 'A' && char <= 'Z') {
34
+ DECODING[upperCode + 32] = i
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Encode a TSID `bigint` value to its 13-character canonical Crockford Base32 string.
40
+ */
41
+ export function encodeTsidString(value: bigint): string {
42
+ let result = ''
43
+ for (let i = 0; i < TSID_STRING_LEN; i += 1) {
44
+ const shift = BigInt(60 - i * 5)
45
+ const group = Number((value >> shift) & 0x1fn)
46
+ result += TSID_ALPHABET[group]
47
+ }
48
+ return result
49
+ }
50
+
51
+ /**
52
+ * Decode a 13-character canonical Crockford Base32 string to a TSID `bigint` value.
53
+ */
54
+ export function decodeTsidString(str: string): bigint {
55
+ if (str.length !== TSID_STRING_LEN) {
56
+ throw new ParseError('TSID_INVALID_LENGTH', `TSID string must be ${TSID_STRING_LEN} characters, got ${str.length}`)
57
+ }
58
+
59
+ let value = 0n
60
+ for (let i = 0; i < TSID_STRING_LEN; i += 1) {
61
+ const code = str.charCodeAt(i)
62
+ const group = code < 128 ? DECODING[code] : 255
63
+ if (group === 255) {
64
+ throw new ParseError('TSID_INVALID_CHAR', `Invalid TSID character: ${str[i]}`)
65
+ }
66
+ if (i === 0 && group > TSID_LEADING_MAX) {
67
+ throw new ParseError(
68
+ 'TSID_LEADING_CHAR_OUT_OF_RANGE',
69
+ `TSID leading character must be one of 0-9, A-F, got: ${str[i]}`,
70
+ )
71
+ }
72
+ value = (value << 5n) | BigInt(group)
73
+ }
74
+ return value
75
+ }
@@ -0,0 +1,310 @@
1
+ import { randomUint32 } from '../common/random'
2
+ import { BufferError, InvalidInputError } from '../errors'
3
+ import { decodeTsidString, encodeTsidString } from './crockford64'
4
+
5
+ /**
6
+ * TSID (Time-Sorted Unique Identifier)
7
+ *
8
+ * A 64-bit Snowflake-style identifier consisting of:
9
+ * - 42 bits: millisecond timestamp relative to a custom epoch (default 2020-01-01T00:00:00.000Z)
10
+ * - 10 bits: node ID (default; configurable via `nodeBits`)
11
+ * - 12 bits: per-millisecond counter (default; `22 - nodeBits`)
12
+ *
13
+ * Unlike every other uniku generator, `tsid()` returns a `bigint` by default -
14
+ * this is a deliberate design decision reflecting that TSID's value proposition
15
+ * is native numeric storage (e.g. a database BIGINT primary key), not a
16
+ * string-first identifier that happens to have a byte encoding. `toString`/
17
+ * `fromString` are the boundary conversions to/from the 13-character canonical
18
+ * Crockford Base32 string.
19
+ *
20
+ * Encoded as 8 big-endian bytes in buffer mode.
21
+ */
22
+
23
+ const TSID_EPOCH = 1577836800000 // 2020-01-01T00:00:00.000Z
24
+ const TSID_BYTES = 8
25
+ const RANDOM_BITS = 22 // combined node + counter bits
26
+ const DEFAULT_NODE_BITS = 10
27
+ const DEFAULT_COUNTER_BITS = RANDOM_BITS - DEFAULT_NODE_BITS
28
+ const DEFAULT_NODE_MASK = (1 << DEFAULT_NODE_BITS) - 1
29
+ const DEFAULT_COUNTER_MASK = (1 << DEFAULT_COUNTER_BITS) - 1
30
+ const MAX_NODE_BITS = 20
31
+ const MAX_TIMESTAMP_DIFF = (1n << 42n) - 1n
32
+
33
+ export type TsidOptions = {
34
+ /**
35
+ * Timestamp in milliseconds since Unix epoch.
36
+ * Defaults to Date.now().
37
+ */
38
+ msecs?: number
39
+ /**
40
+ * Custom epoch in milliseconds since Unix epoch.
41
+ * Defaults to 1577836800000 (2020-01-01T00:00:00.000Z).
42
+ */
43
+ epoch?: number
44
+ /**
45
+ * Node ID (0 to 2^nodeBits - 1).
46
+ * Defaults to a lazily-initialized, persistent random value.
47
+ */
48
+ node?: number
49
+ /**
50
+ * Number of bits allocated to the node ID (0-20).
51
+ * The remaining bits (22 - nodeBits) are allocated to the counter.
52
+ * Defaults to 10.
53
+ */
54
+ nodeBits?: number
55
+ /**
56
+ * Per-millisecond counter (0 to 2^(22 - nodeBits) - 1).
57
+ * Defaults to a fresh random value on a new millisecond, or the previous
58
+ * value incremented by 1 within the same millisecond.
59
+ */
60
+ counter?: number
61
+ }
62
+
63
+ export type Tsid = {
64
+ (): bigint
65
+ <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf
66
+ (options?: TsidOptions, buf?: undefined, offset?: number): bigint
67
+ toBytes(id: bigint): Uint8Array
68
+ fromBytes(bytes: Uint8Array): bigint
69
+ toString(id: bigint): string
70
+ fromString(str: string): bigint
71
+ timestamp(id: bigint, epoch?: number): number
72
+ isValid(id: unknown): id is bigint
73
+ /** The nil TSID (all zeros) */
74
+ NIL: bigint
75
+ /** The max TSID (maximum valid 64-bit value) */
76
+ MAX: bigint
77
+ }
78
+
79
+ type TsidState = {
80
+ node: number | undefined
81
+ msecs: number
82
+ counter: number
83
+ }
84
+
85
+ /**
86
+ * Module-level state for the lazily-initialized persistent node ID and the
87
+ * per-millisecond-reset counter.
88
+ *
89
+ * IMPORTANT: This state persists across all tsid() calls in the module's lifetime.
90
+ * - In serverless/edge functions with warm starts, state persists between invocations.
91
+ * - For isolated state, pass explicit `msecs`, `epoch`, `node`, `nodeBits`, or
92
+ * `counter` via options - doing so bypasses `state` entirely.
93
+ * - Tests should mock Date.now() or provide explicit options for deterministic behavior.
94
+ */
95
+ const state: TsidState = {
96
+ node: undefined,
97
+ msecs: -Infinity,
98
+ counter: 0,
99
+ }
100
+
101
+ function pack(msecsDiff: bigint, node: number, counterBits: number, counter: number): bigint {
102
+ return (msecsDiff << BigInt(RANDOM_BITS)) | (BigInt(node) << BigInt(counterBits)) | BigInt(counter)
103
+ }
104
+
105
+ function writeTsidBytes(value: bigint, buf?: Uint8Array, offset = 0): Uint8Array {
106
+ if (!buf) {
107
+ buf = new Uint8Array(TSID_BYTES)
108
+ offset = 0
109
+ } else if (offset < 0 || offset + TSID_BYTES > buf.length) {
110
+ throw new BufferError(
111
+ 'TSID_BUFFER_OUT_OF_BOUNDS',
112
+ `TSID byte range ${offset}:${offset + TSID_BYTES - 1} is out of buffer bounds`,
113
+ )
114
+ }
115
+
116
+ for (let i = 0; i < TSID_BYTES; i += 1) {
117
+ buf[offset + i] = Number((value >> BigInt((TSID_BYTES - 1 - i) * 8)) & 0xffn)
118
+ }
119
+
120
+ return buf
121
+ }
122
+
123
+ /*
124
+ * Overload: no buffer => return a TSID bigint.
125
+ */
126
+ function tsidFn(options?: TsidOptions, buf?: undefined, offset?: number): bigint
127
+ /*
128
+ * Overload: caller provides a buffer slice to fill with TSID bytes.
129
+ */
130
+ function tsidFn<TBuf extends Uint8Array = Uint8Array>(
131
+ options: TsidOptions | undefined,
132
+ buf: TBuf,
133
+ offset?: number,
134
+ ): TBuf
135
+ function tsidFn<TBuf extends Uint8Array = Uint8Array>(options?: TsidOptions, buf?: TBuf, offset = 0): bigint | TBuf {
136
+ let msecsDiff: bigint
137
+ let node: number
138
+ let counterBits: number
139
+ let counter: number
140
+
141
+ if (options) {
142
+ const nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS
143
+ if (nodeBits < 0 || nodeBits > MAX_NODE_BITS) {
144
+ throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`)
145
+ }
146
+ counterBits = RANDOM_BITS - nodeBits
147
+ const nodeMask = (1 << nodeBits) - 1
148
+ const counterMask = (1 << counterBits) - 1
149
+
150
+ const optNode = options.node
151
+ if (optNode !== undefined) {
152
+ if (optNode < 0 || optNode > nodeMask) {
153
+ throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`)
154
+ }
155
+ node = optNode
156
+ } else {
157
+ node = randomUint32() & nodeMask
158
+ }
159
+
160
+ const optCounter = options.counter
161
+ if (optCounter !== undefined) {
162
+ if (optCounter < 0 || optCounter > counterMask) {
163
+ throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`)
164
+ }
165
+ counter = optCounter
166
+ } else {
167
+ counter = randomUint32() & counterMask
168
+ }
169
+
170
+ const epoch = options.epoch ?? TSID_EPOCH
171
+ const msecs = options.msecs ?? Date.now()
172
+ const diff = BigInt(msecs) - BigInt(epoch)
173
+ if (diff < 0n || diff > MAX_TIMESTAMP_DIFF) {
174
+ throw new InvalidInputError(
175
+ 'TSID_TIMESTAMP_OUT_OF_RANGE',
176
+ `msecs - epoch must be between 0 and ${MAX_TIMESTAMP_DIFF}`,
177
+ )
178
+ }
179
+ msecsDiff = diff
180
+ } else {
181
+ // Lazily initialize the persistent node ID on first no-option call.
182
+ if (state.node === undefined) {
183
+ state.node = randomUint32() & DEFAULT_NODE_MASK
184
+ }
185
+ node = state.node
186
+ counterBits = DEFAULT_COUNTER_BITS
187
+
188
+ /**
189
+ * Note: by default, Cloudflare Workers "freezes" time during request handling
190
+ * to prevent side-channel attacks, so Date.now() returns the same value for
191
+ * the entire duration of a request. Monotonic ordering within such a request
192
+ * relies entirely on the counter (and its clock-drift-ahead overflow below).
193
+ */
194
+ const now = Date.now()
195
+ if (now > state.msecs) {
196
+ state.msecs = now
197
+ state.counter = randomUint32() & DEFAULT_COUNTER_MASK
198
+ } else {
199
+ state.counter += 1
200
+ if (state.counter > DEFAULT_COUNTER_MASK) {
201
+ // Counter overflowed within this real millisecond: advance the internal
202
+ // virtual timestamp ahead of wall-clock time rather than throwing. A
203
+ // 12-bit counter (4096 values/ms) is a realistic throughput ceiling.
204
+ state.msecs += 1
205
+ state.counter = 0
206
+ }
207
+ }
208
+ counter = state.counter
209
+ msecsDiff = BigInt(state.msecs) - BigInt(TSID_EPOCH)
210
+ }
211
+
212
+ const packed = pack(msecsDiff, node, counterBits, counter)
213
+
214
+ if (buf) {
215
+ writeTsidBytes(packed, buf, offset)
216
+ return buf
217
+ }
218
+
219
+ return packed
220
+ }
221
+
222
+ /**
223
+ * Convert a TSID bigint to 8 bytes.
224
+ */
225
+ function toBytes(id: bigint): Uint8Array {
226
+ return writeTsidBytes(id)
227
+ }
228
+
229
+ /**
230
+ * Convert 8 bytes to a TSID bigint.
231
+ */
232
+ function fromBytes(bytes: Uint8Array): bigint {
233
+ if (bytes.length !== TSID_BYTES) {
234
+ throw new BufferError(
235
+ 'TSID_BYTES_INVALID_LENGTH',
236
+ `TSID bytes must be exactly ${TSID_BYTES} bytes, got ${bytes.length}`,
237
+ )
238
+ }
239
+
240
+ let value = 0n
241
+ for (let i = 0; i < TSID_BYTES; i += 1) {
242
+ value = (value << 8n) | BigInt(bytes[i])
243
+ }
244
+ return value
245
+ }
246
+
247
+ /**
248
+ * Extract the timestamp (milliseconds since Unix epoch) from a TSID.
249
+ * The epoch is not self-describing in the packed value - pass the same
250
+ * `epoch` used at generation time if it was overridden from the default.
251
+ */
252
+ function timestamp(id: bigint, epoch: number = TSID_EPOCH): number {
253
+ return epoch + Number(id >> BigInt(RANDOM_BITS))
254
+ }
255
+
256
+ /**
257
+ * Validate that a value is a TSID bigint within the 64-bit range.
258
+ */
259
+ function isValid(id: unknown): id is bigint {
260
+ return typeof id === 'bigint' && id >= 0n && id <= MAX
261
+ }
262
+
263
+ const NIL = 0n
264
+ const MAX = (1n << 64n) - 1n
265
+
266
+ /**
267
+ * Generate a TSID bigint or write the bytes into a buffer.
268
+ *
269
+ * TSID (Time-Sorted Unique Identifier) is a 64-bit Snowflake-style identifier
270
+ * combining a millisecond timestamp, a node ID, and a per-millisecond counter.
271
+ * Unlike every other uniku generator, it returns a `bigint` by default, since
272
+ * TSID's entire value proposition is native numeric storage (e.g. a database
273
+ * BIGINT primary key).
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * import { tsid } from 'uniku/tsid'
278
+ *
279
+ * const id = tsid()
280
+ * // => 862301223059968074n
281
+ *
282
+ * // Canonical string form
283
+ * const str = tsid.toString(id)
284
+ * // => "0QXW2CK4XZM2A"
285
+ * tsid.fromString(str) === id // true
286
+ *
287
+ * // Extract timestamp
288
+ * const ts = tsid.timestamp(id)
289
+ * console.log(new Date(ts))
290
+ *
291
+ * // Validate
292
+ * tsid.isValid(id) // true
293
+ *
294
+ * // Convert to/from bytes (8 bytes)
295
+ * const bytes = tsid.toBytes(id)
296
+ * const restored = tsid.fromBytes(bytes)
297
+ * ```
298
+ */
299
+ export const tsid: Tsid = Object.assign(tsidFn, {
300
+ toBytes,
301
+ fromBytes,
302
+ toString: encodeTsidString,
303
+ fromString: decodeTsidString,
304
+ timestamp,
305
+ isValid,
306
+ NIL,
307
+ MAX,
308
+ })
309
+
310
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'