uniku 0.3.0 → 0.3.1

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 (40) hide show
  1. package/build/cuid2/cuid2.d.mts.map +1 -1
  2. package/build/cuid2/cuid2.mjs +1 -1
  3. package/build/cuid2/cuid2.mjs.map +1 -1
  4. package/build/ksuid/ksuid.d.mts.map +1 -1
  5. package/build/ksuid/ksuid.mjs +1 -1
  6. package/build/ksuid/ksuid.mjs.map +1 -1
  7. package/build/objectid/objectid.d.mts.map +1 -1
  8. package/build/objectid/objectid.mjs +1 -1
  9. package/build/objectid/objectid.mjs.map +1 -1
  10. package/build/tsid/tsid.d.mts.map +1 -1
  11. package/build/tsid/tsid.mjs +1 -1
  12. package/build/tsid/tsid.mjs.map +1 -1
  13. package/build/ulid/ulid.d.mts.map +1 -1
  14. package/build/ulid/ulid.mjs +1 -1
  15. package/build/ulid/ulid.mjs.map +1 -1
  16. package/build/uuid/v4.d.mts +0 -1
  17. package/build/uuid/v4.d.mts.map +1 -1
  18. package/build/uuid/v4.mjs +1 -1
  19. package/build/uuid/v4.mjs.map +1 -1
  20. package/build/uuid/v7.d.mts +3 -0
  21. package/build/uuid/v7.d.mts.map +1 -1
  22. package/build/uuid/v7.mjs +1 -1
  23. package/build/uuid/v7.mjs.map +1 -1
  24. package/build/uuid-CnQIYoQi.mjs +2 -0
  25. package/build/uuid-CnQIYoQi.mjs.map +1 -0
  26. package/build/validation-CTNpXm94.mjs +2 -0
  27. package/build/validation-CTNpXm94.mjs.map +1 -0
  28. package/package.json +1 -1
  29. package/src/common/validation.ts +7 -0
  30. package/src/cuid2/cuid2.ts +7 -3
  31. package/src/ksuid/ksuid.ts +19 -17
  32. package/src/objectid/objectid.ts +25 -17
  33. package/src/tsid/tsid.ts +83 -53
  34. package/src/ulid/crockford.ts +2 -2
  35. package/src/ulid/ulid.ts +35 -24
  36. package/src/uuid/common/uuid.ts +12 -1
  37. package/src/uuid/v4.ts +25 -28
  38. package/src/uuid/v7.ts +75 -47
  39. package/build/uuid-BBjwHv7J.mjs +0 -2
  40. package/build/uuid-BBjwHv7J.mjs.map +0 -1
@@ -8,6 +8,9 @@ type UuidV7Options = {
8
8
  */
9
9
  random?: Uint8Array;
10
10
  msecs?: number;
11
+ /**
12
+ * Unsigned 32-bit sequence value.
13
+ */
11
14
  seq?: number;
12
15
  };
13
16
  type UuidV7 = {
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"v7.d.mts","names":[],"sources":["../../src/uuid/v7.ts"],"mappings":";;;KAKY,aAAA;;;AAAZ;;EAKE,MAAA,GAAS,UAAA;EACT,KAAA;;;;EAIA,GAAA;AAAA;AAAA,KAGU,MAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,aAAA,cAA2B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACvG,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA;EAC3C,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2KW,MAAA,EAAQ,MAAA"}
package/build/uuid/v7.mjs CHANGED
@@ -1,2 +1,2 @@
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};
1
+ 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};
2
2
  //# sourceMappingURL=v7.mjs.map
