uniku 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/build/generators.d.mts +1 -1
- package/build/generators.d.mts.map +1 -1
- package/build/generators.mjs +1 -1
- package/build/generators.mjs.map +1 -1
- package/build/nanoid/nanoid.d.mts.map +1 -1
- package/build/nanoid/nanoid.mjs +1 -1
- package/build/nanoid/nanoid.mjs.map +1 -1
- package/build/xid/xid.d.mts +31 -0
- package/build/xid/xid.d.mts.map +1 -0
- package/build/xid/xid.mjs +2 -0
- package/build/xid/xid.mjs.map +1 -0
- package/package.json +13 -4
- package/src/generators.ts +1 -1
- package/src/nanoid/nanoid.ts +16 -14
- package/src/xid/base32hex.ts +95 -0
- package/src/xid/xid.ts +172 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ console.log(first < second && second < third) // true
|
|
|
35
35
|
| KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
36
36
|
| ObjectID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
|
37
37
|
| TSID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
38
|
+
| XID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
38
39
|
| Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
39
40
|
| ESM-only | ✅ | ✅¹ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
40
41
|
| Edge/Workers | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ⚠️ |
|
|
@@ -67,9 +68,10 @@ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
|
|
|
67
68
|
| KSUID | **1.5× faster** |
|
|
68
69
|
| ObjectID | **1.1× faster** |
|
|
69
70
|
| TSID | **1.7× faster** |
|
|
71
|
+
| XID | **1.3× faster** |
|
|
70
72
|
| UUID v7 | **1.1× faster** |
|
|
71
|
-
| Nanoid |
|
|
72
|
-
| Nanoid (10 chars) |
|
|
73
|
+
| Nanoid | **1.9× faster** |
|
|
74
|
+
| Nanoid (10 chars) | **1.3× faster** |
|
|
73
75
|
| TypeID | **2.6× faster** |
|
|
74
76
|
| UUID v4 | npm is 1.1× faster |
|
|
75
77
|
|
|
@@ -88,6 +90,7 @@ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
|
|
|
88
90
|
| Very high-volume distributed systems | **KSUID** | Time-ordered with 128-bit entropy |
|
|
89
91
|
| MongoDB `_id` compatibility | **ObjectID** | Drop-in match for MongoDB's native document ID format |
|
|
90
92
|
| Native 64-bit sortable integer ID | **TSID** | Fits a database `BIGINT` primary key, no UUID-sized overhead |
|
|
93
|
+
| Go rs/xid compatibility | **XID** | Compact, time-ordered 12-byte identifier |
|
|
91
94
|
|
|
92
95
|
### Detailed Guide
|
|
93
96
|
|
|
@@ -218,10 +221,11 @@ deno install npm:uniku
|
|
|
218
221
|
| `uniku/typeid` | ~1.6 KB |
|
|
219
222
|
| `uniku/cuid/v2` | ~992 B* |
|
|
220
223
|
| `uniku/cuid2` | ~1007 B* |
|
|
221
|
-
| `uniku/nanoid` | ~1.
|
|
224
|
+
| `uniku/nanoid` | ~1.2 KB |
|
|
222
225
|
| `uniku/ksuid` | ~1.3 KB |
|
|
223
226
|
| `uniku/objectid` | ~1.3 KB |
|
|
224
227
|
| `uniku/tsid` | ~1.4 KB |
|
|
228
|
+
| `uniku/xid` | ~1.9 KB |
|
|
225
229
|
| `uniku/generators` | ~98 B |
|
|
226
230
|
|
|
227
231
|
* The CUID2 entry point imports SHA3-512 from `@noble/hashes`; this table's entry-point size excludes that external dependency.
|
|
@@ -375,6 +379,21 @@ const bytes = tsid.toBytes(id)
|
|
|
375
379
|
const restored = tsid.fromBytes(bytes)
|
|
376
380
|
```
|
|
377
381
|
|
|
382
|
+
### XID (time-ordered, rs/xid-compatible)
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { xid } from 'uniku/xid'
|
|
386
|
+
|
|
387
|
+
const id = xid()
|
|
388
|
+
// => "9m4e2mr0ui3e8a215n4g"
|
|
389
|
+
|
|
390
|
+
const bytes = xid.toBytes(id)
|
|
391
|
+
const restored = xid.fromBytes(bytes)
|
|
392
|
+
const timestamp = xid.timestamp(id)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
XID caches a random identity per runtime and uses a shared, always-incrementing counter. Supply `machineId`, `processId`, `secs`, and `counter` for deterministic output.
|
|
396
|
+
|
|
378
397
|
## Migrating to uniku
|
|
379
398
|
|
|
380
399
|
### From `uuid`
|
|
@@ -553,6 +572,19 @@ tsid.MAX // 18446744073709551615n
|
|
|
553
572
|
|
|
554
573
|
> Unlike every other generator listed here, `tsid()` returns `bigint` by default, and `toBytes`/`fromBytes`/`timestamp`/`isValid` all operate on that `bigint`. `toString`/`fromString` are the boundary conversions to/from the 13-character canonical Crockford Base32 string.
|
|
555
574
|
|
|
575
|
+
### `xid` (from `uniku/xid`)
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
xid(options?: XidOptions): string
|
|
579
|
+
xid(options: XidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
580
|
+
xid.toBytes(id: string): Uint8Array
|
|
581
|
+
xid.fromBytes(bytes: Uint8Array): string
|
|
582
|
+
xid.timestamp(id: string): number
|
|
583
|
+
xid.isValid(id: unknown): id is string
|
|
584
|
+
xid.NIL // "00000000000000000000"
|
|
585
|
+
xid.MAX // "vvvvvvvvvvvvvvvvvvvg"
|
|
586
|
+
```
|
|
587
|
+
|
|
556
588
|
## Documentation
|
|
557
589
|
|
|
558
590
|
For advanced usage, examples, and contributing guidelines, see the [full documentation on GitHub](https://github.com/jkomyno/uniku).
|
package/build/generators.d.mts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `@uniku/cli`'s `--type` flag) should derive from this array rather than
|
|
8
8
|
* hand-copying the union.
|
|
9
9
|
*/
|
|
10
|
-
declare const ID_GENERATORS: readonly ["uuid", "ulid", "typeid", "nanoid", "cuid", "ksuid", "objectid", "tsid"];
|
|
10
|
+
declare const ID_GENERATORS: readonly ["uuid", "ulid", "typeid", "nanoid", "cuid", "ksuid", "objectid", "tsid", "xid"];
|
|
11
11
|
/**
|
|
12
12
|
* The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.
|
|
13
13
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generators.d.mts","names":[],"sources":["../src/generators.ts"],"mappings":";;AAWA;;;;
|
|
1
|
+
{"version":3,"file":"generators.d.mts","names":[],"sources":["../src/generators.ts"],"mappings":";;AAWA;;;;AAAuG;AAKvG;;cALa,aAAA;;AAKqB;;KAAtB,WAAA,WAAsB,aAAA"}
|
package/build/generators.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=[`uuid`,`ulid`,`typeid`,`nanoid`,`cuid`,`ksuid`,`objectid`,`tsid`];export{e as ID_GENERATORS};
|
|
1
|
+
const e=[`uuid`,`ulid`,`typeid`,`nanoid`,`cuid`,`ksuid`,`objectid`,`tsid`,`xid`];export{e as ID_GENERATORS};
|
|
2
2
|
//# sourceMappingURL=generators.mjs.map
|
package/build/generators.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generators.mjs","names":[],"sources":["../src/generators.ts"],"sourcesContent":["/**\n * The canonical, ordered list of ID generators supported by uniku.\n *\n * This is the single source of truth for the set of supported ID generators.\n * Tools that need to enumerate or validate ID generators (for example,\n * `@uniku/cli`'s `--type` flag) should derive from this array rather than\n * hand-copying the union.\n */\n// Plain `as const` (not `as const satisfies readonly string[]`): the `satisfies`\n// wrapper defeats isolatedDeclarations' trivial declaration emit for this export.\n// All members are string literals, so the shape guard adds nothing here anyway.\nexport const ID_GENERATORS = ['uuid', 'ulid', 'typeid', 'nanoid', 'cuid', 'ksuid', 'objectid', 'tsid'] as const\n\n/**\n * The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.\n */\nexport type IdGenerator = (typeof ID_GENERATORS)[number]\n"],"mappings":"AAWA,MAAa,EAAgB,CAAC,OAAQ,OAAQ,SAAU,SAAU,OAAQ,QAAS,WAAY,
|
|
1
|
+
{"version":3,"file":"generators.mjs","names":[],"sources":["../src/generators.ts"],"sourcesContent":["/**\n * The canonical, ordered list of ID generators supported by uniku.\n *\n * This is the single source of truth for the set of supported ID generators.\n * Tools that need to enumerate or validate ID generators (for example,\n * `@uniku/cli`'s `--type` flag) should derive from this array rather than\n * hand-copying the union.\n */\n// Plain `as const` (not `as const satisfies readonly string[]`): the `satisfies`\n// wrapper defeats isolatedDeclarations' trivial declaration emit for this export.\n// All members are string literals, so the shape guard adds nothing here anyway.\nexport const ID_GENERATORS = ['uuid', 'ulid', 'typeid', 'nanoid', 'cuid', 'ksuid', 'objectid', 'tsid', 'xid'] as const\n\n/**\n * The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.\n */\nexport type IdGenerator = (typeof ID_GENERATORS)[number]\n"],"mappings":"AAWA,MAAa,EAAgB,CAAC,OAAQ,OAAQ,SAAU,SAAU,OAAQ,QAAS,WAAY,OAAQ,KAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,KAqCD,aAAA;EArCZ;;;;AAAa;EA2CX,MAAA,GAAS,UAAA;EANC;;;;EAWV,QAAA;;;;EAIA,IAAA;AAAA;AAAA,KAGU,MAAA;;GAIT,IAAA;GAEA,OAAA,EAAS,aAAA;;;;AAKY;EAAtB,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;AAyLH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAR,MAAA,EAAQ,MAAA"}
|
package/build/nanoid/nanoid.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{InvalidInputError as e,UniqueIdError as t}from"../errors.mjs";const n=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,r=2048,i=/^[A-Za-z0-9_-]
|
|
1
|
+
import{InvalidInputError as e,UniqueIdError as t}from"../errors.mjs";const n=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,r=2048,i=/^[A-Za-z0-9_-]+$/,a=new TextDecoder;let o,s=``,c=0;function l(e){let t=Math.min(e*128,65536);if((!o||o.length<t)&&(o=new Uint8Array(t)),c+e>s.length){crypto.getRandomValues(o);for(let e=0;e<o.length;e++)o[e]=n.charCodeAt(o[e]&63);s=a.decode(o),c=0}}function u(e){l(e);let t=s.substring(c,c+e);return c+=e,t}function d(t){if(t.length<2)throw new e(`NANOID_ALPHABET_TOO_SHORT`,`Alphabet must contain at least 2 characters`);if(t.length>256)throw new e(`NANOID_ALPHABET_TOO_LONG`,`Alphabet must not exceed 256 characters`);let n=new Set;for(let r of t){let t=r.charCodeAt(0);if(t<32||t>126)throw new e(`NANOID_ALPHABET_INVALID_CHAR`,`Alphabet must contain only printable ASCII characters (32-126)`);if(n.has(r))throw new e(`NANOID_ALPHABET_DUPLICATE`,`Duplicate character in alphabet: "${r}"`);n.add(r)}}function f(t){if(!Number.isInteger(t)||t<0)throw new e(`NANOID_SIZE_INVALID`,`Size must be a non-negative integer`);if(t>r)throw new e(`NANOID_SIZE_TOO_LARGE`,`Size must not exceed ${r}`)}function p(t){if(t===void 0)return u(21);let r=21,i=n,a;if(typeof t==`number`?r=t:(r=t.size??21,i=t.alphabet??`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,a=t.random,t.alphabet!==void 0&&d(i)),f(r),r===0)return``;if(i===`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`&&a===void 0)return u(r);let o=i.length;if(!(o&o-1)){let t=o-1;if(a&&a.length<r)throw new e(`NANOID_RANDOM_BYTES_INSUFFICIENT`,`Insufficient random bytes: need ${r}, have ${a.length}`);let n=a?.subarray(0,r)??globalThis.crypto.getRandomValues(new Uint8Array(r)),s=``;for(let e=0;e<r;e++)s+=i[n[e]&t];return s}let s=(2<<31-Math.clz32(o-1|1))-1,c=Math.ceil(1.6*s*r/o),l=``,p=0;for(;l.length<r;){let t;if(a){if(a.length-p<c)throw new e(`NANOID_RANDOM_BYTES_INSUFFICIENT`,`Insufficient random bytes: need at least ${c} more, have ${a.length-p}`);t=a.subarray(p,p+c),p+=c}else t=globalThis.crypto.getRandomValues(new Uint8Array(c));for(let e=0;e<t.length&&l.length<r;e++){let n=t[e]&s;n<o&&(l+=i[n])}}return l}function m(e){return typeof e==`string`&&e.length>0&&i.test(e)}const h=Object.assign(p,{isValid:m});export{e as InvalidInputError,n as URL_ALPHABET,t as UniqueIdError,h as nanoid};
|
|
2
2
|
//# sourceMappingURL=nanoid.mjs.map
|
|
@@ -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. It consumes `size`\n// bytes directly without the shared helper's UUID/CUID2-oriented branches.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nlet pool: Uint8Array | undefined\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!pool || pool.length < size) {\n pool = new Uint8Array(size)\n crypto.getRandomValues(pool)\n poolOffset = 0\n } else if (poolOffset + bytes > pool.length) {\n crypto.getRandomValues(pool)\n poolOffset = 0\n }\n poolOffset += bytes\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n let id = ''\n for (let i = poolOffset - size; i < poolOffset; i++) {\n id += URL_ALPHABET[pool![i] & 63]\n }\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `size` bytes needed.\n * For other alphabets: ~size * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_SHORT', 'Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_LONG', 'Alphabet must not exceed 256 characters')\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'NANOID_ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('NANOID_ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`)\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate size parameter\n */\nfunction validateSize(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('NANOID_SIZE_INVALID', 'Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('NANOID_SIZE_TOO_LARGE', `Size must not exceed ${MAX_SIZE}`)\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom size\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Size must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested size\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
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 `size` bytes needed.\n * For other alphabets: ~size * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_SHORT', 'Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_LONG', 'Alphabet must not exceed 256 characters')\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'NANOID_ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('NANOID_ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`)\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate size parameter\n */\nfunction validateSize(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('NANOID_SIZE_INVALID', 'Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('NANOID_SIZE_TOO_LARGE', `Size must not exceed ${MAX_SIZE}`)\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom size\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Size must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested size\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/xid/xid.d.ts
|
|
4
|
+
type XidOptions = {
|
|
5
|
+
/** First three bytes used as the XID machine identity. */machineId?: Uint8Array; /** 16-bit process identity. */
|
|
6
|
+
processId?: number; /** Unix timestamp in seconds. Defaults to the current second. */
|
|
7
|
+
secs?: number; /** 24-bit counter. Explicit values do not consume shared state. */
|
|
8
|
+
counter?: number;
|
|
9
|
+
};
|
|
10
|
+
type Xid = {
|
|
11
|
+
(): string;
|
|
12
|
+
<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf;
|
|
13
|
+
(options?: XidOptions, buf?: undefined, offset?: number): string;
|
|
14
|
+
toBytes(id: string): Uint8Array;
|
|
15
|
+
fromBytes(bytes: Uint8Array): string;
|
|
16
|
+
timestamp(id: string): number;
|
|
17
|
+
isValid(id: unknown): id is string;
|
|
18
|
+
NIL: string;
|
|
19
|
+
MAX: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Generate a 20-character rs/xid-compatible identifier.
|
|
23
|
+
*
|
|
24
|
+
* XID embeds seconds, a lazily-random per-runtime identity, and a shared
|
|
25
|
+
* always-incrementing counter. In Cloudflare Workers, time is frozen during a
|
|
26
|
+
* request, so the counter preserves ordering for IDs made in that request.
|
|
27
|
+
*/
|
|
28
|
+
declare const xid: Xid;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { BufferError, InvalidInputError, ParseError, UniqueIdError, Xid, XidOptions, xid };
|
|
31
|
+
//# sourceMappingURL=xid.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xid.d.mts","names":[],"sources":["../../src/xid/xid.ts"],"mappings":";;;KAgBY,UAAA;4DAEV,SAAA,GAAY,UAAA,EAFd;EAIE,SAAA;EAEA,IAAA;EAEA,OAAA;AAAA;AAAA,KAGU,GAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,UAAA,cAAwB,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACpG,OAAA,GAAU,UAAA,EAAY,GAAA,cAAiB,MAAA;EACxC,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EACtB,GAAA;EACA,GAAA;AAAA;;;;;;;;cA8HW,GAAA,EAAK,GAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as e,t}from"../random-Chp-Nkzi.mjs";import{n,t as r}from"../validation-CTNpXm94.mjs";import{BufferError as i,InvalidInputError as a,ParseError as o,UniqueIdError as s}from"../errors.mjs";import{n as c}from"../bytes-xqWxFYsM.mjs";const l=`0123456789abcdefghijklmnopqrstuv`,u=Uint8Array.from(l,e=>e.charCodeAt(0)),d=Array(20),f=[,,,,,,],p=new Uint8Array(65536);p.fill(255);for(let e=0;e<32;e+=1)p[l.charCodeAt(e)]=e;function m(e){if(e.length!==12)throw new i(`XID_BYTES_INVALID_LENGTH`,`XID bytes must be exactly 12 bytes, got ${e.length}`);return d[0]=u[e[0]>>3],d[1]=u[(e[0]<<2|e[1]>>6)&31],d[2]=u[e[1]>>1&31],d[3]=u[(e[1]<<4|e[2]>>4)&31],d[4]=u[(e[2]<<1|e[3]>>7)&31],d[5]=u[e[3]>>2&31],d[6]=u[(e[3]<<3|e[4]>>5)&31],d[7]=u[e[4]&31],d[8]=u[e[5]>>3],d[9]=u[(e[5]<<2|e[6]>>6)&31],d[10]=u[e[6]>>1&31],d[11]=u[(e[6]<<4|e[7]>>4)&31],d[12]=u[(e[7]<<1|e[8]>>7)&31],d[13]=u[e[8]>>2&31],d[14]=u[(e[8]<<3|e[9]>>5)&31],d[15]=u[e[9]&31],d[16]=u[e[10]>>3],d[17]=u[(e[10]<<2|e[11]>>6)&31],d[18]=u[e[11]>>1&31],d[19]=u[e[11]<<4&31],String.fromCharCode(...d)}function h(e,t){let n=t>>>16,r=t>>>8&255,i=t&255;return f[0]=u[(e<<3|n>>5)&31],f[1]=u[n&31],f[2]=u[r>>3],f[3]=u[(r<<2|i>>6)&31],f[4]=u[i>>1&31],f[5]=u[i<<4&31],String.fromCharCode(...f)}function g(e){if(e.length!==20)throw new o(`XID_INVALID_LENGTH`,`XID string must be 20 characters, got ${e.length}`);let t=new Uint8Array(20);for(let n=0;n<20;n+=1){let r=p[e.charCodeAt(n)];if(r===255)throw new o(`XID_INVALID_CHAR`,`Invalid XID character: ${e[n]}`);t[n]=r}if(t[19]!==0&&t[19]!==16)throw new o(`XID_NON_CANONICAL`,`XID trailing bits must be canonically encoded`);return new Uint8Array([t[0]<<3|t[1]>>2,t[1]<<6|t[2]<<1|t[3]>>4,t[3]<<4|t[4]>>1,t[4]<<7|t[5]<<2|t[6]>>3,t[6]<<5|t[7],t[8]<<3|t[9]>>2,t[9]<<6|t[10]<<1|t[11]>>4,t[11]<<4|t[12]>>1,t[12]<<7|t[13]<<2|t[14]>>3,t[14]<<5|t[15],t[16]<<3|t[17]>>2,t[17]<<6|t[18]<<1|t[19]>>4])}const _=4294967295,v=65535,y=16777215,b=/^[0-9a-v]{19}[0g]$/,x=new Uint8Array(12);let S=-1,C=``;const w={machineId:void 0,processId:void 0,counter:void 0};function T(e){return t(e).slice()}function E(){w.machineId===void 0&&(w.machineId=T(3)),w.processId===void 0&&(w.processId=e()&v)}function D(){return w.counter===void 0&&(w.counter=e()&y),w.counter=w.counter+1&y,w.counter}function O(e,t,n,r,i,a){c(i,a,e),i.set(t.subarray(0,3),a+4),i[a+7]=n>>>8,i[a+8]=n&255,i[a+9]=r>>>16,i[a+10]=r>>>8&255,i[a+11]=r&255}function k(e){if(e.machineId!==void 0&&e.machineId.length<3)throw new a(`XID_MACHINE_ID_BYTES_TOO_SHORT`,`Machine ID bytes length must be >= 3 for XID`);if(e.processId!==void 0&&!r(e.processId,0,v))throw new a(`XID_PROCESS_ID_OUT_OF_RANGE`,`Process ID must be between 0 and ${v}`);if(e.secs!==void 0&&!r(e.secs,0,_))throw new a(`XID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be between 0 and ${_}`);if(e.counter!==void 0&&!r(e.counter,0,y))throw new a(`XID_COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${y}`)}function A(e,t,r=0){if(e===void 0&&t===void 0){E();let e=Math.floor(Date.now()/1e3),t=D();return e!==S&&(O(e,w.machineId,w.processId,t,x,0),C=m(x).slice(0,14),S=e),C+h(w.processId&255,t)}e!==void 0&&k(e);let a=e?.secs??Math.floor(Date.now()/1e3);(e?.machineId===void 0||e.processId===void 0)&&E();let o=e?.machineId??w.machineId,s=e?.processId??w.processId,c=e?.counter??D();if(t!==void 0){if(!n(t,r,12))throw new i(`XID_BUFFER_OUT_OF_BOUNDS`,`XID byte range ${r}:${r+11} is out of buffer bounds`);return O(a,o,s,c,t,r),t}return O(a,o,s,c,x,0),m(x)}function j(e){return g(e)}function M(e){return m(e)}function N(e){let t=g(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function P(e){return typeof e==`string`&&b.test(e)}const F=Object.assign(A,{toBytes:j,fromBytes:M,timestamp:N,isValid:P,NIL:`0`.repeat(20),MAX:m(new Uint8Array(12).fill(255))});export{i as BufferError,a as InvalidInputError,o as ParseError,s as UniqueIdError,F as xid};
|
|
2
|
+
//# sourceMappingURL=xid.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xid.mjs","names":["XID_BYTES"],"sources":["../../src/xid/base32hex.ts","../../src/xid/xid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\nconst ENCODING = '0123456789abcdefghijklmnopqrstuv'\nconst ENCODING_CODES = Uint8Array.from(ENCODING, (character) => character.charCodeAt(0))\nconst XID_BYTES = 12\nconst XID_LENGTH = 20\nconst ENCODED = new Array<number>(XID_LENGTH)\nconst COUNTER_SUFFIX = new Array<number>(6)\n\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255)\nfor (let i = 0; i < ENCODING.length; i += 1) {\n DECODING[ENCODING.charCodeAt(i)] = i\n}\n\n/** Encode the 12 canonical XID bytes as lowercase base32hex. */\nexport function encodeBase32Hex(bytes: Uint8Array): string {\n if (bytes.length !== XID_BYTES) {\n throw new BufferError(\n 'XID_BYTES_INVALID_LENGTH',\n `XID bytes must be exactly ${XID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n ENCODED[0] = ENCODING_CODES[bytes[0] >> 3]\n ENCODED[1] = ENCODING_CODES[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f]\n ENCODED[2] = ENCODING_CODES[(bytes[1] >> 1) & 0x1f]\n ENCODED[3] = ENCODING_CODES[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f]\n ENCODED[4] = ENCODING_CODES[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f]\n ENCODED[5] = ENCODING_CODES[(bytes[3] >> 2) & 0x1f]\n ENCODED[6] = ENCODING_CODES[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f]\n ENCODED[7] = ENCODING_CODES[bytes[4] & 0x1f]\n ENCODED[8] = ENCODING_CODES[bytes[5] >> 3]\n ENCODED[9] = ENCODING_CODES[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f]\n ENCODED[10] = ENCODING_CODES[(bytes[6] >> 1) & 0x1f]\n ENCODED[11] = ENCODING_CODES[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f]\n ENCODED[12] = ENCODING_CODES[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f]\n ENCODED[13] = ENCODING_CODES[(bytes[8] >> 2) & 0x1f]\n ENCODED[14] = ENCODING_CODES[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f]\n ENCODED[15] = ENCODING_CODES[bytes[9] & 0x1f]\n ENCODED[16] = ENCODING_CODES[bytes[10] >> 3]\n ENCODED[17] = ENCODING_CODES[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f]\n ENCODED[18] = ENCODING_CODES[(bytes[11] >> 1) & 0x1f]\n ENCODED[19] = ENCODING_CODES[(bytes[11] << 4) & 0x1f]\n return String.fromCharCode(...ENCODED)\n}\n\n/** Encode the six XID characters affected by the 24-bit counter. */\nexport function encodeCounterSuffix(lastIdentityByte: number, counter: number): string {\n const high = counter >>> 16\n const middle = (counter >>> 8) & 0xff\n const low = counter & 0xff\n COUNTER_SUFFIX[0] = ENCODING_CODES[((lastIdentityByte << 3) | (high >> 5)) & 0x1f]\n COUNTER_SUFFIX[1] = ENCODING_CODES[high & 0x1f]\n COUNTER_SUFFIX[2] = ENCODING_CODES[middle >> 3]\n COUNTER_SUFFIX[3] = ENCODING_CODES[((middle << 2) | (low >> 6)) & 0x1f]\n COUNTER_SUFFIX[4] = ENCODING_CODES[(low >> 1) & 0x1f]\n COUNTER_SUFFIX[5] = ENCODING_CODES[(low << 4) & 0x1f]\n return String.fromCharCode(...COUNTER_SUFFIX)\n}\n\n/** Decode a canonical lowercase base32hex XID string to its 12 bytes. */\nexport function decodeBase32Hex(id: string): Uint8Array {\n if (id.length !== XID_LENGTH) {\n throw new ParseError('XID_INVALID_LENGTH', `XID string must be ${XID_LENGTH} characters, got ${id.length}`)\n }\n\n const values = new Uint8Array(XID_LENGTH)\n for (let i = 0; i < XID_LENGTH; i += 1) {\n const value = DECODING[id.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('XID_INVALID_CHAR', `Invalid XID character: ${id[i]}`)\n }\n values[i] = value\n }\n\n if (values[19] !== 0 && values[19] !== 16) {\n throw new ParseError('XID_NON_CANONICAL', 'XID trailing bits must be canonically encoded')\n }\n\n return new Uint8Array([\n (values[0] << 3) | (values[1] >> 2),\n (values[1] << 6) | (values[2] << 1) | (values[3] >> 4),\n (values[3] << 4) | (values[4] >> 1),\n (values[4] << 7) | (values[5] << 2) | (values[6] >> 3),\n (values[6] << 5) | values[7],\n (values[8] << 3) | (values[9] >> 2),\n (values[9] << 6) | (values[10] << 1) | (values[11] >> 4),\n (values[11] << 4) | (values[12] >> 1),\n (values[12] << 7) | (values[13] << 2) | (values[14] >> 3),\n (values[14] << 5) | values[15],\n (values[16] << 3) | (values[17] >> 2),\n (values[17] << 6) | (values[18] << 1) | (values[19] >> 4),\n ])\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase32Hex, encodeBase32Hex, encodeCounterSuffix } from './base32hex'\n\nconst XID_BYTES = 12\nconst MACHINE_ID_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_PROCESS_ID = 0xffff\nconst MAX_COUNTER = 0xffffff\nconst XID_REGEX = /^[0-9a-v]{19}[0g]$/\nconst stringBuffer = new Uint8Array(XID_BYTES)\nlet cachedPrefixSecs = -1\nlet cachedPrefix = ''\n\nexport type XidOptions = {\n /** First three bytes used as the XID machine identity. */\n machineId?: Uint8Array\n /** 16-bit process identity. */\n processId?: number\n /** Unix timestamp in seconds. Defaults to the current second. */\n secs?: number\n /** 24-bit counter. Explicit values do not consume shared state. */\n counter?: number\n}\n\nexport type Xid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: XidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n NIL: string\n MAX: string\n}\n\ntype XidState = {\n machineId: Uint8Array | undefined\n processId: number | undefined\n counter: number | undefined\n}\n\nconst state: XidState = { machineId: undefined, processId: undefined, counter: undefined }\n\n/** Copy pooled bytes before another random draw can refill their backing pool. */\nfunction freshRandom(count: number): Uint8Array {\n return randomBytes(count).slice()\n}\n\nfunction initializeIdentity(): void {\n if (state.machineId === undefined) state.machineId = freshRandom(MACHINE_ID_BYTES)\n if (state.processId === undefined) state.processId = randomUint32() & MAX_PROCESS_ID\n}\n\nfunction nextCounter(): number {\n if (state.counter === undefined) state.counter = randomUint32() & MAX_COUNTER\n state.counter = (state.counter + 1) & MAX_COUNTER\n return state.counter\n}\n\nfunction writeXidBytesUnchecked(\n secs: number,\n machineId: Uint8Array,\n processId: number,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n writeTimestamp32(buf, offset, secs)\n buf.set(machineId.subarray(0, MACHINE_ID_BYTES), offset + 4)\n buf[offset + 7] = processId >>> 8\n buf[offset + 8] = processId & 0xff\n buf[offset + 9] = counter >>> 16\n buf[offset + 10] = (counter >>> 8) & 0xff\n buf[offset + 11] = counter & 0xff\n}\n\nfunction validateOptions(options: XidOptions): void {\n if (options.machineId !== undefined && options.machineId.length < MACHINE_ID_BYTES) {\n throw new InvalidInputError(\n 'XID_MACHINE_ID_BYTES_TOO_SHORT',\n `Machine ID bytes length must be >= ${MACHINE_ID_BYTES} for XID`,\n )\n }\n if (options.processId !== undefined && !isIntegerInRange(options.processId, 0, MAX_PROCESS_ID)) {\n throw new InvalidInputError('XID_PROCESS_ID_OUT_OF_RANGE', `Process ID must be between 0 and ${MAX_PROCESS_ID}`)\n }\n if (options.secs !== undefined && !isIntegerInRange(options.secs, 0, MAX_SECS)) {\n throw new InvalidInputError('XID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)\n }\n if (options.counter !== undefined && !isIntegerInRange(options.counter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('XID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)\n }\n}\n\nfunction xidFn(options?: XidOptions, buf?: undefined, offset?: number): string\nfunction xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf\nfunction xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?: TBuf, offset = 0): string | TBuf {\n if (options === undefined && buf === undefined) {\n initializeIdentity()\n const secs = Math.floor(Date.now() / 1000)\n const counter = nextCounter()\n if (secs !== cachedPrefixSecs) {\n writeXidBytesUnchecked(secs, state.machineId!, state.processId!, counter, stringBuffer, 0)\n cachedPrefix = encodeBase32Hex(stringBuffer).slice(0, 14)\n cachedPrefixSecs = secs\n }\n return cachedPrefix + encodeCounterSuffix(state.processId! & 0xff, counter)\n }\n\n if (options !== undefined) validateOptions(options)\n\n const secs = options?.secs ?? Math.floor(Date.now() / 1000)\n if (options?.machineId === undefined || options.processId === undefined) {\n initializeIdentity()\n }\n const machineId = options?.machineId ?? state.machineId!\n const processId = options?.processId ?? state.processId!\n const counter = options?.counter ?? nextCounter()\n\n if (buf !== undefined) {\n if (!isWritableRange(buf, offset, XID_BYTES)) {\n throw new BufferError(\n 'XID_BUFFER_OUT_OF_BOUNDS',\n `XID byte range ${offset}:${offset + 11} is out of buffer bounds`,\n )\n }\n writeXidBytesUnchecked(secs, machineId, processId, counter, buf, offset)\n return buf\n }\n\n writeXidBytesUnchecked(secs, machineId, processId, counter, stringBuffer, 0)\n return encodeBase32Hex(stringBuffer)\n}\n\nfunction toBytes(id: string): Uint8Array {\n return decodeBase32Hex(id)\n}\n\nfunction fromBytes(bytes: Uint8Array): string {\n return encodeBase32Hex(bytes)\n}\n\nfunction timestamp(id: string): number {\n const bytes = decodeBase32Hex(id)\n return (((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0) * 1000\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && XID_REGEX.test(id)\n}\n\n/**\n * Generate a 20-character rs/xid-compatible identifier.\n *\n * XID embeds seconds, a lazily-random per-runtime identity, and a shared\n * always-incrementing counter. In Cloudflare Workers, time is frozen during a\n * request, so the counter preserves ordering for IDs made in that request.\n */\nexport const xid: Xid = Object.assign(xidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(20),\n MAX: encodeBase32Hex(new Uint8Array(12).fill(0xff)),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "uniku",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alberto Schiabel",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"nanoid",
|
|
29
29
|
"ksuid",
|
|
30
30
|
"objectid",
|
|
31
|
+
"xid",
|
|
31
32
|
"mongodb",
|
|
32
33
|
"tsid",
|
|
33
34
|
"snowflake",
|
|
@@ -110,6 +111,11 @@
|
|
|
110
111
|
"import": "./build/tsid/tsid.mjs",
|
|
111
112
|
"default": "./build/tsid/tsid.mjs"
|
|
112
113
|
},
|
|
114
|
+
"./xid": {
|
|
115
|
+
"types": "./build/xid/xid.d.mts",
|
|
116
|
+
"import": "./build/xid/xid.mjs",
|
|
117
|
+
"default": "./build/xid/xid.mjs"
|
|
118
|
+
},
|
|
113
119
|
"./errors": {
|
|
114
120
|
"types": "./build/errors.d.mts",
|
|
115
121
|
"import": "./build/errors.mjs",
|
|
@@ -130,13 +136,15 @@
|
|
|
130
136
|
"@owpz/ksuid": "^25.7.20",
|
|
131
137
|
"@paralleldrive/cuid2": "^3.3.0",
|
|
132
138
|
"@types/bun": "^1.3.14",
|
|
133
|
-
"effect": "4.0.0-beta.93",
|
|
134
139
|
"bson": "^7.3.1",
|
|
140
|
+
"effect": "4.0.0-beta.93",
|
|
135
141
|
"nanoid": "^5.1.16",
|
|
142
|
+
"nexid": "^2.1.0",
|
|
136
143
|
"tsid-ts": "^0.0.9",
|
|
137
144
|
"typeid-js": "1.2.0",
|
|
138
145
|
"ulid": "3.0.2",
|
|
139
|
-
"uuid": "14.0.1"
|
|
146
|
+
"uuid": "14.0.1",
|
|
147
|
+
"xid-ts": "^1.3.0"
|
|
140
148
|
},
|
|
141
149
|
"scripts": {
|
|
142
150
|
"clean": "rimraf build .turbo",
|
|
@@ -153,7 +161,8 @@
|
|
|
153
161
|
"smoke:runtime:bun": "bun scripts/runtime-smoke.mjs",
|
|
154
162
|
"smoke:runtime:deno": "deno run --node-modules-dir=manual scripts/runtime-smoke.mjs",
|
|
155
163
|
"bench": "vitest bench --run --project unit",
|
|
156
|
-
"bench:compat": "
|
|
164
|
+
"bench:compat": "bun scripts/bench-compat.ts",
|
|
165
|
+
"bench:cold-start": "node scripts/bench-cold-start.mjs",
|
|
157
166
|
"bench:summary": "bun scripts/bench-summary.ts",
|
|
158
167
|
"bench:compare": "git show origin/gh-benchmarks:baseline.json > baseline.json 2>/dev/null && bun scripts/bench-compare.ts || echo 'No baseline found. Run benchmarks on main first.'",
|
|
159
168
|
"bundle:summary": "bun scripts/bundle-summary.ts",
|
package/src/generators.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Plain `as const` (not `as const satisfies readonly string[]`): the `satisfies`
|
|
10
10
|
// wrapper defeats isolatedDeclarations' trivial declaration emit for this export.
|
|
11
11
|
// All members are string literals, so the shape guard adds nothing here anyway.
|
|
12
|
-
export const ID_GENERATORS = ['uuid', 'ulid', 'typeid', 'nanoid', 'cuid', 'ksuid', 'objectid', 'tsid'] as const
|
|
12
|
+
export const ID_GENERATORS = ['uuid', 'ulid', 'typeid', 'nanoid', 'cuid', 'ksuid', 'objectid', 'tsid', 'xid'] as const
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.
|
package/src/nanoid/nanoid.ts
CHANGED
|
@@ -7,32 +7,34 @@ const DEFAULT_SIZE = 21
|
|
|
7
7
|
const MAX_SIZE = 2048
|
|
8
8
|
const NANOID_REGEX = /^[A-Za-z0-9_-]+$/
|
|
9
9
|
|
|
10
|
-
// Keep Nanoid's private pool for its default hot path.
|
|
11
|
-
//
|
|
10
|
+
// Keep Nanoid's private pool for its default hot path. Translate random bytes
|
|
11
|
+
// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.
|
|
12
12
|
const POOL_SIZE_MULTIPLIER = 128
|
|
13
13
|
const MAX_POOL_SIZE = 65_536
|
|
14
|
-
|
|
14
|
+
const ASCII_DECODER = new TextDecoder()
|
|
15
|
+
let poolBytes: Uint8Array | undefined
|
|
16
|
+
let characterPool = ''
|
|
15
17
|
let poolOffset = 0
|
|
16
18
|
|
|
17
19
|
function fillPool(bytes: number): void {
|
|
18
20
|
const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
if (!poolBytes || poolBytes.length < size) {
|
|
22
|
+
poolBytes = new Uint8Array(size)
|
|
23
|
+
}
|
|
24
|
+
if (poolOffset + bytes > characterPool.length) {
|
|
25
|
+
crypto.getRandomValues(poolBytes)
|
|
26
|
+
for (let i = 0; i < poolBytes.length; i++) {
|
|
27
|
+
poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)
|
|
28
|
+
}
|
|
29
|
+
characterPool = ASCII_DECODER.decode(poolBytes)
|
|
25
30
|
poolOffset = 0
|
|
26
31
|
}
|
|
27
|
-
poolOffset += bytes
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function defaultAlphabetFromPool(size: number): string {
|
|
31
35
|
fillPool(size)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
id += URL_ALPHABET[pool![i] & 63]
|
|
35
|
-
}
|
|
36
|
+
const id = characterPool.substring(poolOffset, poolOffset + size)
|
|
37
|
+
poolOffset += size
|
|
36
38
|
return id
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BufferError, ParseError } from '../errors'
|
|
2
|
+
|
|
3
|
+
const ENCODING = '0123456789abcdefghijklmnopqrstuv'
|
|
4
|
+
const ENCODING_CODES = Uint8Array.from(ENCODING, (character) => character.charCodeAt(0))
|
|
5
|
+
const XID_BYTES = 12
|
|
6
|
+
const XID_LENGTH = 20
|
|
7
|
+
const ENCODED = new Array<number>(XID_LENGTH)
|
|
8
|
+
const COUNTER_SUFFIX = new Array<number>(6)
|
|
9
|
+
|
|
10
|
+
const DECODING = new Uint8Array(65536)
|
|
11
|
+
DECODING.fill(255)
|
|
12
|
+
for (let i = 0; i < ENCODING.length; i += 1) {
|
|
13
|
+
DECODING[ENCODING.charCodeAt(i)] = i
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Encode the 12 canonical XID bytes as lowercase base32hex. */
|
|
17
|
+
export function encodeBase32Hex(bytes: Uint8Array): string {
|
|
18
|
+
if (bytes.length !== XID_BYTES) {
|
|
19
|
+
throw new BufferError(
|
|
20
|
+
'XID_BYTES_INVALID_LENGTH',
|
|
21
|
+
`XID bytes must be exactly ${XID_BYTES} bytes, got ${bytes.length}`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ENCODED[0] = ENCODING_CODES[bytes[0] >> 3]
|
|
26
|
+
ENCODED[1] = ENCODING_CODES[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f]
|
|
27
|
+
ENCODED[2] = ENCODING_CODES[(bytes[1] >> 1) & 0x1f]
|
|
28
|
+
ENCODED[3] = ENCODING_CODES[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f]
|
|
29
|
+
ENCODED[4] = ENCODING_CODES[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f]
|
|
30
|
+
ENCODED[5] = ENCODING_CODES[(bytes[3] >> 2) & 0x1f]
|
|
31
|
+
ENCODED[6] = ENCODING_CODES[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f]
|
|
32
|
+
ENCODED[7] = ENCODING_CODES[bytes[4] & 0x1f]
|
|
33
|
+
ENCODED[8] = ENCODING_CODES[bytes[5] >> 3]
|
|
34
|
+
ENCODED[9] = ENCODING_CODES[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f]
|
|
35
|
+
ENCODED[10] = ENCODING_CODES[(bytes[6] >> 1) & 0x1f]
|
|
36
|
+
ENCODED[11] = ENCODING_CODES[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f]
|
|
37
|
+
ENCODED[12] = ENCODING_CODES[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f]
|
|
38
|
+
ENCODED[13] = ENCODING_CODES[(bytes[8] >> 2) & 0x1f]
|
|
39
|
+
ENCODED[14] = ENCODING_CODES[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f]
|
|
40
|
+
ENCODED[15] = ENCODING_CODES[bytes[9] & 0x1f]
|
|
41
|
+
ENCODED[16] = ENCODING_CODES[bytes[10] >> 3]
|
|
42
|
+
ENCODED[17] = ENCODING_CODES[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f]
|
|
43
|
+
ENCODED[18] = ENCODING_CODES[(bytes[11] >> 1) & 0x1f]
|
|
44
|
+
ENCODED[19] = ENCODING_CODES[(bytes[11] << 4) & 0x1f]
|
|
45
|
+
return String.fromCharCode(...ENCODED)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Encode the six XID characters affected by the 24-bit counter. */
|
|
49
|
+
export function encodeCounterSuffix(lastIdentityByte: number, counter: number): string {
|
|
50
|
+
const high = counter >>> 16
|
|
51
|
+
const middle = (counter >>> 8) & 0xff
|
|
52
|
+
const low = counter & 0xff
|
|
53
|
+
COUNTER_SUFFIX[0] = ENCODING_CODES[((lastIdentityByte << 3) | (high >> 5)) & 0x1f]
|
|
54
|
+
COUNTER_SUFFIX[1] = ENCODING_CODES[high & 0x1f]
|
|
55
|
+
COUNTER_SUFFIX[2] = ENCODING_CODES[middle >> 3]
|
|
56
|
+
COUNTER_SUFFIX[3] = ENCODING_CODES[((middle << 2) | (low >> 6)) & 0x1f]
|
|
57
|
+
COUNTER_SUFFIX[4] = ENCODING_CODES[(low >> 1) & 0x1f]
|
|
58
|
+
COUNTER_SUFFIX[5] = ENCODING_CODES[(low << 4) & 0x1f]
|
|
59
|
+
return String.fromCharCode(...COUNTER_SUFFIX)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Decode a canonical lowercase base32hex XID string to its 12 bytes. */
|
|
63
|
+
export function decodeBase32Hex(id: string): Uint8Array {
|
|
64
|
+
if (id.length !== XID_LENGTH) {
|
|
65
|
+
throw new ParseError('XID_INVALID_LENGTH', `XID string must be ${XID_LENGTH} characters, got ${id.length}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const values = new Uint8Array(XID_LENGTH)
|
|
69
|
+
for (let i = 0; i < XID_LENGTH; i += 1) {
|
|
70
|
+
const value = DECODING[id.charCodeAt(i)]
|
|
71
|
+
if (value === 255) {
|
|
72
|
+
throw new ParseError('XID_INVALID_CHAR', `Invalid XID character: ${id[i]}`)
|
|
73
|
+
}
|
|
74
|
+
values[i] = value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (values[19] !== 0 && values[19] !== 16) {
|
|
78
|
+
throw new ParseError('XID_NON_CANONICAL', 'XID trailing bits must be canonically encoded')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return new Uint8Array([
|
|
82
|
+
(values[0] << 3) | (values[1] >> 2),
|
|
83
|
+
(values[1] << 6) | (values[2] << 1) | (values[3] >> 4),
|
|
84
|
+
(values[3] << 4) | (values[4] >> 1),
|
|
85
|
+
(values[4] << 7) | (values[5] << 2) | (values[6] >> 3),
|
|
86
|
+
(values[6] << 5) | values[7],
|
|
87
|
+
(values[8] << 3) | (values[9] >> 2),
|
|
88
|
+
(values[9] << 6) | (values[10] << 1) | (values[11] >> 4),
|
|
89
|
+
(values[11] << 4) | (values[12] >> 1),
|
|
90
|
+
(values[12] << 7) | (values[13] << 2) | (values[14] >> 3),
|
|
91
|
+
(values[14] << 5) | values[15],
|
|
92
|
+
(values[16] << 3) | (values[17] >> 2),
|
|
93
|
+
(values[17] << 6) | (values[18] << 1) | (values[19] >> 4),
|
|
94
|
+
])
|
|
95
|
+
}
|
package/src/xid/xid.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { writeTimestamp32 } from '../common/bytes'
|
|
2
|
+
import { randomBytes, randomUint32 } from '../common/random'
|
|
3
|
+
import { isIntegerInRange, isWritableRange } from '../common/validation'
|
|
4
|
+
import { BufferError, InvalidInputError } from '../errors'
|
|
5
|
+
import { decodeBase32Hex, encodeBase32Hex, encodeCounterSuffix } from './base32hex'
|
|
6
|
+
|
|
7
|
+
const XID_BYTES = 12
|
|
8
|
+
const MACHINE_ID_BYTES = 3
|
|
9
|
+
const MAX_SECS = 0xffffffff
|
|
10
|
+
const MAX_PROCESS_ID = 0xffff
|
|
11
|
+
const MAX_COUNTER = 0xffffff
|
|
12
|
+
const XID_REGEX = /^[0-9a-v]{19}[0g]$/
|
|
13
|
+
const stringBuffer = new Uint8Array(XID_BYTES)
|
|
14
|
+
let cachedPrefixSecs = -1
|
|
15
|
+
let cachedPrefix = ''
|
|
16
|
+
|
|
17
|
+
export type XidOptions = {
|
|
18
|
+
/** First three bytes used as the XID machine identity. */
|
|
19
|
+
machineId?: Uint8Array
|
|
20
|
+
/** 16-bit process identity. */
|
|
21
|
+
processId?: number
|
|
22
|
+
/** Unix timestamp in seconds. Defaults to the current second. */
|
|
23
|
+
secs?: number
|
|
24
|
+
/** 24-bit counter. Explicit values do not consume shared state. */
|
|
25
|
+
counter?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Xid = {
|
|
29
|
+
(): string
|
|
30
|
+
<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf
|
|
31
|
+
(options?: XidOptions, buf?: undefined, offset?: number): string
|
|
32
|
+
toBytes(id: string): Uint8Array
|
|
33
|
+
fromBytes(bytes: Uint8Array): string
|
|
34
|
+
timestamp(id: string): number
|
|
35
|
+
isValid(id: unknown): id is string
|
|
36
|
+
NIL: string
|
|
37
|
+
MAX: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type XidState = {
|
|
41
|
+
machineId: Uint8Array | undefined
|
|
42
|
+
processId: number | undefined
|
|
43
|
+
counter: number | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const state: XidState = { machineId: undefined, processId: undefined, counter: undefined }
|
|
47
|
+
|
|
48
|
+
/** Copy pooled bytes before another random draw can refill their backing pool. */
|
|
49
|
+
function freshRandom(count: number): Uint8Array {
|
|
50
|
+
return randomBytes(count).slice()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function initializeIdentity(): void {
|
|
54
|
+
if (state.machineId === undefined) state.machineId = freshRandom(MACHINE_ID_BYTES)
|
|
55
|
+
if (state.processId === undefined) state.processId = randomUint32() & MAX_PROCESS_ID
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function nextCounter(): number {
|
|
59
|
+
if (state.counter === undefined) state.counter = randomUint32() & MAX_COUNTER
|
|
60
|
+
state.counter = (state.counter + 1) & MAX_COUNTER
|
|
61
|
+
return state.counter
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeXidBytesUnchecked(
|
|
65
|
+
secs: number,
|
|
66
|
+
machineId: Uint8Array,
|
|
67
|
+
processId: number,
|
|
68
|
+
counter: number,
|
|
69
|
+
buf: Uint8Array,
|
|
70
|
+
offset: number,
|
|
71
|
+
): void {
|
|
72
|
+
writeTimestamp32(buf, offset, secs)
|
|
73
|
+
buf.set(machineId.subarray(0, MACHINE_ID_BYTES), offset + 4)
|
|
74
|
+
buf[offset + 7] = processId >>> 8
|
|
75
|
+
buf[offset + 8] = processId & 0xff
|
|
76
|
+
buf[offset + 9] = counter >>> 16
|
|
77
|
+
buf[offset + 10] = (counter >>> 8) & 0xff
|
|
78
|
+
buf[offset + 11] = counter & 0xff
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateOptions(options: XidOptions): void {
|
|
82
|
+
if (options.machineId !== undefined && options.machineId.length < MACHINE_ID_BYTES) {
|
|
83
|
+
throw new InvalidInputError(
|
|
84
|
+
'XID_MACHINE_ID_BYTES_TOO_SHORT',
|
|
85
|
+
`Machine ID bytes length must be >= ${MACHINE_ID_BYTES} for XID`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
if (options.processId !== undefined && !isIntegerInRange(options.processId, 0, MAX_PROCESS_ID)) {
|
|
89
|
+
throw new InvalidInputError('XID_PROCESS_ID_OUT_OF_RANGE', `Process ID must be between 0 and ${MAX_PROCESS_ID}`)
|
|
90
|
+
}
|
|
91
|
+
if (options.secs !== undefined && !isIntegerInRange(options.secs, 0, MAX_SECS)) {
|
|
92
|
+
throw new InvalidInputError('XID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)
|
|
93
|
+
}
|
|
94
|
+
if (options.counter !== undefined && !isIntegerInRange(options.counter, 0, MAX_COUNTER)) {
|
|
95
|
+
throw new InvalidInputError('XID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function xidFn(options?: XidOptions, buf?: undefined, offset?: number): string
|
|
100
|
+
function xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf
|
|
101
|
+
function xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?: TBuf, offset = 0): string | TBuf {
|
|
102
|
+
if (options === undefined && buf === undefined) {
|
|
103
|
+
initializeIdentity()
|
|
104
|
+
const secs = Math.floor(Date.now() / 1000)
|
|
105
|
+
const counter = nextCounter()
|
|
106
|
+
if (secs !== cachedPrefixSecs) {
|
|
107
|
+
writeXidBytesUnchecked(secs, state.machineId!, state.processId!, counter, stringBuffer, 0)
|
|
108
|
+
cachedPrefix = encodeBase32Hex(stringBuffer).slice(0, 14)
|
|
109
|
+
cachedPrefixSecs = secs
|
|
110
|
+
}
|
|
111
|
+
return cachedPrefix + encodeCounterSuffix(state.processId! & 0xff, counter)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (options !== undefined) validateOptions(options)
|
|
115
|
+
|
|
116
|
+
const secs = options?.secs ?? Math.floor(Date.now() / 1000)
|
|
117
|
+
if (options?.machineId === undefined || options.processId === undefined) {
|
|
118
|
+
initializeIdentity()
|
|
119
|
+
}
|
|
120
|
+
const machineId = options?.machineId ?? state.machineId!
|
|
121
|
+
const processId = options?.processId ?? state.processId!
|
|
122
|
+
const counter = options?.counter ?? nextCounter()
|
|
123
|
+
|
|
124
|
+
if (buf !== undefined) {
|
|
125
|
+
if (!isWritableRange(buf, offset, XID_BYTES)) {
|
|
126
|
+
throw new BufferError(
|
|
127
|
+
'XID_BUFFER_OUT_OF_BOUNDS',
|
|
128
|
+
`XID byte range ${offset}:${offset + 11} is out of buffer bounds`,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
writeXidBytesUnchecked(secs, machineId, processId, counter, buf, offset)
|
|
132
|
+
return buf
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
writeXidBytesUnchecked(secs, machineId, processId, counter, stringBuffer, 0)
|
|
136
|
+
return encodeBase32Hex(stringBuffer)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function toBytes(id: string): Uint8Array {
|
|
140
|
+
return decodeBase32Hex(id)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function fromBytes(bytes: Uint8Array): string {
|
|
144
|
+
return encodeBase32Hex(bytes)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function timestamp(id: string): number {
|
|
148
|
+
const bytes = decodeBase32Hex(id)
|
|
149
|
+
return (((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0) * 1000
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isValid(id: unknown): id is string {
|
|
153
|
+
return typeof id === 'string' && XID_REGEX.test(id)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generate a 20-character rs/xid-compatible identifier.
|
|
158
|
+
*
|
|
159
|
+
* XID embeds seconds, a lazily-random per-runtime identity, and a shared
|
|
160
|
+
* always-incrementing counter. In Cloudflare Workers, time is frozen during a
|
|
161
|
+
* request, so the counter preserves ordering for IDs made in that request.
|
|
162
|
+
*/
|
|
163
|
+
export const xid: Xid = Object.assign(xidFn, {
|
|
164
|
+
toBytes,
|
|
165
|
+
fromBytes,
|
|
166
|
+
timestamp,
|
|
167
|
+
isValid,
|
|
168
|
+
NIL: '0'.repeat(20),
|
|
169
|
+
MAX: encodeBase32Hex(new Uint8Array(12).fill(0xff)),
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'
|