uniku 0.5.0 → 0.7.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Documentation](https://img.shields.io/badge/docs-read-5b5bd6.svg)](https://jkomyno.github.io/uniku/)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- Ten ID strategies behind one consistent, type-safe API. The fastest implementation of each in our [current CI benchmark](https://jkomyno.github.io/uniku/docs/guides/performance/).
9
+ Eleven ID strategies behind one consistent, type-safe API. The fastest implementation of each benchmarked format in our [current CI benchmark](https://jkomyno.github.io/uniku/docs/guides/performance/).
10
10
 
11
11
  > **uniku** */uˈniːku/*: Maltese for "unique"
12
12
 
@@ -21,8 +21,8 @@ const id = uuidv7()
21
21
 
22
22
  ## Why `uniku`?
23
23
 
24
- - **Fastest across the benchmark suite.** Every strategy beats its dedicated npm alternative in the current isolated-process CI benchmark, from 1.2× for UUID v4 and ObjectID to 116× for ULID.
25
- - **Ten strategies, one type-safe API.** UUID v4/v7, ULID, TypeID, CUID v2, Nanoid, KSUID, ObjectID, XID, and TSID each expose a callable generator with matching typed helpers.
24
+ - **Fastest across the benchmark suite.** Every benchmarked strategy beats its dedicated npm alternative in the current isolated-process CI benchmark, from 1.2× for UUID v4 and ObjectID to 116× for ULID.
25
+ - **Eleven strategies, one type-safe API.** UUID v4/v5/v7, ULID, TypeID, CUID v2, Nanoid, KSUID, ObjectID, XID, and TSID each expose a callable generator with matching typed helpers.
26
26
  - **Portable by default.** Runs on Node.js, Bun, Deno, browsers, edge runtimes, and Cloudflare Workers using `globalThis.crypto`.
27
27
  - **Tree-shakeable entry points.** Import only the generator you use; the package root is intentionally not exported.
28
28
  - **Useful at system boundaries.** Validate unknown input, convert canonical binary formats to bytes, or write directly into a caller-owned buffer.
@@ -36,26 +36,64 @@ npm install uniku
36
36
 
37
37
  The [getting-started guide](https://jkomyno.github.io/uniku/docs/getting-started/) also covers pnpm, Bun, and Deno.
38
38
 
39
- ## Quick start
39
+ ## Public API at a glance
40
40
 
41
- Every generator has its own entry point and keeps related operations on the generator function:
41
+ Each module exports a callable generator with attached helpers. The most common public API looks like this:
42
42
 
43
43
  ```ts
44
44
  import { uuidv7 } from 'uniku/uuid/v7'
45
45
 
46
46
  const id = uuidv7()
47
+ // e.g. '019f5732-0342-75f9-9efd-fc3c1f8da7fd'
48
+
49
+ uuidv7.isValid(id)
50
+ // true
51
+
47
52
  const bytes = uuidv7.toBytes(id)
48
- const restored = uuidv7.fromBytes(bytes)
53
+ // Uint8Array(16)
54
+
55
+ uuidv7.fromBytes(bytes)
56
+ // the same value as `id`
57
+
58
+ uuidv7.timestamp(id)
59
+ // e.g. 1783874323266
49
60
 
50
- if (uuidv7.isValid(restored)) {
51
- console.log(uuidv7.timestamp(restored))
52
- }
61
+ uuidv7.NIL
62
+ // '00000000-0000-0000-0000-000000000000'
63
+
64
+ uuidv7.MAX
65
+ // 'ffffffff-ffff-ffff-ffff-ffffffffffff'
53
66
  ```
54
67
 
68
+ Binary modules can also write directly into a caller-owned buffer:
69
+
70
+ ```ts
71
+ const destination = new Uint8Array(24)
72
+ const result = uuidv7(undefined, destination, 8)
73
+ // Uint8Array(24), with UUID bytes at offsets 8 through 23
74
+
75
+ result === destination
76
+ // true
77
+ ```
78
+
79
+ ### How modules differ
80
+
81
+ | Modules | Public API |
82
+ | --- | --- |
83
+ | `uuid/v7`, `ulid`, `ksuid`, `objectid`, `xid` | Callable, buffer write, `toBytes()`, `fromBytes()`, `timestamp()`, `isValid()`, `NIL`, `MAX` |
84
+ | `uuid/v4` | Callable, buffer write, `toBytes()`, `fromBytes()`, `isValid()`, `NIL`, `MAX` |
85
+ | `uuid/v5` | Name-based callable, buffer write, `namespace()`, `toBytes()`, `fromBytes()`, `isValid()`, `DNS`, `URL`, `OID`, `X500`, `NIL`, `MAX` |
86
+ | `typeid` | Prefix-first callable, buffer write, `toBytes()`, `fromBytes()`, `toUuid()`, `fromUuid()`, `timestamp()`, `prefix()`, `suffix()`, `isValid()` |
87
+ | `tsid` | `bigint` primary value, buffer write, `toBytes()`, `fromBytes()`, `toString()`, `fromString()`, `timestamp()`, `isValid()`, `NIL`, `MAX` |
88
+ | `cuid/v2`, `nanoid` | Callable and `isValid()` only |
89
+
90
+ Open the [generator reference](https://jkomyno.github.io/uniku/docs/reference/entry-points/) for complete signatures, options, constants, and examples.
91
+
55
92
  There are no barrel exports:
56
93
 
57
94
  ```ts
58
95
  import { uuidv4 } from 'uniku/uuid/v4'
96
+ import { uuidv5 } from 'uniku/uuid/v5'
59
97
  import { ulid } from 'uniku/ulid'
60
98
  import { typeid } from 'uniku/typeid'
61
99
  import { cuidv2 } from 'uniku/cuid/v2'
@@ -78,6 +116,7 @@ import { tsid } from 'uniku/tsid'
78
116
  | Go rs/xid compatibility | XID | Matches rs/xid's text and binary representation |
79
117
  | Native `BIGINT` storage | TSID | A sortable 64-bit integer rather than a UUID-shaped string |
80
118
  | Broad standards compatibility | UUID v4 | The conventional random UUID format |
119
+ | An ID derived from something you already have | UUID v5 | The same name and namespace always give the same UUID |
81
120
 
82
121
  The [choosing guide](https://jkomyno.github.io/uniku/docs/guides/choosing-an-id/) covers ordering, timestamp leakage, storage boundaries, and format-specific trade-offs.
83
122
 
@@ -13,9 +13,9 @@ type KsuidOptions = {
13
13
  */
14
14
  msecs?: number;
15
15
  /**
16
- * Timestamp in seconds since the Unix epoch.
16
+ * Deprecated alias for `msecs`; timestamp in seconds since the Unix epoch.
17
17
  *
18
- * @deprecated Use `msecs` instead. Will be removed at v1-rc.
18
+ * @deprecated Use `msecs` instead. Will be removed in `1.0.0-rc.1`.
19
19
  */
20
20
  secs?: number;
21
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError('INVALID_LENGTH', `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`, {\n strategy: 'ksuid',\n })\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid KSUID character: ${str[i]}`, { strategy: 'ksuid' })\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('VALUE_OUT_OF_RANGE', 'KSUID string exceeds 160-bit range', { strategy: 'ksuid' })\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { resolveTimestampSecs } from '../common/timestamp'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * KSUID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n}\n\nexport type Ksuid = {\n /** Generate a time-ordered KSUID string. */\n (): string\n /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a KSUID string with optional timestamp or random payload bytes. */\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n /** Convert a KSUID string to its canonical 20-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 20 canonical KSUID bytes to a KSUID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid KSUID string. */\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID', {\n strategy: 'ksuid',\n })\n }\n\n let timestamp: number\n const secs = options === undefined ? undefined : resolveTimestampSecs(options, KSUID_EPOCH, KSUID_MAX_SECS, 'ksuid')\n if (secs !== undefined) {\n timestamp = secs - KSUID_EPOCH\n } else {\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'ksuid' },\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
1
+ {"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError('INVALID_LENGTH', `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`, {\n strategy: 'ksuid',\n })\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid KSUID character: ${str[i]}`, { strategy: 'ksuid' })\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('VALUE_OUT_OF_RANGE', 'KSUID string exceeds 160-bit range', { strategy: 'ksuid' })\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { resolveTimestampSecs } from '../common/timestamp'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * KSUID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Deprecated alias for `msecs`; timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed in `1.0.0-rc.1`.\n */\n // TODO(v1-rc.1): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n}\n\nexport type Ksuid = {\n /** Generate a time-ordered KSUID string. */\n (): string\n /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a KSUID string with optional timestamp or random payload bytes. */\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n /** Convert a KSUID string to its canonical 20-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 20 canonical KSUID bytes to a KSUID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid KSUID string. */\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID', {\n strategy: 'ksuid',\n })\n }\n\n let timestamp: number\n const secs = options === undefined ? undefined : resolveTimestampSecs(options, KSUID_EPOCH, KSUID_MAX_SECS, 'ksuid')\n if (secs !== undefined) {\n timestamp = secs - KSUID_EPOCH\n } else {\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'ksuid' },\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"wRAcA,MAAM,EAAkB,iEAClB,EAAO,IACP,GAAmB,IAAM,MAAQ,GAUjC,EAAW,IAAI,WAAW,KAAK,EACrC,EAAS,KAAK,GAAG,EAEjB,IAAK,IAAI,EAAI,EAAG,EAAI,GAAwB,GAAK,EAC/C,EAAS,EAAgB,WAAW,CAAC,GAAK,EAS5C,SAAgB,EAAa,EAA2B,CACtD,GAAI,EAAM,OAASA,GACjB,MAAM,IAAI,EACR,uBACA,8CAA0D,EAAM,SAChE,CAAE,SAAU,OAAQ,CACtB,EAIF,IAAI,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAIA,GAAa,GAAK,EACpC,EAAO,GAAO,GAAM,OAAO,EAAM,EAAE,EAKrC,IAAI,EAAU,GACd,KAAO,EAAM,IAAI,CACf,IAAM,EAAY,EAAM,EACxB,GAAY,EACZ,EAAU,EAAgB,OAAO,CAAS,GAAK,CACjD,CAGA,OAAO,EAAQ,SAASC,GAAkB,GAAG,CAC/C,CAQA,SAAgB,EAAa,EAAyB,CACpD,GAAI,EAAI,SAAWA,GACjB,MAAM,IAAI,EAAW,iBAAkB,2CAA4D,EAAI,SAAU,CAC/G,SAAU,OACZ,CAAC,EAIH,IAAI,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAIA,GAAkB,GAAK,EAAG,CAC5C,IAAM,EAAQ,EAAS,EAAI,WAAW,CAAC,GACvC,GAAI,IAAU,IACZ,MAAM,IAAI,EAAW,eAAgB,4BAA4B,EAAI,KAAM,CAAE,SAAU,OAAQ,CAAC,EAElG,EAAM,EAAM,EAAO,OAAO,CAAK,CACjC,CAEA,GAAI,EAAM,EACR,MAAM,IAAI,EAAW,qBAAsB,qCAAsC,CAAE,SAAU,OAAQ,CAAC,EAIxG,IAAM,EAAQ,IAAI,WAAWD,EAAW,EACxC,IAAK,IAAI,EAAIA,GAAiB,GAAK,EAAG,IACpC,EAAM,GAAK,OAAO,EAAM,IAAK,EAC7B,IAAa,GAGf,OAAO,CACT,CCpFA,MAAM,EAAc,KASd,EAAmB,EAAa,IAAI,WAAW,EAAW,CAAC,CAAC,KAAK,GAAI,CAAC,EAKtE,EAAc,oBA8CpB,SAAS,EAAyB,EAAmB,EAAqB,EAAiB,EAAsB,CAE/G,EAAiB,EAAK,EAAQ,CAAS,EAGvC,IAAK,IAAI,EAAI,EAAG,EAAI,GAAe,GAAK,EACtC,EAAI,EAAS,EAAkB,GAAK,EAAQ,EAEhD,CAcA,SAAS,EAA8C,EAAwB,EAAY,EAAS,EAAkB,CACpH,IAAM,EAAS,GAAS,OACxB,GAAI,GAAU,EAAO,OAAS,GAC5B,MAAM,IAAI,EAAkB,yBAA0B,8CAA+C,CACnG,SAAU,OACZ,CAAC,EAGH,IAAI,EACE,EAAO,IAAY,IAAA,GAAY,IAAA,GAAY,EAAqB,EAAS,EAAa,WAAgB,OAAO,EACnH,AACE,EADE,IAAS,IAAA,GAUC,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EAAI,EAThC,EAAO,EAYrB,IAAM,EAAU,GAAU,EAAI,EAE9B,GAAI,EAAK,CACP,GAAI,CAAC,EAAgB,EAAK,EAAQ,EAAW,EAC3C,MAAM,IAAI,EACR,uBACA,oBAAoB,EAAO,GAAG,EAAS,GAAc,EAAE,0BACvD,CAAE,SAAU,OAAQ,CACtB,EAGF,OADA,EAAyB,EAAW,EAAS,EAAK,CAAM,EACjD,CACT,CAEA,IAAM,EAAQ,IAAI,WAAW,EAAW,EAIxC,OAHA,EAAyB,EAAW,EAAS,EAAO,CAAC,EAG9C,EAAa,CAAK,CAC3B,CAQA,SAAS,EAAQ,EAAwB,CACvC,OAAO,EAAa,CAAE,CACxB,CAKA,SAAS,EAAU,EAA2B,CAC5C,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EACR,uBACA,6CAAyD,EAAM,SAC/D,CAAE,SAAU,OAAQ,CACtB,EAEF,OAAO,EAAa,CAAK,CAC3B,CAOA,SAAS,EAAU,EAAoB,CACrC,IAAM,EAAQ,EAAa,CAAE,EAI7B,SAFe,EAAM,IAAM,GAAO,EAAM,IAAM,GAAO,EAAM,IAAM,EAAK,EAAM,MAAQ,GAErE,GAAe,GAChC,CAKA,SAAS,EAAQ,EAA2B,CAC1C,OACE,OAAO,GAAO,UACd,EAAG,SAAW,IACd,EAAY,KAAK,CAAE,GAEnB,GAAM,CAEV,CAMA,MAAa,EAAe,OAAO,OAAO,EAAS,CACjD,UACA,YACA,YACA,UACA,IAAK,8BACL,IAAK,CACP,CAAC"}
@@ -20,9 +20,9 @@ type NanoidOptions = {
20
20
  */
21
21
  length?: number;
22
22
  /**
23
- * Length of generated ID.
23
+ * Deprecated alias for `length`.
24
24
  *
25
- * @deprecated Use `length` instead. Will be removed at v1-rc.
25
+ * @deprecated Use `length` instead. Will be removed in `1.0.0-rc.1`.
26
26
  */
27
27
  size?: number;
28
28
  };
@@ -1 +1 @@
1
- {"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. Translate random bytes\n// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nconst ASCII_DECODER = new TextDecoder()\nlet poolBytes: Uint8Array | undefined\nlet characterPool = ''\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!poolBytes || poolBytes.length < size) {\n poolBytes = new Uint8Array(size)\n }\n if (poolOffset + bytes > characterPool.length) {\n crypto.getRandomValues(poolBytes)\n for (let i = 0; i < poolBytes.length; i++) {\n poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)\n }\n characterPool = ASCII_DECODER.decode(poolBytes)\n poolOffset = 0\n }\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n const id = characterPool.substring(poolOffset, poolOffset + size)\n poolOffset += size\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `length` bytes needed.\n * For other alphabets: ~length * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n length?: number\n /**\n * Length of generated ID.\n *\n * @deprecated Use `length` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must contain at least 2 characters', {\n strategy: 'nanoid',\n })\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must not exceed 256 characters', {\n strategy: 'nanoid',\n })\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n {\n strategy: 'nanoid',\n },\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`, {\n strategy: 'nanoid',\n })\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate length parameter\n */\nfunction validateLength(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', 'Length must be a non-negative integer', { strategy: 'nanoid' })\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', `Length must not exceed ${MAX_SIZE}`, { strategy: 'nanoid' })\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n if (sizeOrOptions.length !== undefined && sizeOrOptions.size !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `length` or `size`, not both', {\n strategy: 'nanoid',\n })\n }\n size = sizeOrOptions.length ?? sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateLength(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n { strategy: 'nanoid' },\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n { strategy: 'nanoid' },\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom length\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', length: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Length must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested length\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
1
+ {"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. Translate random bytes\n// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nconst ASCII_DECODER = new TextDecoder()\nlet poolBytes: Uint8Array | undefined\nlet characterPool = ''\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!poolBytes || poolBytes.length < size) {\n poolBytes = new Uint8Array(size)\n }\n if (poolOffset + bytes > characterPool.length) {\n crypto.getRandomValues(poolBytes)\n for (let i = 0; i < poolBytes.length; i++) {\n poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)\n }\n characterPool = ASCII_DECODER.decode(poolBytes)\n poolOffset = 0\n }\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n const id = characterPool.substring(poolOffset, poolOffset + size)\n poolOffset += size\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `length` bytes needed.\n * For other alphabets: ~length * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n length?: number\n /**\n * Deprecated alias for `length`.\n *\n * @deprecated Use `length` instead. Will be removed in `1.0.0-rc.1`.\n */\n // TODO(v1-rc.1): remove this alias (tracked in docs/STABILITY.md).\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must contain at least 2 characters', {\n strategy: 'nanoid',\n })\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must not exceed 256 characters', {\n strategy: 'nanoid',\n })\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n {\n strategy: 'nanoid',\n },\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`, {\n strategy: 'nanoid',\n })\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate length parameter\n */\nfunction validateLength(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', 'Length must be a non-negative integer', { strategy: 'nanoid' })\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', `Length must not exceed ${MAX_SIZE}`, { strategy: 'nanoid' })\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n if (sizeOrOptions.length !== undefined && sizeOrOptions.size !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `length` or `size`, not both', {\n strategy: 'nanoid',\n })\n }\n size = sizeOrOptions.length ?? sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateLength(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n { strategy: 'nanoid' },\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n { strategy: 'nanoid' },\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom length\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', length: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Length must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested length\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
@@ -13,9 +13,9 @@ type ObjectIdOptions = {
13
13
  */
14
14
  msecs?: number;
15
15
  /**
16
- * Timestamp in seconds since the Unix epoch.
16
+ * Deprecated alias for `msecs`; timestamp in seconds since the Unix epoch.
17
17
  *
18
- * @deprecated Use `msecs` instead. Will be removed at v1-rc.
18
+ * @deprecated Use `msecs` instead. Will be removed in `1.0.0-rc.1`.
19
19
  */
20
20
  secs?: number;
21
21
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES"],"sources":["../../src/objectid/hex.ts","../../src/objectid/objectid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Hex encoding/decoding for ObjectID.\n * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode)\n *\n * ObjectID binary format: 12 bytes (96 bits)\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter\n *\n * String format: 24 characters of hex\n */\n\nconst HEX_CHARS = '0123456789abcdef'\nconst OBJECTID_BYTES = 12\nconst OBJECTID_STRING_LEN = 24\n\n// Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string.\nconst ENCODE_TABLE: string[] = Array.from(\n { length: 256 },\n (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf],\n)\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 128 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i <= 9; i += 1) {\n DECODING['0'.charCodeAt(0) + i] = i\n}\nfor (let i = 0; i < 6; i += 1) {\n DECODING['a'.charCodeAt(0) + i] = 10 + i\n DECODING['A'.charCodeAt(0) + i] = 10 + i\n}\n\n/**\n * Encode 12 ObjectID bytes to a 24-character lowercase hex string.\n */\nexport function encodeObjectIdHex(bytes: Uint8Array): string {\n if (bytes.length < OBJECTID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\n )\n }\n\n let encoded = ''\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n encoded += ENCODE_TABLE[bytes[i]]\n }\n return encoded\n}\n\n/**\n * Decode a 24-character hex string to 12 ObjectID bytes.\n */\nexport function decodeObjectIdHex(str: string): Uint8Array {\n if (str.length !== OBJECTID_STRING_LEN) {\n throw new ParseError(\n 'INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n { strategy: 'objectid' },\n )\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n const hiIndex = i * 2\n const loIndex = hiIndex + 1\n const hi = DECODING[str.charCodeAt(hiIndex)]\n const lo = DECODING[str.charCodeAt(loIndex)]\n\n if (hi === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`, { strategy: 'objectid' })\n }\n if (lo === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`, { strategy: 'objectid' })\n }\n\n bytes[i] = (hi << 4) | lo\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { resolveTimestampSecs } from '../common/timestamp'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeObjectIdHex, encodeObjectIdHex } from './hex'\n\n/**\n * MongoDB ObjectID\n *\n * A 96-bit (12-byte) identifier consisting of:\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0)\n *\n * Encoded as a 24-character lowercase hex string.\n */\n\nconst OBJECTID_BYTES = 12\nconst TIMESTAMP_BYTES = 4\nconst RANDOM_BYTES = 5\nconst COUNTER_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_COUNTER = 0xffffff\n\n// Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid()\nconst OBJECTID_REGEX = /^[0-9a-f]{24}$/i\n\nexport type ObjectIdOptions = {\n /**\n * 5 bytes of random data to use for the ObjectID's random field.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * ObjectID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n /** Generate a MongoDB-compatible ObjectID string. */\n (): string\n /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate an ObjectID string with optional timestamp, random field, or counter. */\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n /** Convert an ObjectID string to its canonical 12-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 12 canonical ObjectID bytes to an ObjectID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid ObjectID string. */\n isValid(id: unknown): id is string\n /** The nil ObjectID (all zeros) */\n NIL: string\n /** The max ObjectID (all 0xff) */\n MAX: string\n}\n\ntype ObjectIdState = {\n random: Uint8Array | undefined\n counter: number | undefined\n}\n\n/**\n * Module-level state for the per-process random value and the always-incrementing counter.\n *\n * IMPORTANT: This state persists across all objectid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb\n * across warm starts is exactly the anti-collision behavior the ObjectID spec intends.\n * - For isolated state, pass explicit `random`, `msecs`, or `counter` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: ObjectIdState = {\n random: undefined,\n counter: undefined,\n}\n\n/**\n * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the\n * result is unaffected by any later pool refill triggered by other random draws\n * (e.g. the counter's `randomUint32()` call) before these bytes are consumed.\n */\nfunction freshRandom(): Uint8Array {\n const bytes = new Uint8Array(RANDOM_BYTES)\n bytes.set(randomBytes(RANDOM_BYTES))\n return bytes\n}\n\nfunction writeObjectIdBytesUnchecked(\n secs: number,\n random: Uint8Array,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, secs)\n\n // Random (5 bytes) -> bytes 4-8\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = random[i]\n }\n\n // Counter (24-bit big-endian) -> bytes 9-11\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff\n}\n\n/*\n * Overload: no buffer => return an ObjectID string.\n */\nfunction objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ObjectID bytes.\n */\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options: ObjectIdOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options?: ObjectIdOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n let secs: number\n let random: Uint8Array\n let counter: number\n\n if (options) {\n const optRandom = options.random\n if (optRandom && optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n {\n strategy: 'objectid',\n },\n )\n }\n\n const optSecs = resolveTimestampSecs(options, 0, MAX_SECS, 'objectid')\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`, {\n strategy: 'objectid',\n })\n }\n\n // Options bypass persistent state entirely: every field is sourced fresh (given\n // value, or an independently-defaulted one), never read from or written to\n // `state` (see KTD2). This is what makes options-based generation deterministic\n // when all three fields are supplied.\n secs = optSecs ?? Math.floor(Date.now() / 1000)\n random = optRandom ?? freshRandom()\n counter = optCounter ?? randomUint32() & MAX_COUNTER\n } else {\n // Lazily initialize persistent state on first no-option call.\n if (state.random === undefined) {\n state.random = freshRandom()\n }\n if (state.counter === undefined) {\n state.counter = randomUint32() & MAX_COUNTER\n }\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ObjectIDs generated within a single request will share the same timestamp.\n * - monotonic ordering relies entirely on the ever-incrementing counter.\n */\n secs = Math.floor(Date.now() / 1000)\n random = state.random\n state.counter = (state.counter + 1) & MAX_COUNTER\n counter = state.counter\n }\n\n if (buf) {\n if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'objectid' },\n )\n }\n writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Convert an ObjectID hex string to 12 bytes.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeObjectIdHex(id)\n}\n\n/**\n * Convert 12 bytes to an ObjectID hex string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== OBJECTID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\n )\n }\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Extract the timestamp from an ObjectID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid.\n * Note: ObjectID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeObjectIdHex(id)\n // First 4 bytes are big-endian timestamp (seconds since Unix epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n return secs * 1000\n}\n\n/**\n * Validate an ObjectID string format.\n * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing\n * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though\n * generation always emits lowercase (matching MongoDB driver output).\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && OBJECTID_REGEX.test(id)\n}\n\n/**\n * Generate a MongoDB ObjectID string or write the bytes into a buffer.\n *\n * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte\n * per-process random value, and a 3-byte always-incrementing counter, encoded\n * as a 24-character lowercase hex string. It is time-ordered and compatible\n * with MongoDB's own ObjectID implementation.\n *\n * @example\n * ```ts\n * import { objectid } from 'uniku/objectid'\n *\n * const id = objectid()\n * // => \"667c3f2a1e2b3c4d5e6f7081\"\n *\n * // Extract timestamp\n * const ts = objectid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * objectid.isValid(id) // true\n *\n * // Convert to/from bytes (12 bytes)\n * const bytes = objectid.toBytes(id)\n * const restored = objectid.fromBytes(bytes)\n * ```\n */\nexport const objectid: ObjectId = Object.assign(objectIdFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(24),\n MAX: 'f'.repeat(24),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
1
+ {"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES"],"sources":["../../src/objectid/hex.ts","../../src/objectid/objectid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Hex encoding/decoding for ObjectID.\n * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode)\n *\n * ObjectID binary format: 12 bytes (96 bits)\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter\n *\n * String format: 24 characters of hex\n */\n\nconst HEX_CHARS = '0123456789abcdef'\nconst OBJECTID_BYTES = 12\nconst OBJECTID_STRING_LEN = 24\n\n// Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string.\nconst ENCODE_TABLE: string[] = Array.from(\n { length: 256 },\n (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf],\n)\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 128 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i <= 9; i += 1) {\n DECODING['0'.charCodeAt(0) + i] = i\n}\nfor (let i = 0; i < 6; i += 1) {\n DECODING['a'.charCodeAt(0) + i] = 10 + i\n DECODING['A'.charCodeAt(0) + i] = 10 + i\n}\n\n/**\n * Encode 12 ObjectID bytes to a 24-character lowercase hex string.\n */\nexport function encodeObjectIdHex(bytes: Uint8Array): string {\n if (bytes.length < OBJECTID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\n )\n }\n\n let encoded = ''\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n encoded += ENCODE_TABLE[bytes[i]]\n }\n return encoded\n}\n\n/**\n * Decode a 24-character hex string to 12 ObjectID bytes.\n */\nexport function decodeObjectIdHex(str: string): Uint8Array {\n if (str.length !== OBJECTID_STRING_LEN) {\n throw new ParseError(\n 'INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n { strategy: 'objectid' },\n )\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n const hiIndex = i * 2\n const loIndex = hiIndex + 1\n const hi = DECODING[str.charCodeAt(hiIndex)]\n const lo = DECODING[str.charCodeAt(loIndex)]\n\n if (hi === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`, { strategy: 'objectid' })\n }\n if (lo === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`, { strategy: 'objectid' })\n }\n\n bytes[i] = (hi << 4) | lo\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { resolveTimestampSecs } from '../common/timestamp'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeObjectIdHex, encodeObjectIdHex } from './hex'\n\n/**\n * MongoDB ObjectID\n *\n * A 96-bit (12-byte) identifier consisting of:\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0)\n *\n * Encoded as a 24-character lowercase hex string.\n */\n\nconst OBJECTID_BYTES = 12\nconst TIMESTAMP_BYTES = 4\nconst RANDOM_BYTES = 5\nconst COUNTER_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_COUNTER = 0xffffff\n\n// Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid()\nconst OBJECTID_REGEX = /^[0-9a-f]{24}$/i\n\nexport type ObjectIdOptions = {\n /**\n * 5 bytes of random data to use for the ObjectID's random field.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * ObjectID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Deprecated alias for `msecs`; timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed in `1.0.0-rc.1`.\n */\n // TODO(v1-rc.1): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n /** Generate a MongoDB-compatible ObjectID string. */\n (): string\n /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate an ObjectID string with optional timestamp, random field, or counter. */\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n /** Convert an ObjectID string to its canonical 12-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 12 canonical ObjectID bytes to an ObjectID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid ObjectID string. */\n isValid(id: unknown): id is string\n /** The nil ObjectID (all zeros) */\n NIL: string\n /** The max ObjectID (all 0xff) */\n MAX: string\n}\n\ntype ObjectIdState = {\n random: Uint8Array | undefined\n counter: number | undefined\n}\n\n/**\n * Module-level state for the per-process random value and the always-incrementing counter.\n *\n * IMPORTANT: This state persists across all objectid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb\n * across warm starts is exactly the anti-collision behavior the ObjectID spec intends.\n * - For isolated state, pass explicit `random`, `msecs`, or `counter` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: ObjectIdState = {\n random: undefined,\n counter: undefined,\n}\n\n/**\n * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the\n * result is unaffected by any later pool refill triggered by other random draws\n * (e.g. the counter's `randomUint32()` call) before these bytes are consumed.\n */\nfunction freshRandom(): Uint8Array {\n const bytes = new Uint8Array(RANDOM_BYTES)\n bytes.set(randomBytes(RANDOM_BYTES))\n return bytes\n}\n\nfunction writeObjectIdBytesUnchecked(\n secs: number,\n random: Uint8Array,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, secs)\n\n // Random (5 bytes) -> bytes 4-8\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = random[i]\n }\n\n // Counter (24-bit big-endian) -> bytes 9-11\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff\n}\n\n/*\n * Overload: no buffer => return an ObjectID string.\n */\nfunction objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ObjectID bytes.\n */\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options: ObjectIdOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options?: ObjectIdOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n let secs: number\n let random: Uint8Array\n let counter: number\n\n if (options) {\n const optRandom = options.random\n if (optRandom && optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n {\n strategy: 'objectid',\n },\n )\n }\n\n const optSecs = resolveTimestampSecs(options, 0, MAX_SECS, 'objectid')\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`, {\n strategy: 'objectid',\n })\n }\n\n // Options bypass persistent state entirely: every field is sourced fresh (given\n // value, or an independently-defaulted one), never read from or written to\n // `state` (see KTD2). This is what makes options-based generation deterministic\n // when all three fields are supplied.\n secs = optSecs ?? Math.floor(Date.now() / 1000)\n random = optRandom ?? freshRandom()\n counter = optCounter ?? randomUint32() & MAX_COUNTER\n } else {\n // Lazily initialize persistent state on first no-option call.\n if (state.random === undefined) {\n state.random = freshRandom()\n }\n if (state.counter === undefined) {\n state.counter = randomUint32() & MAX_COUNTER\n }\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ObjectIDs generated within a single request will share the same timestamp.\n * - monotonic ordering relies entirely on the ever-incrementing counter.\n */\n secs = Math.floor(Date.now() / 1000)\n random = state.random\n state.counter = (state.counter + 1) & MAX_COUNTER\n counter = state.counter\n }\n\n if (buf) {\n if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'objectid' },\n )\n }\n writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Convert an ObjectID hex string to 12 bytes.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeObjectIdHex(id)\n}\n\n/**\n * Convert 12 bytes to an ObjectID hex string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== OBJECTID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\n )\n }\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Extract the timestamp from an ObjectID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid.\n * Note: ObjectID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeObjectIdHex(id)\n // First 4 bytes are big-endian timestamp (seconds since Unix epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n return secs * 1000\n}\n\n/**\n * Validate an ObjectID string format.\n * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing\n * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though\n * generation always emits lowercase (matching MongoDB driver output).\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && OBJECTID_REGEX.test(id)\n}\n\n/**\n * Generate a MongoDB ObjectID string or write the bytes into a buffer.\n *\n * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte\n * per-process random value, and a 3-byte always-incrementing counter, encoded\n * as a 24-character lowercase hex string. It is time-ordered and compatible\n * with MongoDB's own ObjectID implementation.\n *\n * @example\n * ```ts\n * import { objectid } from 'uniku/objectid'\n *\n * const id = objectid()\n * // => \"667c3f2a1e2b3c4d5e6f7081\"\n *\n * // Extract timestamp\n * const ts = objectid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * objectid.isValid(id) // true\n *\n * // Convert to/from bytes (12 bytes)\n * const bytes = objectid.toBytes(id)\n * const restored = objectid.fromBytes(bytes)\n * ```\n */\nexport const objectid: ObjectId = Object.assign(objectIdFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(24),\n MAX: 'f'.repeat(24),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"timestamp-ChrSuQCR.mjs","names":[],"sources":["../src/common/timestamp.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\nimport type { IdGenerator } from '../generators'\nimport { isIntegerInRange } from './validation'\n\n/**\n * Timestamp options accepted by second-precision generators (ksuid, objectid,\n * xid) during the pre-v1 transition to unified millisecond inputs.\n */\nexport type TimestampSecsOptions = {\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * The generator stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n}\n\n/**\n * Resolve caller-provided timestamp options to whole seconds since the Unix\n * epoch, or `undefined` when neither option was supplied.\n *\n * `msecs` is the unified input and is truncated to whole seconds; `secs` is\n * the deprecated pre-v1 alias. Passing both is an error.\n *\n * TODO(v1-rc): drop the `secs` branch once the alias is removed.\n */\nexport function resolveTimestampSecs(\n options: TimestampSecsOptions,\n minSecs: number,\n maxSecs: number,\n strategy: IdGenerator,\n): number | undefined {\n const { msecs, secs } = options\n\n if (msecs !== undefined && secs !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `msecs` or `secs`, not both', { strategy })\n }\n\n if (msecs !== undefined) {\n const minMsecs = minSecs * 1000\n const maxMsecs = maxSecs * 1000 + 999\n if (!isIntegerInRange(msecs, minMsecs, maxMsecs)) {\n throw new InvalidInputError(\n 'TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between ${minMsecs} and ${maxMsecs} milliseconds`,\n { strategy },\n )\n }\n return Math.floor(msecs / 1000)\n }\n\n if (secs !== undefined) {\n if (!isIntegerInRange(secs, minSecs, maxSecs)) {\n throw new InvalidInputError(\n 'TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between ${minSecs} and ${maxSecs} seconds`,\n { strategy },\n )\n }\n return secs\n }\n\n return undefined\n}\n"],"mappings":"+FAgCA,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,GAAM,CAAE,QAAO,QAAS,EAExB,GAAI,IAAU,IAAA,IAAa,IAAS,IAAA,GAClC,MAAM,IAAI,EAAkB,sBAAuB,+CAAgD,CAAE,UAAS,CAAC,EAGjH,GAAI,IAAU,IAAA,GAAW,CACvB,IAAM,EAAW,EAAU,IACrB,EAAW,EAAU,IAAO,IAClC,GAAI,CAAC,EAAiB,EAAO,EAAU,CAAQ,EAC7C,MAAM,IAAI,EACR,yBACA,wCAAwC,EAAS,OAAO,EAAS,eACjE,CAAE,UAAS,CACb,EAEF,OAAO,KAAK,MAAM,EAAQ,GAAI,CAChC,CAEA,GAAI,IAAS,IAAA,GAAW,CACtB,GAAI,CAAC,EAAiB,EAAM,EAAS,CAAO,EAC1C,MAAM,IAAI,EACR,yBACA,wCAAwC,EAAQ,OAAO,EAAQ,UAC/D,CAAE,UAAS,CACb,EAEF,OAAO,CACT,CAGF"}
1
+ {"version":3,"file":"timestamp-ChrSuQCR.mjs","names":[],"sources":["../src/common/timestamp.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\nimport type { IdGenerator } from '../generators'\nimport { isIntegerInRange } from './validation'\n\n/**\n * Timestamp options accepted by second-precision generators (ksuid, objectid,\n * xid) during the pre-v1 transition to unified millisecond inputs.\n */\nexport type TimestampSecsOptions = {\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * The generator stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Deprecated alias for `msecs`; timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed in `1.0.0-rc.1`.\n */\n // TODO(v1-rc.1): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n}\n\n/**\n * Resolve caller-provided timestamp options to whole seconds since the Unix\n * epoch, or `undefined` when neither option was supplied.\n *\n * `msecs` is the unified input and is truncated to whole seconds; `secs` is\n * the deprecated pre-v1 alias. Passing both is an error.\n *\n * TODO(v1-rc.1): drop the `secs` branch once the alias is removed.\n */\nexport function resolveTimestampSecs(\n options: TimestampSecsOptions,\n minSecs: number,\n maxSecs: number,\n strategy: IdGenerator,\n): number | undefined {\n const { msecs, secs } = options\n\n if (msecs !== undefined && secs !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `msecs` or `secs`, not both', { strategy })\n }\n\n if (msecs !== undefined) {\n const minMsecs = minSecs * 1000\n const maxMsecs = maxSecs * 1000 + 999\n if (!isIntegerInRange(msecs, minMsecs, maxMsecs)) {\n throw new InvalidInputError(\n 'TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between ${minMsecs} and ${maxMsecs} milliseconds`,\n { strategy },\n )\n }\n return Math.floor(msecs / 1000)\n }\n\n if (secs !== undefined) {\n if (!isIntegerInRange(secs, minSecs, maxSecs)) {\n throw new InvalidInputError(\n 'TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between ${minSecs} and ${maxSecs} seconds`,\n { strategy },\n )\n }\n return secs\n }\n\n return undefined\n}\n"],"mappings":"+FAgCA,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,GAAM,CAAE,QAAO,QAAS,EAExB,GAAI,IAAU,IAAA,IAAa,IAAS,IAAA,GAClC,MAAM,IAAI,EAAkB,sBAAuB,+CAAgD,CAAE,UAAS,CAAC,EAGjH,GAAI,IAAU,IAAA,GAAW,CACvB,IAAM,EAAW,EAAU,IACrB,EAAW,EAAU,IAAO,IAClC,GAAI,CAAC,EAAiB,EAAO,EAAU,CAAQ,EAC7C,MAAM,IAAI,EACR,yBACA,wCAAwC,EAAS,OAAO,EAAS,eACjE,CAAE,UAAS,CACb,EAEF,OAAO,KAAK,MAAM,EAAQ,GAAI,CAChC,CAEA,GAAI,IAAS,IAAA,GAAW,CACtB,GAAI,CAAC,EAAiB,EAAM,EAAS,CAAO,EAC1C,MAAM,IAAI,EACR,yBACA,wCAAwC,EAAQ,OAAO,EAAQ,UAC/D,CAAE,UAAS,CACb,EAEF,OAAO,CACT,CAGF"}
@@ -6,22 +6,25 @@ type TypeidOptions = UuidV7Options;
6
6
  type Typeid = {
7
7
  /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */(prefix: string, options?: TypeidOptions): string; /** Generate a TypeID with explicit options or write its 16 canonical UUID v7 bytes into a caller-owned buffer. */
8
8
  <TBuf extends Uint8Array = Uint8Array>(prefix: string, options: TypeidOptions | undefined, buf: TBuf, offset?: number): TBuf; /** Generate a TypeID string with optional timestamp, counter, or random bytes. */
9
- (prefix: string, options?: TypeidOptions, buf?: undefined, offset?: number): string; /** Convert a TypeID's UUID v7 suffix to its canonical 16-byte representation. */
10
- toBytes(id: string): Uint8Array; /** Build a TypeID from a prefix and canonical UUID v7 bytes. */
11
- fromBytes(prefix: string, bytes: Uint8Array): string; /** Convert a TypeID to its UUID v7 string. */
12
- toUuid(id: string): string; /** Build a TypeID from a prefix and UUID v7 string. */
13
- fromUuid(prefix: string, uuid: string): string; /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. */
9
+ (prefix: string, options?: TypeidOptions, buf?: undefined, offset?: number): string; /** Convert a TypeID's suffix to its 16 UUID bytes. */
10
+ toBytes(id: string): Uint8Array; /** Build a TypeID from a prefix and 16 UUID bytes of any version. */
11
+ fromBytes(prefix: string, bytes: Uint8Array): string; /** Convert a TypeID to its UUID string. */
12
+ toUuid(id: string): string; /** Build a TypeID from a prefix and a UUID string of any version. */
13
+ fromUuid(prefix: string, uuid: string): string; /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. Throws for a TypeID that does not wrap a UUID v7. */
14
14
  timestamp(id: string): number; /** Read a TypeID's entity prefix. */
15
- prefix(id: string): string; /** Read a TypeID's UUID v7 suffix. */
16
- suffix(id: string): string; /** Return whether a value is a syntactically valid TypeID. */
15
+ prefix(id: string): string; /** Read a TypeID's base32 suffix. */
16
+ suffix(id: string): string; /** Return whether a value is a syntactically valid TypeID, whatever UUID version it wraps. */
17
17
  isValid(id: unknown): id is string;
18
18
  };
19
19
  /**
20
20
  * Generate a TypeID string backed by UUID v7.
21
21
  *
22
22
  * TypeID combines a lowercase snake_case type prefix with a 26-character
23
- * modified base32 encoding of a UUID v7, producing sortable domain identifiers
24
- * such as `user_01h2xcejqtf2nbrexx3vqjhp41`.
23
+ * modified base32 encoding of a UUID, producing domain identifiers such as
24
+ * `user_01h2xcejqtf2nbrexx3vqjhp41`. Generated TypeIDs always wrap a UUID v7,
25
+ * so they sort by creation time. As the TypeID specification recommends, the
26
+ * conversion and validation functions accept any 128-bit value, such as a
27
+ * name-based UUID v5.
25
28
  *
26
29
  * @example
27
30
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"typeid.d.mts","names":[],"sources":["../../src/typeid/typeid.ts"],"mappings":";;;;KAKY,aAAA,GAAgB,aAAA;AAAA,KAEhB,MAAA;gFAET,MAAA,UAAgB,OAAA,GAAU,aAAA,WAJjB;EAAA,cAMI,UAAA,GAAa,UAAA,EACzB,MAAA,UACA,OAAA,EAAS,aAAA,cACT,GAAA,EAAK,IAAA,EACL,MAAA,YACC,IAAA,EAXuB;EAAA,CAazB,MAAA,UAAgB,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA,oBAX7D;EAaE,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,MAAA,UAAgB,KAAA,EAAO,UAAA;EAEjC,MAAA,CAAO,EAAA;EAEP,QAAA,CAAS,MAAA,UAAgB,IAAA;EAEzB,SAAA,CAAU,EAAA;EAEV,MAAA,CAAO,EAAA,mBAR0B;EAUjC,MAAA,CAAO,EAAA;EAEP,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;;;;;;;;;;;;;;;cAgVX,MAAA,EAAQ,MAAA"}
1
+ {"version":3,"file":"typeid.d.mts","names":[],"sources":["../../src/typeid/typeid.ts"],"mappings":";;;;KAMY,aAAA,GAAgB,aAAA;AAAA,KAEhB,MAAA;gFAET,MAAA,UAAgB,OAAA,GAAU,aAAA,WAJjB;EAAA,cAMI,UAAA,GAAa,UAAA,EACzB,MAAA,UACA,OAAA,EAAS,aAAA,cACT,GAAA,EAAK,IAAA,EACL,MAAA,YACC,IAAA,EAXuB;EAAA,CAazB,MAAA,UAAgB,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA,oBAX7D;EAaE,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,MAAA,UAAgB,KAAA,EAAO,UAAA;EAEjC,MAAA,CAAO,EAAA;EAEP,QAAA,CAAS,MAAA,UAAgB,IAAA;EAEzB,SAAA,CAAU,EAAA;EAEV,MAAA,CAAO,EAAA,mBAR0B;EAUjC,MAAA,CAAO,EAAA;EAEP,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;cA4TX,MAAA,EAAQ,MAAA"}
@@ -1,2 +1,2 @@
1
- import{n as e,t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{uuidv7 as o}from"../uuid/v7.mjs";const s=0xffffffffffff,c=4294967295,l=`0123456789abcdefghjkmnpqrstvwxyz`,u=Array.from({length:128},()=>-1);for(let e=0;e<32;e+=1)u[l.charCodeAt(e)]=e;function d(e){if(e.length!==0){if(e.length>63)throw new r(`PREFIX_TOO_LONG`,`TypeID prefix must be at most 63 characters`,{strategy:`typeid`});for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t),i=n>=97&&n<=122;if(!i&&n!==95)throw new r(`PREFIX_INVALID_CHAR`,`TypeID prefix must contain only lowercase ASCII letters and underscores`,{strategy:`typeid`});if((t===0||t===e.length-1)&&!i)throw new r(`PREFIX_INVALID_BOUNDARY`,`TypeID prefix must start and end with a-z`,{strategy:`typeid`})}}}function f(e,t){let n=e.charCodeAt(t),r=n<u.length?u[n]:-1;if(r===-1)throw new i(`INVALID_CHAR`,`TypeID suffix contains invalid character at position ${t}`,{strategy:`typeid`});return r}function p(e){if(e.length!==26)throw new i(`INVALID_LENGTH`,`TypeID suffix must be 26 characters, got ${e.length}`,{strategy:`typeid`});if(e[0]>`7`)throw new i(`VALUE_OUT_OF_RANGE`,`TypeID suffix must encode a 128-bit value`,{strategy:`typeid`})}function m(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});return l[(e[0]&224)>>5]+l[e[0]&31]+l[(e[1]&248)>>3]+l[(e[1]&7)<<2|(e[2]&192)>>6]+l[(e[2]&62)>>1]+l[(e[2]&1)<<4|(e[3]&240)>>4]+l[(e[3]&15)<<1|(e[4]&128)>>7]+l[(e[4]&124)>>2]+l[(e[4]&3)<<3|(e[5]&224)>>5]+l[e[5]&31]+l[(e[6]&248)>>3]+l[(e[6]&7)<<2|(e[7]&192)>>6]+l[(e[7]&62)>>1]+l[(e[7]&1)<<4|(e[8]&240)>>4]+l[(e[8]&15)<<1|(e[9]&128)>>7]+l[(e[9]&124)>>2]+l[(e[9]&3)<<3|(e[10]&224)>>5]+l[e[10]&31]+l[(e[11]&248)>>3]+l[(e[11]&7)<<2|(e[12]&192)>>6]+l[(e[12]&62)>>1]+l[(e[12]&1)<<4|(e[13]&240)>>4]+l[(e[13]&15)<<1|(e[14]&128)>>7]+l[(e[14]&124)>>2]+l[(e[14]&3)<<3|(e[15]&224)>>5]+l[e[15]&31]}function h(e){p(e);let t=Array(26);for(let n=0;n<26;n+=1)t[n]=f(e,n);let n=new Uint8Array(16);return n[0]=t[0]<<5|t[1],n[1]=t[2]<<3|t[3]>>2,n[2]=(t[3]&3)<<6|t[4]<<1|t[5]>>4,n[3]=(t[5]&15)<<4|t[6]>>1,n[4]=(t[6]&1)<<7|t[7]<<2|t[8]>>3,n[5]=(t[8]&7)<<5|t[9],n[6]=t[10]<<3|t[11]>>2,n[7]=(t[11]&3)<<6|t[12]<<1|t[13]>>4,n[8]=(t[13]&15)<<4|t[14]>>1,n[9]=(t[14]&1)<<7|t[15]<<2|t[16]>>3,n[10]=(t[16]&7)<<5|t[17],n[11]=t[18]<<3|t[19]>>2,n[12]=(t[19]&3)<<6|t[20]<<1|t[21]>>4,n[13]=(t[21]&15)<<4|t[22]>>1,n[14]=(t[22]&1)<<7|t[23]<<2|t[24]>>3,n[15]=(t[24]&7)<<5|t[25],n}function g(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});if(e[6]>>4!=7||(e[8]&192)!=128)throw new r(`UUID_NOT_V7`,`TypeID UUID bytes must encode a UUID v7 value`,{strategy:`typeid`})}function _(e){let t=h(e);return g(t),t}function v(e){if(!o.isValid(e))throw new r(`UUID_NOT_V7`,`TypeID can only wrap UUID v7 values`,{strategy:`typeid`});return o.toBytes(e)}function y(e){let t=e.lastIndexOf(`_`);if(t===0)throw new i(`INVALID_FORMAT`,`TypeID must not start with "_"`,{strategy:`typeid`});let n=t===-1?``:e.slice(0,t),r=t===-1?e:e.slice(t+1);return d(n),{prefix:n,suffix:r,bytes:_(r)}}function b(e,t){return e===``?t:`${e}_${t}`}function x(e,t){return d(e),g(t),b(e,m(t))}function S(e){let t=0;for(let n=0;n<6;n+=1)t=t*256+e[n];return t}function C(e){let n=e.msecs;if(n!==void 0&&!t(n,0,s))throw new r(`TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between 0 and ${s}`,{strategy:`typeid`});if(e.counter!==void 0&&e.seq!==void 0)throw new r(`CONFLICTING_OPTIONS`,"Pass only one of `counter` or `seq`, not both",{strategy:`typeid`});let i=e.counter??e.seq;if(i!==void 0&&!t(i,0,c))throw new r(`COUNTER_OUT_OF_RANGE`,`Counter must be an integer between 0 and ${c}`,{strategy:`typeid`});let a=e.random;if(a&&a.length<16)throw new r(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`,{strategy:`typeid`})}function w(t,r,i,a=0){if(r!==void 0&&C(r),i){if(!e(i,a,16))throw new n(`BUFFER_OUT_OF_BOUNDS`,`TypeID byte range ${a}:${a+16-1} is out of buffer bounds`,{strategy:`typeid`});return d(t),o(r,i,a)}return x(t,o(r,new Uint8Array(16)))}function T(e){return y(e).bytes}function E(e,t){return x(e,t)}function D(e){return o.fromBytes(T(e))}function O(e,t){return x(e,v(t))}function k(e){return S(y(e).bytes)}function A(e){return y(e).prefix}function j(e){return y(e).suffix}function M(e){if(typeof e!=`string`)return!1;try{y(e)}catch{return!1}return!0}const N=Object.assign(w,{toBytes:T,fromBytes:E,toUuid:D,fromUuid:O,timestamp:k,prefix:A,suffix:j,isValid:M});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,N as typeid};
1
+ import{n as e,t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{i as o,n as s}from"../uuid-Q8CMAbBu.mjs";import{uuidv7 as c}from"../uuid/v7.mjs";const l=0xffffffffffff,u=4294967295,d=`0123456789abcdefghjkmnpqrstvwxyz`,f=Array.from({length:128},()=>-1);for(let e=0;e<32;e+=1)f[d.charCodeAt(e)]=e;function p(e){if(e.length!==0){if(e.length>63)throw new r(`PREFIX_TOO_LONG`,`TypeID prefix must be at most 63 characters`,{strategy:`typeid`});for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t),i=n>=97&&n<=122;if(!i&&n!==95)throw new r(`PREFIX_INVALID_CHAR`,`TypeID prefix must contain only lowercase ASCII letters and underscores`,{strategy:`typeid`});if((t===0||t===e.length-1)&&!i)throw new r(`PREFIX_INVALID_BOUNDARY`,`TypeID prefix must start and end with a-z`,{strategy:`typeid`})}}}function m(e,t){let n=e.charCodeAt(t),r=n<f.length?f[n]:-1;if(r===-1)throw new i(`INVALID_CHAR`,`TypeID suffix contains invalid character at position ${t}`,{strategy:`typeid`});return r}function h(e){if(e.length!==26)throw new i(`INVALID_LENGTH`,`TypeID suffix must be 26 characters, got ${e.length}`,{strategy:`typeid`});if(e[0]>`7`)throw new i(`VALUE_OUT_OF_RANGE`,`TypeID suffix must encode a 128-bit value`,{strategy:`typeid`})}function g(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});return d[(e[0]&224)>>5]+d[e[0]&31]+d[(e[1]&248)>>3]+d[(e[1]&7)<<2|(e[2]&192)>>6]+d[(e[2]&62)>>1]+d[(e[2]&1)<<4|(e[3]&240)>>4]+d[(e[3]&15)<<1|(e[4]&128)>>7]+d[(e[4]&124)>>2]+d[(e[4]&3)<<3|(e[5]&224)>>5]+d[e[5]&31]+d[(e[6]&248)>>3]+d[(e[6]&7)<<2|(e[7]&192)>>6]+d[(e[7]&62)>>1]+d[(e[7]&1)<<4|(e[8]&240)>>4]+d[(e[8]&15)<<1|(e[9]&128)>>7]+d[(e[9]&124)>>2]+d[(e[9]&3)<<3|(e[10]&224)>>5]+d[e[10]&31]+d[(e[11]&248)>>3]+d[(e[11]&7)<<2|(e[12]&192)>>6]+d[(e[12]&62)>>1]+d[(e[12]&1)<<4|(e[13]&240)>>4]+d[(e[13]&15)<<1|(e[14]&128)>>7]+d[(e[14]&124)>>2]+d[(e[14]&3)<<3|(e[15]&224)>>5]+d[e[15]&31]}function _(e){h(e);let t=Array(26);for(let n=0;n<26;n+=1)t[n]=m(e,n);let n=new Uint8Array(16);return n[0]=t[0]<<5|t[1],n[1]=t[2]<<3|t[3]>>2,n[2]=(t[3]&3)<<6|t[4]<<1|t[5]>>4,n[3]=(t[5]&15)<<4|t[6]>>1,n[4]=(t[6]&1)<<7|t[7]<<2|t[8]>>3,n[5]=(t[8]&7)<<5|t[9],n[6]=t[10]<<3|t[11]>>2,n[7]=(t[11]&3)<<6|t[12]<<1|t[13]>>4,n[8]=(t[13]&15)<<4|t[14]>>1,n[9]=(t[14]&1)<<7|t[15]<<2|t[16]>>3,n[10]=(t[16]&7)<<5|t[17],n[11]=t[18]<<3|t[19]>>2,n[12]=(t[19]&3)<<6|t[20]<<1|t[21]>>4,n[13]=(t[21]&15)<<4|t[22]>>1,n[14]=(t[22]&1)<<7|t[23]<<2|t[24]>>3,n[15]=(t[24]&7)<<5|t[25],n}function v(e){let t=e.lastIndexOf(`_`);if(t===0)throw new i(`INVALID_FORMAT`,`TypeID must not start with "_"`,{strategy:`typeid`});let n=t===-1?``:e.slice(0,t),r=t===-1?e:e.slice(t+1);return p(n),{prefix:n,suffix:r,bytes:_(r)}}function y(e,t){return e===``?t:`${e}_${t}`}function b(e,t){return p(e),y(e,g(t))}function x(e){if(e[6]>>4!=7||(e[8]&192)!=128)throw new r(`UUID_NOT_V7`,`TypeID must wrap a UUID v7 to have a timestamp`,{strategy:`typeid`});let t=0;for(let n=0;n<6;n+=1)t=t*256+e[n];return t}function S(e){let n=e.msecs;if(n!==void 0&&!t(n,0,l))throw new r(`TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between 0 and ${l}`,{strategy:`typeid`});if(e.counter!==void 0&&e.seq!==void 0)throw new r(`CONFLICTING_OPTIONS`,"Pass only one of `counter` or `seq`, not both",{strategy:`typeid`});let i=e.counter??e.seq;if(i!==void 0&&!t(i,0,u))throw new r(`COUNTER_OUT_OF_RANGE`,`Counter must be an integer between 0 and ${u}`,{strategy:`typeid`});let a=e.random;if(a&&a.length<16)throw new r(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`,{strategy:`typeid`})}function C(t,r,i,a=0){if(r!==void 0&&S(r),i){if(!e(i,a,16))throw new n(`BUFFER_OUT_OF_BOUNDS`,`TypeID byte range ${a}:${a+16-1} is out of buffer bounds`,{strategy:`typeid`});return p(t),c(r,i,a)}return b(t,c(r,new Uint8Array(16)))}function w(e){return v(e).bytes}function T(e,t){return b(e,t)}function E(e){return s(w(e))}function D(e,t){return b(e,o(t,`typeid`))}function O(e){return x(v(e).bytes)}function k(e){return v(e).prefix}function A(e){return v(e).suffix}function j(e){if(typeof e!=`string`)return!1;try{v(e)}catch{return!1}return!0}const M=Object.assign(C,{toBytes:w,fromBytes:T,toUuid:E,fromUuid:D,timestamp:O,prefix:k,suffix:A,isValid:j});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,M as typeid};
2
2
  //# sourceMappingURL=typeid.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"typeid.mjs","names":[],"sources":["../../src/typeid/typeid.ts"],"sourcesContent":["import { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError, ParseError } from '../errors'\nimport type { UuidV7Options } from '../uuid/v7'\nimport { uuidv7 } from '../uuid/v7'\n\nexport type TypeidOptions = UuidV7Options\n\nexport type Typeid = {\n /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */\n (prefix: string, options?: TypeidOptions): string\n /** Generate a TypeID with explicit options or write its 16 canonical UUID v7 bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(\n prefix: string,\n options: TypeidOptions | undefined,\n buf: TBuf,\n offset?: number,\n ): TBuf\n /** Generate a TypeID string with optional timestamp, counter, or random bytes. */\n (prefix: string, options?: TypeidOptions, buf?: undefined, offset?: number): string\n /** Convert a TypeID's UUID v7 suffix to its canonical 16-byte representation. */\n toBytes(id: string): Uint8Array\n /** Build a TypeID from a prefix and canonical UUID v7 bytes. */\n fromBytes(prefix: string, bytes: Uint8Array): string\n /** Convert a TypeID to its UUID v7 string. */\n toUuid(id: string): string\n /** Build a TypeID from a prefix and UUID v7 string. */\n fromUuid(prefix: string, uuid: string): string\n /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. */\n timestamp(id: string): number\n /** Read a TypeID's entity prefix. */\n prefix(id: string): string\n /** Read a TypeID's UUID v7 suffix. */\n suffix(id: string): string\n /** Return whether a value is a syntactically valid TypeID. */\n isValid(id: unknown): id is string\n}\n\nconst TYPEID_SUFFIX_LENGTH = 26\nconst TYPEID_UUID_BYTE_LENGTH = 16\n// 48-bit UUID v7 timestamp capacity, mirrored from uuid/v7 alongside the\n// 32-bit counter capacity, so option validation is attributed to the typeid\n// boundary instead of leaking `strategy: 'uuid'` through delegation.\nconst MAX_MSECS = 0xffffffffffff\nconst MAX_COUNTER = 0xffffffff\nconst TYPEID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'\n\nconst BASE32_DECODE: number[] = Array.from({ length: 128 }, () => -1)\nfor (let i = 0; i < TYPEID_ALPHABET.length; i += 1) {\n BASE32_DECODE[TYPEID_ALPHABET.charCodeAt(i)] = i\n}\n\ntype ParsedTypeid = {\n prefix: string\n suffix: string\n bytes: Uint8Array\n}\n\nfunction assertValidPrefix(prefix: string): void {\n if (prefix.length === 0) {\n return\n }\n\n if (prefix.length > 63) {\n throw new InvalidInputError('PREFIX_TOO_LONG', 'TypeID prefix must be at most 63 characters', {\n strategy: 'typeid',\n })\n }\n\n for (let i = 0; i < prefix.length; i += 1) {\n const code = prefix.charCodeAt(i)\n const isLowercaseLetter = code >= 97 && code <= 122\n const isUnderscore = code === 95\n\n if (!isLowercaseLetter && !isUnderscore) {\n throw new InvalidInputError(\n 'PREFIX_INVALID_CHAR',\n 'TypeID prefix must contain only lowercase ASCII letters and underscores',\n { strategy: 'typeid' },\n )\n }\n\n if ((i === 0 || i === prefix.length - 1) && !isLowercaseLetter) {\n throw new InvalidInputError('PREFIX_INVALID_BOUNDARY', 'TypeID prefix must start and end with a-z', {\n strategy: 'typeid',\n })\n }\n }\n}\n\nfunction decodeSuffixValue(suffix: string, index: number): number {\n const code = suffix.charCodeAt(index)\n const value = code < BASE32_DECODE.length ? BASE32_DECODE[code] : -1\n\n if (value === -1) {\n throw new ParseError('INVALID_CHAR', `TypeID suffix contains invalid character at position ${index}`, {\n strategy: 'typeid',\n })\n }\n\n return value\n}\n\nfunction assertValidSuffixShape(suffix: string): void {\n if (suffix.length !== TYPEID_SUFFIX_LENGTH) {\n throw new ParseError('INVALID_LENGTH', `TypeID suffix must be 26 characters, got ${suffix.length}`, {\n strategy: 'typeid',\n })\n }\n\n if (suffix[0] > '7') {\n throw new ParseError('VALUE_OUT_OF_RANGE', 'TypeID suffix must encode a 128-bit value', {\n strategy: 'typeid',\n })\n }\n}\n\nfunction encodeBytesToSuffix(bytes: Uint8Array): string {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`, {\n strategy: 'typeid',\n })\n }\n\n return (\n TYPEID_ALPHABET[(bytes[0] & 0xe0) >> 5] +\n TYPEID_ALPHABET[bytes[0] & 0x1f] +\n TYPEID_ALPHABET[(bytes[1] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[1] & 0x07) << 2) | ((bytes[2] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[2] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[2] & 0x01) << 4) | ((bytes[3] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[3] & 0x0f) << 1) | ((bytes[4] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[4] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[4] & 0x03) << 3) | ((bytes[5] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[5] & 0x1f] +\n TYPEID_ALPHABET[(bytes[6] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[6] & 0x07) << 2) | ((bytes[7] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[7] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[7] & 0x01) << 4) | ((bytes[8] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[8] & 0x0f) << 1) | ((bytes[9] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[9] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[9] & 0x03) << 3) | ((bytes[10] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[10] & 0x1f] +\n TYPEID_ALPHABET[(bytes[11] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[11] & 0x07) << 2) | ((bytes[12] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[12] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[12] & 0x01) << 4) | ((bytes[13] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[13] & 0x0f) << 1) | ((bytes[14] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[14] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[14] & 0x03) << 3) | ((bytes[15] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[15] & 0x1f]\n )\n}\n\nfunction decodeSuffixToBytes(suffix: string): Uint8Array {\n assertValidSuffixShape(suffix)\n\n const values = new Array<number>(TYPEID_SUFFIX_LENGTH)\n for (let i = 0; i < TYPEID_SUFFIX_LENGTH; i += 1) {\n values[i] = decodeSuffixValue(suffix, i)\n }\n\n const bytes = new Uint8Array(TYPEID_UUID_BYTE_LENGTH)\n bytes[0] = (values[0] << 5) | values[1]\n bytes[1] = (values[2] << 3) | (values[3] >> 2)\n bytes[2] = ((values[3] & 0x03) << 6) | (values[4] << 1) | (values[5] >> 4)\n bytes[3] = ((values[5] & 0x0f) << 4) | (values[6] >> 1)\n bytes[4] = ((values[6] & 0x01) << 7) | (values[7] << 2) | (values[8] >> 3)\n bytes[5] = ((values[8] & 0x07) << 5) | values[9]\n bytes[6] = (values[10] << 3) | (values[11] >> 2)\n bytes[7] = ((values[11] & 0x03) << 6) | (values[12] << 1) | (values[13] >> 4)\n bytes[8] = ((values[13] & 0x0f) << 4) | (values[14] >> 1)\n bytes[9] = ((values[14] & 0x01) << 7) | (values[15] << 2) | (values[16] >> 3)\n bytes[10] = ((values[16] & 0x07) << 5) | values[17]\n bytes[11] = (values[18] << 3) | (values[19] >> 2)\n bytes[12] = ((values[19] & 0x03) << 6) | (values[20] << 1) | (values[21] >> 4)\n bytes[13] = ((values[21] & 0x0f) << 4) | (values[22] >> 1)\n bytes[14] = ((values[22] & 0x01) << 7) | (values[23] << 2) | (values[24] >> 3)\n bytes[15] = ((values[24] & 0x07) << 5) | values[25]\n\n return bytes\n}\n\nfunction assertUuidV7Bytes(bytes: Uint8Array): void {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`, {\n strategy: 'typeid',\n })\n }\n\n if (bytes[6] >> 4 !== 7 || (bytes[8] & 0xc0) !== 0x80) {\n throw new InvalidInputError('UUID_NOT_V7', 'TypeID UUID bytes must encode a UUID v7 value', {\n strategy: 'typeid',\n })\n }\n}\n\nfunction uuidV7BytesFromSuffix(suffix: string): Uint8Array {\n const bytes = decodeSuffixToBytes(suffix)\n assertUuidV7Bytes(bytes)\n return bytes\n}\n\nfunction uuidV7BytesFromUuid(uuid: string): Uint8Array {\n if (!uuidv7.isValid(uuid)) {\n throw new InvalidInputError('UUID_NOT_V7', 'TypeID can only wrap UUID v7 values', { strategy: 'typeid' })\n }\n\n return uuidv7.toBytes(uuid)\n}\n\nfunction parseTypeid(id: string): ParsedTypeid {\n const separatorIndex = id.lastIndexOf('_')\n\n if (separatorIndex === 0) {\n throw new ParseError('INVALID_FORMAT', 'TypeID must not start with \"_\"', { strategy: 'typeid' })\n }\n\n const prefix = separatorIndex === -1 ? '' : id.slice(0, separatorIndex)\n const suffix = separatorIndex === -1 ? id : id.slice(separatorIndex + 1)\n\n assertValidPrefix(prefix)\n const bytes = uuidV7BytesFromSuffix(suffix)\n\n return { prefix, suffix, bytes }\n}\n\nfunction joinPrefixAndSuffix(prefix: string, suffix: string): string {\n return prefix === '' ? suffix : `${prefix}_${suffix}`\n}\n\nfunction formatTypeidFromUuidV7Bytes(prefix: string, bytes: Uint8Array): string {\n assertValidPrefix(prefix)\n assertUuidV7Bytes(bytes)\n return joinPrefixAndSuffix(prefix, encodeBytesToSuffix(bytes))\n}\n\nfunction timestampFromUuidV7Bytes(bytes: Uint8Array): number {\n let msecs = 0\n for (let i = 0; i < 6; i += 1) {\n msecs = msecs * 256 + bytes[i]\n }\n return msecs\n}\n\n/**\n * Mirror uuid/v7's option validation at the typeid boundary, so failures are\n * attributed to `strategy: 'typeid'` instead of leaking `strategy: 'uuid'`\n * through delegation.\n */\nfunction validateTypeidOptions(options: TypeidOptions): void {\n const msecs = options.msecs\n if (msecs !== undefined && !isIntegerInRange(msecs, 0, MAX_MSECS)) {\n throw new InvalidInputError('TIMESTAMP_OUT_OF_RANGE', `Timestamp must be an integer between 0 and ${MAX_MSECS}`, {\n strategy: 'typeid',\n })\n }\n\n if (options.counter !== undefined && options.seq !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `counter` or `seq`, not both', {\n strategy: 'typeid',\n })\n }\n const counter = options.counter ?? options.seq\n if (counter !== undefined && !isIntegerInRange(counter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('COUNTER_OUT_OF_RANGE', `Counter must be an integer between 0 and ${MAX_COUNTER}`, {\n strategy: 'typeid',\n })\n }\n\n const random = options.random\n if (random && random.length < TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${TYPEID_UUID_BYTE_LENGTH}`, {\n strategy: 'typeid',\n })\n }\n}\n\nfunction typeidFn(prefix: string, options?: TypeidOptions, buf?: undefined, offset?: number): string\nfunction typeidFn<TBuf extends Uint8Array = Uint8Array>(\n prefix: string,\n options: TypeidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction typeidFn<TBuf extends Uint8Array = Uint8Array>(\n prefix: string,\n options?: TypeidOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n if (options !== undefined) {\n validateTypeidOptions(options)\n }\n\n if (buf) {\n if (!isWritableRange(buf, offset, TYPEID_UUID_BYTE_LENGTH)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `TypeID byte range ${offset}:${offset + TYPEID_UUID_BYTE_LENGTH - 1} is out of buffer bounds`,\n { strategy: 'typeid' },\n )\n }\n assertValidPrefix(prefix)\n return uuidv7(options, buf, offset)\n }\n\n const bytes = uuidv7(options, new Uint8Array(TYPEID_UUID_BYTE_LENGTH))\n return formatTypeidFromUuidV7Bytes(prefix, bytes)\n}\n\nfunction typeidToBytes(id: string): Uint8Array {\n return parseTypeid(id).bytes\n}\n\nfunction typeidFromBytes(prefix: string, bytes: Uint8Array): string {\n return formatTypeidFromUuidV7Bytes(prefix, bytes)\n}\n\nfunction typeidToUuid(id: string): string {\n return uuidv7.fromBytes(typeidToBytes(id))\n}\n\nfunction typeidFromUuid(prefix: string, uuid: string): string {\n return formatTypeidFromUuidV7Bytes(prefix, uuidV7BytesFromUuid(uuid))\n}\n\nfunction timestamp(id: string): number {\n return timestampFromUuidV7Bytes(parseTypeid(id).bytes)\n}\n\nfunction prefix(id: string): string {\n return parseTypeid(id).prefix\n}\n\nfunction suffix(id: string): string {\n return parseTypeid(id).suffix\n}\n\nfunction isValid(id: unknown): id is string {\n if (typeof id !== 'string') {\n return false\n }\n\n try {\n parseTypeid(id)\n } catch {\n return false\n }\n\n return true\n}\n\n/**\n * Generate a TypeID string backed by UUID v7.\n *\n * TypeID combines a lowercase snake_case type prefix with a 26-character\n * modified base32 encoding of a UUID v7, producing sortable domain identifiers\n * such as `user_01h2xcejqtf2nbrexx3vqjhp41`.\n *\n * @example\n * ```ts\n * import { typeid } from 'uniku/typeid'\n *\n * const id = typeid('user')\n * // => \"user_01h2xcejqtf2nbrexx3vqjhp41\"\n *\n * const uuid = typeid.toUuid(id)\n * const restored = typeid.fromUuid('user', uuid)\n * ```\n */\nexport const typeid: Typeid = Object.assign(typeidFn, {\n toBytes: typeidToBytes,\n fromBytes: typeidFromBytes,\n toUuid: typeidToUuid,\n fromUuid: typeidFromUuid,\n timestamp,\n prefix,\n suffix,\n isValid,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
1
+ {"version":3,"file":"typeid.mjs","names":[],"sources":["../../src/typeid/typeid.ts"],"sourcesContent":["import { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError, ParseError } from '../errors'\nimport { formatUuidUnchecked, parseUuidFor } from '../uuid/common/uuid'\nimport type { UuidV7Options } from '../uuid/v7'\nimport { uuidv7 } from '../uuid/v7'\n\nexport type TypeidOptions = UuidV7Options\n\nexport type Typeid = {\n /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */\n (prefix: string, options?: TypeidOptions): string\n /** Generate a TypeID with explicit options or write its 16 canonical UUID v7 bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(\n prefix: string,\n options: TypeidOptions | undefined,\n buf: TBuf,\n offset?: number,\n ): TBuf\n /** Generate a TypeID string with optional timestamp, counter, or random bytes. */\n (prefix: string, options?: TypeidOptions, buf?: undefined, offset?: number): string\n /** Convert a TypeID's suffix to its 16 UUID bytes. */\n toBytes(id: string): Uint8Array\n /** Build a TypeID from a prefix and 16 UUID bytes of any version. */\n fromBytes(prefix: string, bytes: Uint8Array): string\n /** Convert a TypeID to its UUID string. */\n toUuid(id: string): string\n /** Build a TypeID from a prefix and a UUID string of any version. */\n fromUuid(prefix: string, uuid: string): string\n /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. Throws for a TypeID that does not wrap a UUID v7. */\n timestamp(id: string): number\n /** Read a TypeID's entity prefix. */\n prefix(id: string): string\n /** Read a TypeID's base32 suffix. */\n suffix(id: string): string\n /** Return whether a value is a syntactically valid TypeID, whatever UUID version it wraps. */\n isValid(id: unknown): id is string\n}\n\nconst TYPEID_SUFFIX_LENGTH = 26\nconst TYPEID_UUID_BYTE_LENGTH = 16\n// 48-bit UUID v7 timestamp capacity, mirrored from uuid/v7 alongside the\n// 32-bit counter capacity, so option validation is attributed to the typeid\n// boundary instead of leaking `strategy: 'uuid'` through delegation.\nconst MAX_MSECS = 0xffffffffffff\nconst MAX_COUNTER = 0xffffffff\nconst TYPEID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'\n\nconst BASE32_DECODE: number[] = Array.from({ length: 128 }, () => -1)\nfor (let i = 0; i < TYPEID_ALPHABET.length; i += 1) {\n BASE32_DECODE[TYPEID_ALPHABET.charCodeAt(i)] = i\n}\n\ntype ParsedTypeid = {\n prefix: string\n suffix: string\n bytes: Uint8Array\n}\n\nfunction assertValidPrefix(prefix: string): void {\n if (prefix.length === 0) {\n return\n }\n\n if (prefix.length > 63) {\n throw new InvalidInputError('PREFIX_TOO_LONG', 'TypeID prefix must be at most 63 characters', {\n strategy: 'typeid',\n })\n }\n\n for (let i = 0; i < prefix.length; i += 1) {\n const code = prefix.charCodeAt(i)\n const isLowercaseLetter = code >= 97 && code <= 122\n const isUnderscore = code === 95\n\n if (!isLowercaseLetter && !isUnderscore) {\n throw new InvalidInputError(\n 'PREFIX_INVALID_CHAR',\n 'TypeID prefix must contain only lowercase ASCII letters and underscores',\n { strategy: 'typeid' },\n )\n }\n\n if ((i === 0 || i === prefix.length - 1) && !isLowercaseLetter) {\n throw new InvalidInputError('PREFIX_INVALID_BOUNDARY', 'TypeID prefix must start and end with a-z', {\n strategy: 'typeid',\n })\n }\n }\n}\n\nfunction decodeSuffixValue(suffix: string, index: number): number {\n const code = suffix.charCodeAt(index)\n const value = code < BASE32_DECODE.length ? BASE32_DECODE[code] : -1\n\n if (value === -1) {\n throw new ParseError('INVALID_CHAR', `TypeID suffix contains invalid character at position ${index}`, {\n strategy: 'typeid',\n })\n }\n\n return value\n}\n\nfunction assertValidSuffixShape(suffix: string): void {\n if (suffix.length !== TYPEID_SUFFIX_LENGTH) {\n throw new ParseError('INVALID_LENGTH', `TypeID suffix must be 26 characters, got ${suffix.length}`, {\n strategy: 'typeid',\n })\n }\n\n if (suffix[0] > '7') {\n throw new ParseError('VALUE_OUT_OF_RANGE', 'TypeID suffix must encode a 128-bit value', {\n strategy: 'typeid',\n })\n }\n}\n\nfunction encodeBytesToSuffix(bytes: Uint8Array): string {\n if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`, {\n strategy: 'typeid',\n })\n }\n\n return (\n TYPEID_ALPHABET[(bytes[0] & 0xe0) >> 5] +\n TYPEID_ALPHABET[bytes[0] & 0x1f] +\n TYPEID_ALPHABET[(bytes[1] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[1] & 0x07) << 2) | ((bytes[2] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[2] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[2] & 0x01) << 4) | ((bytes[3] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[3] & 0x0f) << 1) | ((bytes[4] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[4] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[4] & 0x03) << 3) | ((bytes[5] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[5] & 0x1f] +\n TYPEID_ALPHABET[(bytes[6] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[6] & 0x07) << 2) | ((bytes[7] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[7] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[7] & 0x01) << 4) | ((bytes[8] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[8] & 0x0f) << 1) | ((bytes[9] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[9] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[9] & 0x03) << 3) | ((bytes[10] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[10] & 0x1f] +\n TYPEID_ALPHABET[(bytes[11] & 0xf8) >> 3] +\n TYPEID_ALPHABET[((bytes[11] & 0x07) << 2) | ((bytes[12] & 0xc0) >> 6)] +\n TYPEID_ALPHABET[(bytes[12] & 0x3e) >> 1] +\n TYPEID_ALPHABET[((bytes[12] & 0x01) << 4) | ((bytes[13] & 0xf0) >> 4)] +\n TYPEID_ALPHABET[((bytes[13] & 0x0f) << 1) | ((bytes[14] & 0x80) >> 7)] +\n TYPEID_ALPHABET[(bytes[14] & 0x7c) >> 2] +\n TYPEID_ALPHABET[((bytes[14] & 0x03) << 3) | ((bytes[15] & 0xe0) >> 5)] +\n TYPEID_ALPHABET[bytes[15] & 0x1f]\n )\n}\n\nfunction decodeSuffixToBytes(suffix: string): Uint8Array {\n assertValidSuffixShape(suffix)\n\n const values = new Array<number>(TYPEID_SUFFIX_LENGTH)\n for (let i = 0; i < TYPEID_SUFFIX_LENGTH; i += 1) {\n values[i] = decodeSuffixValue(suffix, i)\n }\n\n const bytes = new Uint8Array(TYPEID_UUID_BYTE_LENGTH)\n bytes[0] = (values[0] << 5) | values[1]\n bytes[1] = (values[2] << 3) | (values[3] >> 2)\n bytes[2] = ((values[3] & 0x03) << 6) | (values[4] << 1) | (values[5] >> 4)\n bytes[3] = ((values[5] & 0x0f) << 4) | (values[6] >> 1)\n bytes[4] = ((values[6] & 0x01) << 7) | (values[7] << 2) | (values[8] >> 3)\n bytes[5] = ((values[8] & 0x07) << 5) | values[9]\n bytes[6] = (values[10] << 3) | (values[11] >> 2)\n bytes[7] = ((values[11] & 0x03) << 6) | (values[12] << 1) | (values[13] >> 4)\n bytes[8] = ((values[13] & 0x0f) << 4) | (values[14] >> 1)\n bytes[9] = ((values[14] & 0x01) << 7) | (values[15] << 2) | (values[16] >> 3)\n bytes[10] = ((values[16] & 0x07) << 5) | values[17]\n bytes[11] = (values[18] << 3) | (values[19] >> 2)\n bytes[12] = ((values[19] & 0x03) << 6) | (values[20] << 1) | (values[21] >> 4)\n bytes[13] = ((values[21] & 0x0f) << 4) | (values[22] >> 1)\n bytes[14] = ((values[22] & 0x01) << 7) | (values[23] << 2) | (values[24] >> 3)\n bytes[15] = ((values[24] & 0x07) << 5) | values[25]\n\n return bytes\n}\n\nfunction parseTypeid(id: string): ParsedTypeid {\n const separatorIndex = id.lastIndexOf('_')\n\n if (separatorIndex === 0) {\n throw new ParseError('INVALID_FORMAT', 'TypeID must not start with \"_\"', { strategy: 'typeid' })\n }\n\n const prefix = separatorIndex === -1 ? '' : id.slice(0, separatorIndex)\n const suffix = separatorIndex === -1 ? id : id.slice(separatorIndex + 1)\n\n assertValidPrefix(prefix)\n const bytes = decodeSuffixToBytes(suffix)\n\n return { prefix, suffix, bytes }\n}\n\nfunction joinPrefixAndSuffix(prefix: string, suffix: string): string {\n return prefix === '' ? suffix : `${prefix}_${suffix}`\n}\n\nfunction formatTypeid(prefix: string, bytes: Uint8Array): string {\n assertValidPrefix(prefix)\n return joinPrefixAndSuffix(prefix, encodeBytesToSuffix(bytes))\n}\n\nfunction timestampFromUuidV7Bytes(bytes: Uint8Array): number {\n if (bytes[6] >> 4 !== 7 || (bytes[8] & 0xc0) !== 0x80) {\n throw new InvalidInputError('UUID_NOT_V7', 'TypeID must wrap a UUID v7 to have a timestamp', {\n strategy: 'typeid',\n })\n }\n\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\n/**\n * Mirror uuid/v7's option validation at the typeid boundary, so failures are\n * attributed to `strategy: 'typeid'` instead of leaking `strategy: 'uuid'`\n * through delegation.\n */\nfunction validateTypeidOptions(options: TypeidOptions): void {\n const msecs = options.msecs\n if (msecs !== undefined && !isIntegerInRange(msecs, 0, MAX_MSECS)) {\n throw new InvalidInputError('TIMESTAMP_OUT_OF_RANGE', `Timestamp must be an integer between 0 and ${MAX_MSECS}`, {\n strategy: 'typeid',\n })\n }\n\n if (options.counter !== undefined && options.seq !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `counter` or `seq`, not both', {\n strategy: 'typeid',\n })\n }\n const counter = options.counter ?? options.seq\n if (counter !== undefined && !isIntegerInRange(counter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('COUNTER_OUT_OF_RANGE', `Counter must be an integer between 0 and ${MAX_COUNTER}`, {\n strategy: 'typeid',\n })\n }\n\n const random = options.random\n if (random && random.length < TYPEID_UUID_BYTE_LENGTH) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', `Random bytes length must be >= ${TYPEID_UUID_BYTE_LENGTH}`, {\n strategy: 'typeid',\n })\n }\n}\n\nfunction typeidFn(prefix: string, options?: TypeidOptions, buf?: undefined, offset?: number): string\nfunction typeidFn<TBuf extends Uint8Array = Uint8Array>(\n prefix: string,\n options: TypeidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction typeidFn<TBuf extends Uint8Array = Uint8Array>(\n prefix: string,\n options?: TypeidOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n if (options !== undefined) {\n validateTypeidOptions(options)\n }\n\n if (buf) {\n if (!isWritableRange(buf, offset, TYPEID_UUID_BYTE_LENGTH)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `TypeID byte range ${offset}:${offset + TYPEID_UUID_BYTE_LENGTH - 1} is out of buffer bounds`,\n { strategy: 'typeid' },\n )\n }\n assertValidPrefix(prefix)\n return uuidv7(options, buf, offset)\n }\n\n const bytes = uuidv7(options, new Uint8Array(TYPEID_UUID_BYTE_LENGTH))\n return formatTypeid(prefix, bytes)\n}\n\nfunction typeidToBytes(id: string): Uint8Array {\n return parseTypeid(id).bytes\n}\n\nfunction typeidFromBytes(prefix: string, bytes: Uint8Array): string {\n return formatTypeid(prefix, bytes)\n}\n\nfunction typeidToUuid(id: string): string {\n return formatUuidUnchecked(typeidToBytes(id))\n}\n\nfunction typeidFromUuid(prefix: string, uuid: string): string {\n return formatTypeid(prefix, parseUuidFor(uuid, 'typeid'))\n}\n\nfunction timestamp(id: string): number {\n return timestampFromUuidV7Bytes(parseTypeid(id).bytes)\n}\n\nfunction prefix(id: string): string {\n return parseTypeid(id).prefix\n}\n\nfunction suffix(id: string): string {\n return parseTypeid(id).suffix\n}\n\nfunction isValid(id: unknown): id is string {\n if (typeof id !== 'string') {\n return false\n }\n\n try {\n parseTypeid(id)\n } catch {\n return false\n }\n\n return true\n}\n\n/**\n * Generate a TypeID string backed by UUID v7.\n *\n * TypeID combines a lowercase snake_case type prefix with a 26-character\n * modified base32 encoding of a UUID, producing domain identifiers such as\n * `user_01h2xcejqtf2nbrexx3vqjhp41`. Generated TypeIDs always wrap a UUID v7,\n * so they sort by creation time. As the TypeID specification recommends, the\n * conversion and validation functions accept any 128-bit value, such as a\n * name-based UUID v5.\n *\n * @example\n * ```ts\n * import { typeid } from 'uniku/typeid'\n *\n * const id = typeid('user')\n * // => \"user_01h2xcejqtf2nbrexx3vqjhp41\"\n *\n * const uuid = typeid.toUuid(id)\n * const restored = typeid.fromUuid('user', uuid)\n * ```\n */\nexport const typeid: Typeid = Object.assign(typeidFn, {\n toBytes: typeidToBytes,\n fromBytes: typeidFromBytes,\n toUuid: typeidToUuid,\n fromUuid: typeidFromUuid,\n timestamp,\n prefix,\n suffix,\n isValid,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
package/build/uuid/v4.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o,r as s,t as c}from"../uuid-BPebYihz.mjs";const l=globalThis.crypto.randomUUID.bind(globalThis.crypto),u=new Uint8Array(16),d=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function f(e,t,n){for(let r=0;r<16;r+=1)t[n+r]=e[r];t[n+6]=t[n+6]&15|64,t[n+8]=t[n+8]&63|128}function p(e,t,n){return!t&&!e?l():m(e,t,n)}function m(i,a,s){let c=i?.random;if(c&&c.length<16)throw new r(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`,{strategy:`uuid`});let l=a?s??0:0;if(a&&!t(a,l,16))throw new n(`BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${l}:${l+16-1} is out of buffer bounds`,{strategy:`uuid`});let d=a??u;return f(c??e(),d,l),a??o(d)}function h(e){return typeof e==`string`&&d.test(e)}const g=Object.assign(p,{toBytes:s,fromBytes:c,isValid:h,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,g as uuidv4};
1
+ import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o,r as s,t as c}from"../uuid-Q8CMAbBu.mjs";const l=globalThis.crypto.randomUUID.bind(globalThis.crypto),u=new Uint8Array(16),d=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function f(e,t,n){for(let r=0;r<16;r+=1)t[n+r]=e[r];t[n+6]=t[n+6]&15|64,t[n+8]=t[n+8]&63|128}function p(e,t,n){return!t&&!e?l():m(e,t,n)}function m(i,a,s){let c=i?.random;if(c&&c.length<16)throw new r(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`,{strategy:`uuid`});let l=a?s??0:0;if(a&&!t(a,l,16))throw new n(`BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${l}:${l+16-1} is out of buffer bounds`,{strategy:`uuid`});let d=a??u;return f(c??e(),d,l),a??o(d)}function h(e){return typeof e==`string`&&d.test(e)}const g=Object.assign(p,{toBytes:s,fromBytes:c,isValid:h,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,g as uuidv4};
2
2
  //# sourceMappingURL=v4.mjs.map