@@ -1 +1 @@
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
+ {"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":"8PAgCA,MAAM,EAAgB,yEAEhB,EAAY,eACZ,EAAU,WAIV,EAAc,IAAI,WAAW,EAAU,EAevC,EAAiB,CAAE,MAAO,KAAW,IAAK,CAAE,EAElD,SAAS,EAAsB,EAAkB,EAAe,EAAa,EAAiB,EAAsB,CAGlH,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,GACvB,CAEA,SAAS,EAAa,EAAkB,EAAe,EAAa,EAAiB,EAAsB,CACzG,GAAI,CAAC,EAAgB,EAAK,EAAQ,EAAU,EAC1C,MAAM,IAAI,EACR,4BACA,mBAAmB,EAAO,GAAG,EAAS,GAAa,EAAE,yBACvD,EAEF,EAAsB,EAAM,EAAO,EAAK,EAAK,CAAM,CACrD,CAEA,SAAS,EACP,EACA,EACA,EAAS,EACM,CACf,IAAM,EAAW,EAAQ,MACzB,GAAI,IAAa,IAAA,IAAa,CAAC,EAAiB,EAAU,EAAG,CAAS,EACpE,MAAM,IAAI,EACR,8BACA,8CAA8C,GAChD,EAEF,IAAM,EAAS,EAAQ,IACvB,GAAI,IAAW,IAAA,IAAa,CAAC,EAAiB,EAAQ,EAAG,CAAO,EAC9D,MAAM,IAAI,EAAkB,6BAA8B,6CAA6C,GAAS,EAElH,IAAM,EAAY,EAAQ,OAC1B,GAAI,GAAa,EAAU,OAAS,GAClC,MAAM,IAAI,EAAkB,8BAA+B,mCAA8C,EAG3G,IAAM,EAAO,GAAa,EAAI,EACxB,EAAQ,GAAY,KAAK,IAAI,EAE7B,EAAM,GAAW,EAAK,IAAM,GAAO,EAAK,IAAM,GAAO,EAAK,IAAM,EAAK,EAAK,GAOhF,OALI,GACF,EAAa,EAAM,EAAO,EAAK,EAAK,CAAM,EACnC,IAET,EAAsB,EAAM,EAAO,EAAK,EAAa,CAAC,EAC/C,EAAoB,CAAW,EACxC,CAUA,SAAS,EAAyC,EAAyB,EAAY,EAAgC,CACrH,GAAI,EACF,OAAO,EAAc,EAAS,EAAK,CAAM,EAI3C,IAAM,EAAM,KAAK,IAAI,EACf,EAAO,EAAI,EAmBjB,OAhBI,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,UAIN,GACF,EAAa,EAAM,EAAM,MAAO,EAAM,IAAK,EAAK,GAAU,CAAC,EACpD,IAET,EAAsB,EAAM,EAAM,MAAO,EAAM,IAAK,EAAa,CAAC,EAC3D,EAAoB,CAAW,EACxC,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"}
@@ -0,0 +1,2 @@
1
+ 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};
2
+ //# sourceMappingURL=uuid-CnQIYoQi.mjs.map
@@ -0,0 +1 @@
1
+ {"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"}
@@ -0,0 +1,2 @@
1
+ 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};
2
+ //# sourceMappingURL=validation-CTNpXm94.mjs.map
@@ -0,0 +1 @@
1
+ {"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "uniku",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime",
6
6
  "author": {
7
7
  "name": "Alberto Schiabel",
@@ -0,0 +1,7 @@
1
+ export function isIntegerInRange(value: number, min: number, max: number): boolean {
2
+ return Number.isInteger(value) && value >= min && value <= max
3
+ }
4
+
5
+ export function isWritableRange(buffer: Uint8Array, offset: number, byteLength: number): boolean {
6
+ return Number.isInteger(offset) && offset >= 0 && offset + byteLength <= buffer.length
7
+ }
@@ -135,14 +135,18 @@ function getRandomFn(random?: Uint8Array): () => number {
135
135
  // --- Main generator ---
136
136
 
137
137
  function cuid2Fn(options?: Cuid2Options): string {
138
- const length = options?.length ?? DEFAULT_LENGTH
138
+ const requestedLength = options?.length
139
139
 
140
- if (length < MIN_LENGTH || length > MAX_LENGTH) {
140
+ if (
141
+ requestedLength !== undefined &&
142
+ (!Number.isInteger(requestedLength) || requestedLength < MIN_LENGTH || requestedLength > MAX_LENGTH)
143
+ ) {
141
144
  throw new InvalidInputError(
142
145
  'CUID2_LENGTH_OUT_OF_RANGE',
143
- `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`,
146
+ `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${requestedLength}`,
144
147
  )
145
148
  }
149
+ const length = requestedLength ?? DEFAULT_LENGTH
146
150
 
147
151
  const random = getRandomFn(options?.random)
148
152
 
@@ -1,5 +1,6 @@
1
1
  import { writeTimestamp32 } from '../common/bytes'
2
2
  import { rng } from '../common/random'
3
+ import { isWritableRange } from '../common/validation'
3
4
  import { BufferError, InvalidInputError } from '../errors'
4
5
  import { decodeBase62, encodeBase62 } from './base62'
5
6
 
@@ -58,19 +59,9 @@ export type Ksuid = {
58
59
  }
59
60
 
60
61
  /**
61
- * Write KSUID bytes to a buffer.
62
+ * Write already-validated KSUID fields to a buffer.
62
63
  */
63
- function ksuidBytes(timestamp: number, payload: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {
64
- if (!buf) {
65
- buf = new Uint8Array(KSUID_BYTES)
66
- offset = 0
67
- } else if (offset < 0 || offset + KSUID_BYTES > buf.length) {
68
- throw new BufferError(
69
- 'KSUID_BUFFER_OUT_OF_BOUNDS',
70
- `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,
71
- )
72
- }
73
-
64
+ function writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {
74
65
  // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3
75
66
  writeTimestamp32(buf, offset, timestamp)
76
67
 
@@ -78,8 +69,6 @@ function ksuidBytes(timestamp: number, payload: Uint8Array, buf?: Uint8Array, of
78
69
  for (let i = 0; i < PAYLOAD_BYTES; i += 1) {
79
70
  buf[offset + TIMESTAMP_BYTES + i] = payload[i]
80
71
  }
81
-
82
- return buf
83
72
  }
84
73
 
85
74
  /*
@@ -103,7 +92,7 @@ function ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, b
103
92
  let timestamp: number
104
93
  const secs = options?.secs
105
94
  if (secs !== undefined) {
106
- if (secs < KSUID_EPOCH) {
95
+ if (!Number.isInteger(secs) || secs < KSUID_EPOCH) {
107
96
  throw new InvalidInputError('KSUID_TIMESTAMP_TOO_LOW', 'Timestamp must be >= KSUID epoch')
108
97
  }
109
98
 
@@ -126,11 +115,18 @@ function ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, b
126
115
  const payload = random ?? rng()
127
116
 
128
117
  if (buf) {
129
- ksuidBytes(timestamp, payload, buf, offset)
118
+ if (!isWritableRange(buf, offset, KSUID_BYTES)) {
119
+ throw new BufferError(
120
+ 'KSUID_BUFFER_OUT_OF_BOUNDS',
121
+ `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,
122
+ )
123
+ }
124
+ writeKsuidBytesUnchecked(timestamp, payload, buf, offset)
130
125
  return buf
131
126
  }
132
127
 
133
- const bytes = ksuidBytes(timestamp, payload)
128
+ const bytes = new Uint8Array(KSUID_BYTES)
129
+ writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)
134
130
 
135
131
  // String mode: create bytes then encode to Base62
136
132
  return encodeBase62(bytes)
@@ -150,6 +146,12 @@ function toBytes(id: string): Uint8Array {
150
146
  * Convert 20 bytes to a KSUID string.
151
147
  */
152
148
  function fromBytes(bytes: Uint8Array): string {
149
+ if (bytes.length !== KSUID_BYTES) {
150
+ throw new BufferError(
151
+ 'KSUID_BYTES_INVALID_LENGTH',
152
+ `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,
153
+ )
154
+ }
153
155
  return encodeBase62(bytes)
154
156
  }
155
157
 
@@ -1,5 +1,6 @@
1
1
  import { writeTimestamp32 } from '../common/bytes'
2
2
  import { randomBytes, randomUint32 } from '../common/random'
3
+ import { isIntegerInRange, isWritableRange } from '../common/validation'
3
4
  import { BufferError, InvalidInputError } from '../errors'
4
5
  import { decodeObjectIdHex, encodeObjectIdHex } from './hex'
5
6
 
@@ -85,17 +86,13 @@ function freshRandom(): Uint8Array {
85
86
  return bytes
86
87
  }
87
88
 
88
- function objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?: Uint8Array, offset = 0): Uint8Array {
89
- if (!buf) {
90
- buf = new Uint8Array(OBJECTID_BYTES)
91
- offset = 0
92
- } else if (offset < 0 || offset + OBJECTID_BYTES > buf.length) {
93
- throw new BufferError(
94
- 'OBJECTID_BUFFER_OUT_OF_BOUNDS',
95
- `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,
96
- )
97
- }
98
-
89
+ function writeObjectIdBytesUnchecked(
90
+ secs: number,
91
+ random: Uint8Array,
92
+ counter: number,
93
+ buf: Uint8Array,
94
+ offset: number,
95
+ ): void {
99
96
  // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3
100
97
  writeTimestamp32(buf, offset, secs)
101
98
 
@@ -108,8 +105,6 @@ function objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?:
108
105
  buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff
109
106
  buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff
110
107
  buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff
111
-
112
- return buf
113
108
  }
114
109
 
115
110
  /*
@@ -143,12 +138,12 @@ function objectIdFn<TBuf extends Uint8Array = Uint8Array>(
143
138
  }
144
139
 
145
140
  const optSecs = options.secs
146
- if (optSecs !== undefined && (optSecs < 0 || optSecs > MAX_SECS)) {
141
+ if (optSecs !== undefined && !isIntegerInRange(optSecs, 0, MAX_SECS)) {
147
142
  throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)
148
143
  }
149
144
 
150
145
  const optCounter = options.counter
151
- if (optCounter !== undefined && (optCounter < 0 || optCounter > MAX_COUNTER)) {
146
+ if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {
152
147
  throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)
153
148
  }
154
149
 
@@ -183,11 +178,18 @@ function objectIdFn<TBuf extends Uint8Array = Uint8Array>(
183
178
  }
184
179
 
185
180
  if (buf) {
186
- objectIdBytes(secs, random, counter, buf, offset)
181
+ if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {
182
+ throw new BufferError(
183
+ 'OBJECTID_BUFFER_OUT_OF_BOUNDS',
184
+ `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,
185
+ )
186
+ }
187
+ writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)
187
188
  return buf
188
189
  }
189
190
 
190
- const bytes = objectIdBytes(secs, random, counter)
191
+ const bytes = new Uint8Array(OBJECTID_BYTES)
192
+ writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)
191
193
  return encodeObjectIdHex(bytes)
192
194
  }
193
195
 
@@ -202,6 +204,12 @@ function toBytes(id: string): Uint8Array {
202
204
  * Convert 12 bytes to an ObjectID hex string.
203
205
  */
204
206
  function fromBytes(bytes: Uint8Array): string {
207
+ if (bytes.length !== OBJECTID_BYTES) {
208
+ throw new BufferError(
209
+ 'OBJECTID_BYTES_INVALID_LENGTH',
210
+ `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,
211
+ )
212
+ }
205
213
  return encodeObjectIdHex(bytes)
206
214
  }
207
215
 
package/src/tsid/tsid.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomUint32 } from '../common/random'
2
+ import { isIntegerInRange, isWritableRange } from '../common/validation'
2
3
  import { BufferError, InvalidInputError } from '../errors'
3
4
  import { decodeTsidString, encodeTsidString } from './crockford64'
4
5
 
@@ -102,22 +103,60 @@ function pack(msecsDiff: bigint, node: number, counterBits: number, counter: num
102
103
  return (msecsDiff << BigInt(RANDOM_BITS)) | (BigInt(node) << BigInt(counterBits)) | BigInt(counter)
103
104
  }
104
105
 
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
-
106
+ function writeTsidBytesUnchecked(value: bigint, buf: Uint8Array, offset: number): void {
116
107
  for (let i = 0; i < TSID_BYTES; i += 1) {
117
108
  buf[offset + i] = Number((value >> BigInt((TSID_BYTES - 1 - i) * 8)) & 0xffn)
118
109
  }
110
+ }
111
+
112
+ type ResolvedTsidOptions = {
113
+ msecsDiff: bigint
114
+ node: number
115
+ counterBits: number
116
+ counter: number
117
+ }
118
+
119
+ function resolveTsidOptions(options: TsidOptions): ResolvedTsidOptions {
120
+ const nodeBits = options.nodeBits ?? DEFAULT_NODE_BITS
121
+ if (!isIntegerInRange(nodeBits, 0, MAX_NODE_BITS)) {
122
+ throw new InvalidInputError('TSID_NODE_BITS_OUT_OF_RANGE', `nodeBits must be between 0 and ${MAX_NODE_BITS}`)
123
+ }
119
124
 
120
- return buf
125
+ const counterBits = RANDOM_BITS - nodeBits
126
+ const nodeMask = (1 << nodeBits) - 1
127
+ const counterMask = (1 << counterBits) - 1
128
+ const optNode = options.node
129
+ if (optNode !== undefined && !isIntegerInRange(optNode, 0, nodeMask)) {
130
+ throw new InvalidInputError('TSID_NODE_OUT_OF_RANGE', `node must be between 0 and ${nodeMask}`)
131
+ }
132
+
133
+ const optCounter = options.counter
134
+ if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, counterMask)) {
135
+ throw new InvalidInputError('TSID_COUNTER_OUT_OF_RANGE', `counter must be between 0 and ${counterMask}`)
136
+ }
137
+
138
+ const epoch = options.epoch ?? TSID_EPOCH
139
+ if (!Number.isInteger(epoch)) {
140
+ throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer')
141
+ }
142
+ const msecs = options.msecs ?? Date.now()
143
+ if (!Number.isInteger(msecs)) {
144
+ throw new InvalidInputError('TSID_TIMESTAMP_INVALID', 'msecs must be a finite integer')
145
+ }
146
+ const msecsDiff = BigInt(msecs) - BigInt(epoch)
147
+ if (msecsDiff < 0n || msecsDiff > MAX_TIMESTAMP_DIFF) {
148
+ throw new InvalidInputError(
149
+ 'TSID_TIMESTAMP_OUT_OF_RANGE',
150
+ `msecs - epoch must be between 0 and ${MAX_TIMESTAMP_DIFF}`,
151
+ )
152
+ }
153
+
154
+ return {
155
+ msecsDiff,
156
+ node: optNode ?? randomUint32() & nodeMask,
157
+ counterBits,
158
+ counter: optCounter ?? randomUint32() & counterMask,
159
+ }
121
160
  }
122
161
 
123
162
  /*
@@ -139,44 +178,11 @@ function tsidFn<TBuf extends Uint8Array = Uint8Array>(options?: TsidOptions, buf
139
178
  let counter: number
140
179
 
141
180
  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
181
+ const resolved = resolveTsidOptions(options)
182
+ msecsDiff = resolved.msecsDiff
183
+ node = resolved.node
184
+ counterBits = resolved.counterBits
185
+ counter = resolved.counter
180
186
  } else {
181
187
  // Lazily initialize the persistent node ID on first no-option call.
182
188
  if (state.node === undefined) {
@@ -212,7 +218,13 @@ function tsidFn<TBuf extends Uint8Array = Uint8Array>(options?: TsidOptions, buf
212
218
  const packed = pack(msecsDiff, node, counterBits, counter)
213
219
 
214
220
  if (buf) {
215
- writeTsidBytes(packed, buf, offset)
221
+ if (!isWritableRange(buf, offset, TSID_BYTES)) {
222
+ throw new BufferError(
223
+ 'TSID_BUFFER_OUT_OF_BOUNDS',
224
+ `TSID byte range ${offset}:${offset + TSID_BYTES - 1} is out of buffer bounds`,
225
+ )
226
+ }
227
+ writeTsidBytesUnchecked(packed, buf, offset)
216
228
  return buf
217
229
  }
218
230
 
@@ -223,7 +235,10 @@ function tsidFn<TBuf extends Uint8Array = Uint8Array>(options?: TsidOptions, buf
223
235
  * Convert a TSID bigint to 8 bytes.
224
236
  */
225
237
  function toBytes(id: bigint): Uint8Array {
226
- return writeTsidBytes(id)
238
+ assertValidTsid(id)
239
+ const bytes = new Uint8Array(TSID_BYTES)
240
+ writeTsidBytesUnchecked(id, bytes, 0)
241
+ return bytes
227
242
  }
228
243
 
229
244
  /**
@@ -250,6 +265,10 @@ function fromBytes(bytes: Uint8Array): bigint {
250
265
  * `epoch` used at generation time if it was overridden from the default.
251
266
  */
252
267
  function timestamp(id: bigint, epoch: number = TSID_EPOCH): number {
268
+ assertValidTsid(id)
269
+ if (!Number.isInteger(epoch)) {
270
+ throw new InvalidInputError('TSID_EPOCH_INVALID', 'epoch must be a finite integer')
271
+ }
253
272
  return epoch + Number(id >> BigInt(RANDOM_BITS))
254
273
  }
255
274
 
@@ -263,6 +282,17 @@ function isValid(id: unknown): id is bigint {
263
282
  const NIL = 0n
264
283
  const MAX = (1n << 64n) - 1n
265
284
 
285
+ function assertValidTsid(id: bigint): void {
286
+ if (id < NIL || id > MAX) {
287
+ throw new InvalidInputError('TSID_VALUE_OUT_OF_RANGE', `TSID value must be between ${NIL} and ${MAX}`)
288
+ }
289
+ }
290
+
291
+ function toString(id: bigint): string {
292
+ assertValidTsid(id)
293
+ return encodeTsidString(id)
294
+ }
295
+
266
296
  /**
267
297
  * Generate a TSID bigint or write the bytes into a buffer.
268
298
  *
@@ -299,7 +329,7 @@ const MAX = (1n << 64n) - 1n
299
329
  export const tsid: Tsid = Object.assign(tsidFn, {
300
330
  toBytes,
301
331
  fromBytes,
302
- toString: encodeTsidString,
332
+ toString,
303
333
  fromString: decodeTsidString,
304
334
  timestamp,
305
335
  isValid,
@@ -231,8 +231,8 @@ export function decodeToBytes(str: string): Uint8Array {
231
231
  * Encode 16 bytes to a 26-character ULID string.
232
232
  */
233
233
  export function bytesToUlid(bytes: Uint8Array): string {
234
- if (bytes.length < 16) {
235
- throw new BufferError('ULID_BYTES_TOO_SHORT', 'Byte array must be at least 16 bytes')
234
+ if (bytes.length !== 16) {
235
+ throw new BufferError('ULID_BYTES_INVALID_LENGTH', `ULID bytes must be exactly 16 bytes, got ${bytes.length}`)
236
236
  }
237
237
 
238
238
  // Timestamp: bytes 0-5 -> 10 characters
package/src/ulid/ulid.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { incrementBytesInPlace, writeTimestamp48 } from '../common/bytes'
2
2
  import { rng } from '../common/random'
3
+ import { isIntegerInRange, isWritableRange } from '../common/validation'
3
4
  import { BufferError, InvalidInputError } from '../errors'
4
5
  import { bytesToUlid, decodeToBytes, decodeUlidTime, encodeRandom, encodeTime } from './crockford'
5
6
 
@@ -32,6 +33,9 @@ export type Ulid = {
32
33
 
33
34
  // Validation regex: first char [0-7] to prevent overflow, rest from Crockford alphabet
34
35
  const ULID_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i
36
+ const ULID_BYTES = 16
37
+ const RANDOM_BYTES = 10
38
+ const MAX_MSECS = 0xffffffffffff
35
39
 
36
40
  type UlidState = {
37
41
  msecs: number
@@ -48,29 +52,27 @@ type UlidState = {
48
52
  */
49
53
  const state: UlidState = {
50
54
  msecs: -Infinity,
51
- lastRandom: new Uint8Array(10),
55
+ lastRandom: new Uint8Array(RANDOM_BYTES),
52
56
  }
53
57
 
54
- function ulidBytes(time: number, random: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {
55
- if (!buf) {
56
- buf = new Uint8Array(16)
57
- offset = 0
58
- } else if (offset < 0 || offset + 16 > buf.length) {
59
- throw new BufferError(
60
- 'ULID_BUFFER_OUT_OF_BOUNDS',
61
- `ULID byte range ${offset}:${offset + 15} is out of buffer bounds`,
62
- )
63
- }
64
-
58
+ function writeUlidBytesUnchecked(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void {
65
59
  // Timestamp (48-bit big-endian milliseconds since Unix epoch) -> bytes 0-5
66
60
  writeTimestamp48(buf, offset, time)
67
61
 
68
62
  // Random (80 bits) -> bytes 6-15
69
- for (let i = 0; i < 10; i += 1) {
63
+ for (let i = 0; i < RANDOM_BYTES; i += 1) {
70
64
  buf[offset + 6 + i] = random[i]
71
65
  }
66
+ }
72
67
 
73
- return buf
68
+ function writeUlidBytes(time: number, random: Uint8Array, buf: Uint8Array, offset: number): void {
69
+ if (!isWritableRange(buf, offset, ULID_BYTES)) {
70
+ throw new BufferError(
71
+ 'ULID_BUFFER_OUT_OF_BOUNDS',
72
+ `ULID byte range ${offset}:${offset + ULID_BYTES - 1} is out of buffer bounds`,
73
+ )
74
+ }
75
+ writeUlidBytesUnchecked(time, random, buf, offset)
74
76
  }
75
77
 
76
78
  /*
@@ -97,27 +99,36 @@ function ulidFn<TBuf extends Uint8Array = Uint8Array>(options?: UlidOptions, buf
97
99
  * - all ULIDs generated within a single request will have the same timestamp.
98
100
  * - the monotonic ordering will rely entirely on incrementing the random portion.
99
101
  */
100
- const defaultTime = Date.now()
101
-
102
102
  if (options) {
103
103
  // Explicit options provided - use them directly without monotonic state
104
- time = options.msecs ?? defaultTime
105
- if (options.random) {
106
- if (options.random.length < 10) {
107
- throw new InvalidInputError('ULID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 10 for ULID')
104
+ const optMsecs = options.msecs
105
+ if (optMsecs !== undefined && !isIntegerInRange(optMsecs, 0, MAX_MSECS)) {
106
+ throw new InvalidInputError(
107
+ 'ULID_TIMESTAMP_OUT_OF_RANGE',
108
+ `Timestamp must be an integer between 0 and ${MAX_MSECS}`,
109
+ )
110
+ }
111
+ time = optMsecs ?? Date.now()
112
+ const optRandom = options.random
113
+ if (optRandom) {
114
+ if (optRandom.length < RANDOM_BYTES) {
115
+ throw new InvalidInputError(
116
+ 'ULID_RANDOM_BYTES_TOO_SHORT',
117
+ `Random bytes length must be >= ${RANDOM_BYTES} for ULID`,
118
+ )
108
119
  }
109
- random = options.random
120
+ random = optRandom
110
121
  } else {
111
122
  random = rng()
112
123
  }
113
124
  } else {
114
- time = defaultTime
125
+ time = Date.now()
115
126
 
116
127
  if (time > state.msecs) {
117
128
  // New millisecond: generate fresh random
118
129
  random = rng()
119
130
  state.msecs = time
120
- state.lastRandom.set(random.subarray(0, 10))
131
+ state.lastRandom.set(random.subarray(0, RANDOM_BYTES))
121
132
  } else {
122
133
  // Same millisecond or clock rollback: preserve last timestamp and increment random portion.
123
134
  time = state.msecs
@@ -133,7 +144,7 @@ function ulidFn<TBuf extends Uint8Array = Uint8Array>(options?: UlidOptions, buf
133
144
  }
134
145
 
135
146
  if (buf) {
136
- ulidBytes(time, random, buf, offset)
147
+ writeUlidBytes(time, random, buf, offset)
137
148
  return buf
138
149
  }
139
150