uniku 0.4.0 → 0.4.2

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
@@ -2,618 +2,110 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/uniku.svg)](https://www.npmjs.com/package/uniku)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/uniku.svg)](https://npmjs.com/package/uniku)
5
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/uniku)](https://bundlephobia.com/package/uniku)
6
5
  [![CI](https://github.com/jkomyno/uniku/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/jkomyno/uniku/actions/workflows/ci.yaml)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
+ [![Documentation](https://img.shields.io/badge/docs-read-5b5bd6.svg)](https://jkomyno.github.io/uniku/)
8
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
8
 
10
- One library. Every ID format. Every runtime. One runtime dependency (`@noble/hashes`, CUID2 only).
9
+ One library. Every ID format. Every modern JavaScript runtime.
11
10
 
12
11
  > **uniku** */uˈniːku/* — Maltese for "unique"
13
12
 
13
+ [Documentation](https://jkomyno.github.io/uniku/) · [Getting started](https://jkomyno.github.io/uniku/docs/getting-started/) · [Choosing an ID](https://jkomyno.github.io/uniku/docs/guides/choosing-an-id/) · [API reference](https://jkomyno.github.io/uniku/docs/reference/entry-points/) · [CLI](https://jkomyno.github.io/uniku/docs/cli/)
14
+
14
15
  ```ts
15
16
  import { uuidv7 } from 'uniku/uuid/v7'
16
17
 
17
18
  const id = uuidv7()
18
19
  // => "018e5e5c-7c8a-7000-8000-000000000000"
19
-
20
- // Time-ordered: IDs sort by creation time
21
- const [first, second, third] = [uuidv7(), uuidv7(), uuidv7()]
22
- console.log(first < second && second < third) // true
23
- ```
24
-
25
- ## At a Glance
26
-
27
- | | uniku | [uuid](https://github.com/uuidjs/uuid) | [typeid-js](https://github.com/jetify-com/typeid-js) | [nanoid](https://github.com/ai/nanoid) | [ulid](https://github.com/ulid/javascript) | [cuid2](https://github.com/paralleldrive/cuid2) | [ksuid](https://github.com/owpz/ksuid) | [bson](https://github.com/mongodb/js-bson) | [tsid-ts](https://github.com/yubinTW/tsid-ts) |
28
- |--------------------|:-----:|:----:|:---------:|:------:|:----:|:-----:|:-----:|:-----:|:-----:|
29
- | UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
30
- | UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
31
- | TypeID | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
32
- | Nanoid | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
33
- | ULID | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
34
- | CUID2 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
35
- | KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
36
- | ObjectID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
37
- | TSID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
38
- | XID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
39
- | Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
40
- | ESM-only | ✅ | ✅¹ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
41
- | Edge/Workers | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ⚠️ |
42
- | Byte ↔ String | ✅ | ✅ | ✅ | - | ⚠️² | - | ✅ | ✅ | ✅ |
43
-
44
- > **Notes:**
45
- > - Byte ↔ String conversion doesn't make sense for nanoid and cuid2, since they are string-native formats with no canonical binary representation.
46
- > - ¹ `uuid@13` is ESM-only; earlier versions support CommonJS.
47
- > - ² `ulid` only provides timestamp encoding/decoding, not full binary serialization.
48
-
49
- ### Works Everywhere
50
-
51
- <p align="center">
52
- <img src="https://img.shields.io/badge/Node.js-339933?logo=nodedotjs&logoColor=white" alt="Node.js">
53
- <img src="https://img.shields.io/badge/Deno-000000?logo=deno&logoColor=white" alt="Deno">
54
- <img src="https://img.shields.io/badge/Bun-000000?logo=bun&logoColor=white" alt="Bun">
55
- <img src="https://img.shields.io/badge/Cloudflare_Workers-F38020?logo=cloudflare&logoColor=white" alt="Cloudflare Workers">
56
- <img src="https://img.shields.io/badge/Vercel_Edge-000000?logo=vercel&logoColor=white" alt="Vercel Edge">
57
- <img src="https://img.shields.io/badge/Browsers-4285F4?logo=googlechrome&logoColor=white" alt="Browsers">
58
- </p>
59
-
60
- Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
61
-
62
- ## Performance
63
-
64
- | Generator | uniku vs npm |
65
- |-----------|-------------:|
66
- | ULID | **85× faster** |
67
- | CUID2 | **8× faster** |
68
- | KSUID | **1.5× faster** |
69
- | ObjectID | **1.1× faster** |
70
- | TSID | **1.7× faster** |
71
- | XID | See the current benchmark summary |
72
- | UUID v7 | **1.1× faster** |
73
- | Nanoid | **~comparable speed** |
74
- | Nanoid (10 chars) | npm is 1.1× faster |
75
- | TypeID | **2.6× faster** |
76
- | UUID v4 | npm is 1.1× faster |
77
-
78
- ## Which ID Should I Use?
79
-
80
- ### Quick Recommendations
81
-
82
- | Use Case | Recommended | Why |
83
- |----------|-------------|-----|
84
- | Database primary keys | **UUID v7** or **ULID** | Time-ordered for index efficiency |
85
- | API/domain identifiers | **TypeID** | UUID v7 with readable type prefixes like `user_...` |
86
- | URL shorteners | **Nanoid** | Compact, URL-safe characters |
87
- | Prevent enumeration | **CUID2** | Non-sequential, secure |
88
- | Maximum compatibility | **UUID v4** | Universal standard |
89
- | Distributed systems needing sortable, URL-safe IDs | **ULID** | Millisecond ordering + 80-bit entropy |
90
- | Very high-volume distributed systems | **KSUID** | Time-ordered with 128-bit entropy |
91
- | MongoDB `_id` compatibility | **ObjectID** | Drop-in match for MongoDB's native document ID format |
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 |
94
-
95
- ### Detailed Guide
96
-
97
- **UUID v4** — Use when you need maximum compatibility with existing systems.
98
- - 36 characters, 122-bit entropy
99
- - Universally supported by databases (native `UUID` type)
100
- - Not time-ordered (random distribution)
101
- - Best for: Legacy systems, cross-platform compatibility
102
-
103
- **UUID v7** — Use for database primary keys when you need RFC compliance.
104
- - 36 characters, time-ordered with millisecond precision
105
- - Native database support as `UUID` type
106
- - Strictly monotonic within same millisecond
107
- - Best for: PostgreSQL/MySQL primary keys, API identifiers
108
-
109
- **ULID** — Use for database primary keys when you want URL-safe IDs.
110
- - 26 characters, time-ordered with millisecond precision
111
- - URL-safe (no hyphens), case-insensitive
112
- - 80-bit random entropy per millisecond
113
- - Best for: APIs, URLs, distributed systems needing sortability
114
-
115
- **TypeID** — Use when API IDs should expose their entity type.
116
- - Prefix plus 26-character UUID v7 suffix, e.g. `user_01h2xcejqtf2nbrexx3vqjhp41`
117
- - Empty prefix support for canonical 26-character TypeIDs
118
- - Time-ordered through UUID v7, with lowercase snake_case type prefixes
119
- - Best for: Public API identifiers, logs, debugging, domain-specific IDs
120
-
121
- **KSUID** — Use when you need highest collision resistance.
122
- - 27 characters, time-ordered with second precision
123
- - URL-safe, 128-bit random entropy (highest of all formats)
124
- - Best for: Very high-volume distributed systems, event sourcing
125
-
126
- **ObjectID** — Use when you need drop-in compatibility with MongoDB.
127
- - 24-character lowercase hex, time-ordered with second precision
128
- - 4-byte timestamp + 5-byte per-process random + 3-byte always-incrementing counter
129
- - Counter never resets on a new timestamp (unlike ULID/UUIDv7/KSUID sequences)
130
- - Best for: MongoDB `_id` fields, systems already speaking the ObjectID wire format
131
-
132
- **TSID** — Use when you need a native 64-bit sortable integer ID, e.g. a database `BIGINT` primary key.
133
- - 64-bit Snowflake-style value, time-ordered with millisecond precision
134
- - Returns a `bigint` by default (the only uniku generator that isn't string-primary); `toString`/`fromString` convert to/from the 13-character canonical string
135
- - 42-bit timestamp + 10-bit node + 12-bit per-millisecond counter, with a persistent lazily-random node ID
136
- - Best for: relational database `BIGINT` primary keys, high-throughput distributed ID generation without central coordination
137
-
138
- **Nanoid** — Use for short, URL-friendly identifiers.
139
- - 21 characters (configurable), 126-bit entropy
140
- - Customizable alphabet and length
141
- - Not time-ordered
142
- - Best for: URL shorteners, session tokens, invite codes
143
-
144
- **CUID2** — Use when ID unpredictability matters.
145
- - 24 characters (configurable), hash-based
146
- - Non-sequential, prevents enumeration attacks
147
- - Not time-ordered
148
- - Best for: User-facing IDs where security matters
149
-
150
- ### Binary Serialization: When to Use `toBytes()`
151
-
152
- Formats with binary representations (UUID, ULID, KSUID, ObjectID, TSID) support `toBytes()` and `fromBytes()` for efficient storage:
153
-
154
- ```ts
155
- // Store as BINARY(16) instead of VARCHAR(36)
156
- const bytes = uuidv7.toBytes(id) // 16 bytes
157
- db.insert({ id: bytes })
158
-
159
- // Retrieve and convert back
160
- const id = uuidv7.fromBytes(row.id)
161
20
  ```
162
21
 
163
- **Benefits of binary storage:**
164
- - **Up to 50% smaller**: E.g., 16 bytes vs 36 characters for UUID
165
- - **Faster indexing**: Binary comparison is faster than string comparison
166
- - **Reduced I/O**: Less data transferred between app and database
167
-
168
- **When to use binary:**
169
- - High-volume tables (millions of rows)
170
- - Performance-critical queries on ID columns
171
- - Storage cost is a concern
172
-
173
- **Exception — `tsid`:** `toBytes()`/`fromBytes()` operate on `bigint`, not `string`, since `bigint` is TSID's primary type (see [`tsid`](#tsid-from-unikutsid) in the API Reference below). Every other generator's `toBytes()`/`fromBytes()` operate on its primary `string` type.
174
-
175
- **When to keep strings:**
176
- - Debugging/logging convenience
177
- - API responses (humans read them)
178
- - Small tables where savings are negligible
22
+ ## Why `uniku`?
179
23
 
180
- | Format | String Length | Binary Size | Savings |
181
- |--------|--------------|-------------|---------|
182
- | UUID v4/v7 | 36 chars | 16 bytes | 56% |
183
- | ULID | 26 chars | 16 bytes | 38% |
184
- | TypeID | prefix + 27 chars | 16 bytes | prefix-dependent |
185
- | KSUID | 27 chars | 20 bytes | 26% |
186
- | ObjectID | 24 chars | 12 bytes | 50% |
187
- | TSID | 13 chars* | 8 bytes | 38% |
188
- | Nanoid | 21 chars | N/A | - |
189
- | CUID2 | 24 chars | N/A | - |
24
+ - **One focused package** — UUID v4/v7, ULID, TypeID, CUID v2, Nanoid, KSUID, ObjectID, XID, and TSID.
25
+ - **Portable by default** — Runs on Node.js, Bun, Deno, browsers, edge runtimes, and Cloudflare Workers using `globalThis.crypto`.
26
+ - **Tree-shakeable entry points** — Import only the generator you use; the package root is intentionally not exported.
27
+ - **Useful at system boundaries** — Validate unknown input, convert canonical binary formats to bytes, or write directly into a caller-owned buffer.
28
+ - **Small dependency surface** — CUID v2 is the only generator with a runtime dependency (`@noble/hashes`).
190
29
 
191
- > **Note**: Nanoid and CUID2 don't have binary representations because they're string-native formats with no canonical byte encoding.
192
- >
193
- > \* TSID's primary type is `bigint`, not a string; the 13-character figure is its canonical `toString()` form, shown here only for a size comparison against the other formats' native strings.
30
+ ## Install
194
31
 
195
- ## Installation
196
-
197
- ```bash
198
- # pnpm (recommended)
199
- pnpm add uniku
200
-
201
- # bun
202
- bun add uniku
203
-
204
- # npm
32
+ ```sh
205
33
  npm install uniku
206
-
207
- # yarn
208
- yarn add uniku
209
-
210
- # deno
211
- deno install npm:uniku
212
34
  ```
213
35
 
214
- ### Bundle Impact
215
-
216
- | Import | Minified + gzipped |
217
- |--------|-------------------:|
218
- | `uniku/uuid/v4` | ~1.1 KB |
219
- | `uniku/uuid/v7` | ~1.4 KB |
220
- | `uniku/ulid` | ~1.8 KB |
221
- | `uniku/typeid` | ~1.6 KB |
222
- | `uniku/cuid/v2` | ~992 B* |
223
- | `uniku/cuid2` | ~1007 B* |
224
- | `uniku/nanoid` | ~1.1 KB |
225
- | `uniku/ksuid` | ~1.3 KB |
226
- | `uniku/objectid` | ~1.3 KB |
227
- | `uniku/tsid` | ~1.4 KB |
228
- | `uniku/xid` | ~1.7 KB |
229
- | `uniku/generators` | ~98 B |
230
-
231
- * The CUID2 entry point imports SHA3-512 from `@noble/hashes`; this table's entry-point size excludes that external dependency.
232
-
233
- ## Quick Start
234
-
235
- ### UUID v4 (random)
36
+ The [getting-started guide](https://jkomyno.github.io/uniku/docs/getting-started/) also covers pnpm, Bun, and Deno.
236
37
 
237
- ```ts
238
- import { uuidv4 } from 'uniku/uuid/v4'
239
-
240
- const id = uuidv4()
241
- // => "550e8400-e29b-41d4-a716-446655440000"
242
-
243
- // Convert to/from bytes
244
- const bytes = uuidv4.toBytes(id)
245
- const str = uuidv4.fromBytes(bytes)
246
- ```
38
+ ## Quick start
247
39
 
248
- ### UUID v7 (time-ordered)
40
+ Every generator has its own entry point and keeps related operations on the generator function:
249
41
 
250
42
  ```ts
251
43
  import { uuidv7 } from 'uniku/uuid/v7'
252
44
 
253
45
  const id = uuidv7()
254
- // => "018e5e5c-7c8a-7000-8000-000000000000"
46
+ const bytes = uuidv7.toBytes(id)
47
+ const restored = uuidv7.fromBytes(bytes)
255
48
 
256
- // IDs are lexicographically sortable by creation time
257
- const ids = [uuidv7(), uuidv7(), uuidv7()]
258
- ids.sort() // Already in creation order
49
+ if (uuidv7.isValid(restored)) {
50
+ console.log(uuidv7.timestamp(restored))
51
+ }
259
52
  ```
260
53
 
261
- ### ULID (time-ordered)
54
+ There are no barrel exports:
262
55
 
263
56
  ```ts
57
+ import { uuidv4 } from 'uniku/uuid/v4'
264
58
  import { ulid } from 'uniku/ulid'
265
-
266
- const id = ulid()
267
- // => "01HW9T2W9W9YJ3JZ1H4P4M2T8Q"
268
-
269
- // Convert to/from bytes
270
- const bytes = ulid.toBytes(id)
271
- const str = ulid.fromBytes(bytes)
272
- ```
273
-
274
- ### TypeID (prefixed UUID v7)
275
-
276
- ```ts
277
59
  import { typeid } from 'uniku/typeid'
278
-
279
- const id = typeid('user')
280
- // => "user_01h2xcejqtf2nbrexx3vqjhp41"
281
-
282
- const uuid = typeid.toUuid(id)
283
- const restored = typeid.fromUuid('user', uuid)
284
-
285
- const canonical = typeid('')
286
- // => "01h2xcejqtf2nbrexx3vqjhp41"
287
- ```
288
-
289
- ### CUID2 (secure, non-time-ordered)
290
-
291
- ```ts
292
60
  import { cuidv2 } from 'uniku/cuid/v2'
293
-
294
- const id = cuidv2()
295
- // => "pfh0haxfpzowht3oi213cqos"
296
-
297
- // Custom length
298
- const shortId = cuidv2({ length: 10 })
299
- // => "tz4a98xxat"
300
-
301
- // Validation (type guard)
302
- if (cuidv2.isValid(maybeId)) {
303
- console.log(maybeId) // TypeScript knows this is a string
304
- }
305
- ```
306
-
307
- > `uniku/cuid/v2` is the canonical entry point. The older `uniku/cuid2` import
308
- > still works and re-exports the same generator, but is now deprecated — prefer
309
- > `import { cuidv2 } from 'uniku/cuid/v2'`.
310
-
311
- ### Nanoid (URL-friendly, custom alphabet)
312
-
313
- ```ts
314
61
  import { nanoid } from 'uniku/nanoid'
315
-
316
- const id = nanoid()
317
- // => "V1StGXR8_Z5jdHi6B-myT"
318
-
319
- // Custom size
320
- const shortId = nanoid(10)
321
- // => "IRFa-VaY2b"
322
-
323
- // Custom alphabet and size via options
324
- const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })
325
- // => "4f90d13a42bc"
326
- ```
327
-
328
- ### KSUID (time-ordered, high entropy)
329
-
330
- ```ts
331
62
  import { ksuid } from 'uniku/ksuid'
332
-
333
- const id = ksuid()
334
- // => "2QnJjKLvpSfpZqGiPPxVwWLMy2p"
335
-
336
- // Convert to/from bytes
337
- const bytes = ksuid.toBytes(id)
338
- const str = ksuid.fromBytes(bytes)
339
-
340
- // Extract timestamp (milliseconds)
341
- const ts = ksuid.timestamp(id)
342
- ```
343
-
344
- ### ObjectID (time-ordered, MongoDB-compatible)
345
-
346
- ```ts
347
63
  import { objectid } from 'uniku/objectid'
348
-
349
- const id = objectid()
350
- // => "667c3f2a1e2b3c4d5e6f7081"
351
-
352
- // Convert to/from bytes
353
- const bytes = objectid.toBytes(id)
354
- const str = objectid.fromBytes(bytes)
355
-
356
- // Extract timestamp (milliseconds)
357
- const ts = objectid.timestamp(id)
358
- ```
359
-
360
- ### TSID (time-ordered, 64-bit integer)
361
-
362
- ```ts
363
- import { tsid } from 'uniku/tsid'
364
-
365
- // Generate a TSID bigint (the primary type, not a string)
366
- const id = tsid()
367
- // => 862301223059968074n
368
-
369
- // Convert to/from the canonical 13-character string
370
- const str = tsid.toString(id)
371
- // => "0QXW2CK4XZM2A"
372
- tsid.fromString(str) === id // true
373
-
374
- // Extract timestamp (milliseconds, full precision)
375
- const ts = tsid.timestamp(id)
376
-
377
- // Convert to/from 8 bytes
378
- const bytes = tsid.toBytes(id)
379
- const restored = tsid.fromBytes(bytes)
380
- ```
381
-
382
- ### XID (time-ordered, rs/xid-compatible)
383
-
384
- ```ts
385
64
  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
-
397
- ## Migrating to uniku
398
-
399
- ### From `uuid`
400
-
401
- ```diff
402
- - import { v4 as uuidv4 } from 'uuid'
403
- + import { uuidv4 } from 'uniku/uuid/v4'
404
- ```
405
-
406
- ### From `nanoid`
407
-
408
- ```diff
409
- - import { nanoid } from 'nanoid'
410
- + import { nanoid } from 'uniku/nanoid'
411
- ```
412
-
413
- ### From `ulid`
414
-
415
- ```diff
416
- - import { ulid } from 'ulid'
417
- + import { ulid } from 'uniku/ulid'
418
- ```
419
-
420
- ### From `@paralleldrive/cuid2`
421
-
422
- ```diff
423
- - import { createId } from '@paralleldrive/cuid2'
424
- + import { cuid2 } from 'uniku/cuid2'
425
- ```
426
-
427
- ### From `@owpz/ksuid`
428
-
429
- ```diff
430
- - import { KSUID } from '@owpz/ksuid'
431
- + import { ksuid } from 'uniku/ksuid'
432
-
433
- - const id = KSUID.random().toString()
434
- + const id = ksuid()
435
-
436
- - const parsed = KSUID.parse(str)
437
- - const timestamp = parsed.timestamp
438
- + const timestamp = ksuid.timestamp(str)
439
- ```
440
-
441
- ### From `bson`
442
-
443
- ```diff
444
- - import { ObjectId } from 'bson'
445
- + import { objectid } from 'uniku/objectid'
446
-
447
- - const id = new ObjectId().toHexString()
448
- + const id = objectid()
449
-
450
- - const timestamp = new ObjectId(str).getTimestamp().getTime()
451
- + const timestamp = objectid.timestamp(str)
452
- ```
453
-
454
- ## API Reference
455
-
456
- ### `uuidv4` (from `uniku/uuid/v4`)
457
-
458
- ```ts
459
- uuidv4(options?: UuidV4Options): string
460
- uuidv4(options: UuidV4Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
461
- uuidv4.toBytes(id: string): Uint8Array
462
- uuidv4.fromBytes(bytes: Uint8Array): string
463
- uuidv4.isValid(id: unknown): id is string
464
- uuidv4.NIL // "00000000-0000-0000-0000-000000000000"
465
- uuidv4.MAX // "ffffffff-ffff-ffff-ffff-ffffffffffff"
466
- ```
467
-
468
- ### `uuidv7` (from `uniku/uuid/v7`)
469
-
470
- ```ts
471
- uuidv7(options?: UuidV7Options): string
472
- uuidv7(options: UuidV7Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
473
- uuidv7.toBytes(id: string): Uint8Array
474
- uuidv7.fromBytes(bytes: Uint8Array): string
475
- uuidv7.timestamp(id: string): number
476
- uuidv7.isValid(id: unknown): id is string
477
- uuidv7.NIL // "00000000-0000-0000-0000-000000000000"
478
- uuidv7.MAX // "ffffffff-ffff-ffff-ffff-ffffffffffff"
479
- ```
480
-
481
- ### `ulid` (from `uniku/ulid`)
482
-
483
- ```ts
484
- ulid(options?: UlidOptions): string
485
- ulid(options: UlidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
486
- ulid.toBytes(id: string): Uint8Array
487
- ulid.fromBytes(bytes: Uint8Array): string
488
- ulid.timestamp(id: string): number
489
- ulid.isValid(id: unknown): id is string
490
- ulid.NIL // "00000000000000000000000000"
491
- ulid.MAX // "7ZZZZZZZZZZZZZZZZZZZZZZZZZ"
492
- ```
493
-
494
- ### `typeid` (from `uniku/typeid`)
495
-
496
- ```ts
497
- typeid(prefix: string, options?: TypeidOptions): string
498
- typeid.toBytes(id: string): Uint8Array
499
- typeid.fromBytes(prefix: string, bytes: Uint8Array): string
500
- typeid.toUuid(id: string): string
501
- typeid.fromUuid(prefix: string, uuid: string): string
502
- typeid.timestamp(id: string): number
503
- typeid.prefix(id: string): string
504
- typeid.suffix(id: string): string
505
- typeid.isValid(id: unknown): id is string
506
- ```
507
-
508
- Use an empty prefix (`typeid('')`) to generate Jetify-compatible prefixless TypeIDs.
509
-
510
- ### `cuidv2` (from `uniku/cuid/v2`)
511
-
512
- ```ts
513
- cuidv2(options?: CuidV2Options): string
514
- cuidv2.isValid(id: unknown): id is string
515
- ```
516
-
517
- > Also available (deprecated) as `cuid2` from `uniku/cuid2` — the same generator
518
- > under the pre-versioned entry point.
519
-
520
- ### `nanoid` (from `uniku/nanoid`)
521
-
522
- ```ts
523
- nanoid(): string
524
- nanoid(size: number): string
525
- nanoid(options: NanoidOptions): string
526
- nanoid.isValid(id: unknown): id is string
527
-
528
- // Constant
529
- URL_ALPHABET // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
530
- ```
531
-
532
- ### `ksuid` (from `uniku/ksuid`)
533
-
534
- ```ts
535
- ksuid(options?: KsuidOptions): string
536
- ksuid(options: KsuidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
537
- ksuid.toBytes(id: string): Uint8Array
538
- ksuid.fromBytes(bytes: Uint8Array): string
539
- ksuid.timestamp(id: string): number
540
- ksuid.isValid(id: unknown): id is string
541
- ksuid.NIL // "000000000000000000000000000"
542
- ksuid.MAX // "aWgEPTl1tmebfsQzFP4bxwgy80V"
543
- ```
544
-
545
- ### `objectid` (from `uniku/objectid`)
546
-
547
- ```ts
548
- objectid(options?: ObjectIdOptions): string
549
- objectid(options: ObjectIdOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
550
- objectid.toBytes(id: string): Uint8Array
551
- objectid.fromBytes(bytes: Uint8Array): string
552
- objectid.timestamp(id: string): number
553
- objectid.isValid(id: unknown): id is string
554
- objectid.NIL // "000000000000000000000000"
555
- objectid.MAX // "ffffffffffffffffffffffff"
65
+ import { tsid } from 'uniku/tsid'
556
66
  ```
557
67
 
558
- ### `tsid` (from `uniku/tsid`)
68
+ ## Choose an ID
559
69
 
560
- ```ts
561
- tsid(options?: TsidOptions): bigint
562
- tsid(options: TsidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
563
- tsid.toBytes(id: bigint): Uint8Array
564
- tsid.fromBytes(bytes: Uint8Array): bigint
565
- tsid.toString(id: bigint): string
566
- tsid.fromString(str: string): bigint
567
- tsid.timestamp(id: bigint, epoch?: number): number
568
- tsid.isValid(id: unknown): id is bigint
569
- tsid.NIL // 0n
570
- tsid.MAX // 18446744073709551615n
571
- ```
70
+ | Situation | Start with | Why |
71
+ | --- | --- | --- |
72
+ | Database primary keys | UUID v7 or ULID | Time-ordered values improve index locality |
73
+ | Public API resources | TypeID | UUID v7 with a readable, domain-specific prefix |
74
+ | Short URLs and invite codes | Nanoid | Compact and URL-safe |
75
+ | Values that should resist enumeration | CUID v2 | Non-sequential and secure |
76
+ | MongoDB `_id` compatibility | ObjectID | Matches MongoDB's 12-byte ObjectID format |
77
+ | Go rs/xid compatibility | XID | Matches rs/xid's text and binary representation |
78
+ | Native `BIGINT` storage | TSID | A sortable 64-bit integer rather than a UUID-shaped string |
79
+ | Broad standards compatibility | UUID v4 | The conventional random UUID format |
572
80
 
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.
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
- ```
81
+ The [choosing guide](https://jkomyno.github.io/uniku/docs/guides/choosing-an-id/) covers ordering, timestamp leakage, storage boundaries, and format-specific trade-offs.
587
82
 
588
83
  ## Documentation
589
84
 
590
- For advanced usage, examples, and contributing guidelines, see the [full documentation on GitHub](https://github.com/jkomyno/uniku).
591
- The [`uniku@1.x` stability contract](../../docs/STABILITY.md) defines the supported entry points, runtimes, and release gates.
85
+ - [Getting started](https://jkomyno.github.io/uniku/docs/getting-started/) — installation, direct imports, validation, and byte helpers
86
+ - [Generator reference](https://jkomyno.github.io/uniku/docs/reference/entry-points/) — generated from the public TypeScript signatures and JSDoc
87
+ - [Migration guide](https://jkomyno.github.io/uniku/docs/migration/from-other-libraries/) — move from `uuid`, `nanoid`, `ulid`, CUID2, KSUID, BSON, TSID, or XID libraries
88
+ - [Performance and bundle size](https://jkomyno.github.io/uniku/docs/guides/performance/) — benchmark methodology, current measurements, and reproduction commands
89
+ - [Stability contract](https://github.com/jkomyno/uniku/blob/main/docs/STABILITY.md) — the entry points and compatibility policy planned for `uniku@1.0.0`
592
90
 
593
- ## Related Projects
91
+ ## CLI companion
594
92
 
595
- Third-party libraries that inspired this project:
93
+ `@uniku/cli` generates, validates, and inspects IDs from a terminal or shell pipeline. Install the current standalone binary:
596
94
 
597
- - [uuid](https://github.com/uuidjs/uuid): the most popular UUID library for JavaScript
598
- - [ulid](https://github.com/ulid/javascript): the reference ULID implementation for JavaScript
599
- - [TypeID-JS](https://github.com/jetify-com/typeid-js): official JavaScript implementation of TypeID
600
- - [@paralleldrive/cuid2](https://github.com/paralleldrive/cuid2): secure, collision-resistant IDs
601
- - [@owpz/ksuid](https://github.com/owpz/ksuid): K-Sortable Unique Identifier
602
- - [bson](https://github.com/mongodb/js-bson): official MongoDB BSON library, including the ObjectId implementation
603
- - [nanoid](https://github.com/ai/nanoid): tiny, URL-friendly unique string ID generator
604
- - [tsid-ts](https://github.com/yubinTW/tsid-ts): TypeScript implementation of TSID, a Snowflake-style 64-bit Time-Sorted Unique Identifier
95
+ ```sh
96
+ curl -fsSL https://raw.githubusercontent.com/jkomyno/uniku/main/install.sh | sh
605
97
 
606
- Other:
607
-
608
- - [pnpm-monorepo-template](https://github.com/jkomyno/pnpm-monorepo-template): the template I used to create this library
98
+ uniku uuid -v 7
99
+ uniku validate 018e5e5c-7c8a-7000-8000-000000000000
100
+ uniku inspect 018e5e5c-7c8a-7000-8000-000000000000
101
+ ```
609
102
 
610
- ## Author
103
+ See the [CLI documentation](https://jkomyno.github.io/uniku/docs/cli/) for package-manager installs and the complete command reference.
611
104
 
612
- Hi, I'm Alberto Schiabel, aka @jkomyno. You can follow me on:
105
+ ## Contributing
613
106
 
614
- - Github: [@jkomyno](https://github.com/jkomyno)
615
- - X: [@jkomyno](https://x.com/jkomyno)
107
+ See [CONTRIBUTING.md](https://github.com/jkomyno/uniku/blob/main/CONTRIBUTING.md) for the development workflow, tests, benchmarks, and preview releases.
616
108
 
617
109
  ## License
618
110
 
619
- MIT — see [LICENSE](https://github.com/jkomyno/uniku/blob/main/LICENSE)
111
+ MIT © [Alberto Schiabel](https://github.com/jkomyno)
@@ -1 +1 @@
1
- {"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,KAmCD,aAAA;EAnCZ;;;;AAAa;EAyCX,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"}
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"}
@@ -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_-]+$/;let a,o=0;function s(e){let t=Math.min(e*128,65536);!a||a.length<t?(a=new Uint8Array(t),crypto.getRandomValues(a),o=0):o+e>a.length&&(crypto.getRandomValues(a),o=0),o+=e}function c(e){s(e);let t=``;for(let r=o-e;r<o;r++)t+=n[a[r]&63];return t}function l(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 u(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 d(t){if(t===void 0)return c(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&&l(i)),u(r),r===0)return``;if(i===`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`&&a===void 0)return c(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,d=Math.ceil(1.6*s*r/o),f=``,p=0;for(;f.length<r;){let t;if(a){if(a.length-p<d)throw new e(`NANOID_RANDOM_BYTES_INSUFFICIENT`,`Insufficient random bytes: need at least ${d} more, have ${a.length-p}`);t=a.subarray(p,p+d),p+=d}else t=globalThis.crypto.getRandomValues(new Uint8Array(d));for(let e=0;e<t.length&&f.length<r;e++){let n=t[e]&s;n<o&&(f+=i[n])}}return f}function f(e){return typeof e==`string`&&e.length>0&&i.test(e)}const p=Object.assign(d,{isValid:f});export{e as InvalidInputError,n as URL_ALPHABET,t as UniqueIdError,p as nanoid};
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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"xid.d.mts","names":[],"sources":["../../src/xid/xid.ts"],"mappings":";;;KAaY,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;;;;;;;;cAmHW,GAAA,EAAK,GAAA"}
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"}
package/build/xid/xid.mjs CHANGED
@@ -1,2 +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=new Uint8Array(65536);u.fill(255);for(let e=0;e<32;e+=1)u[l.charCodeAt(e)]=e;function d(e){if(e.length!==12)throw new i(`XID_BYTES_INVALID_LENGTH`,`XID bytes must be exactly 12 bytes, got ${e.length}`);return l[e[0]>>3]+l[(e[0]<<2|e[1]>>6)&31]+l[e[1]>>1&31]+l[(e[1]<<4|e[2]>>4)&31]+l[(e[2]<<1|e[3]>>7)&31]+l[e[3]>>2&31]+l[(e[3]<<3|e[4]>>5)&31]+l[e[4]&31]+l[e[5]>>3]+l[(e[5]<<2|e[6]>>6)&31]+l[e[6]>>1&31]+l[(e[6]<<4|e[7]>>4)&31]+l[(e[7]<<1|e[8]>>7)&31]+l[e[8]>>2&31]+l[(e[8]<<3|e[9]>>5)&31]+l[e[9]&31]+l[e[10]>>3]+l[(e[10]<<2|e[11]>>6)&31]+l[e[11]>>1&31]+l[e[11]<<4&31]}function f(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=u[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 p=4294967295,m=65535,h=16777215,g=/^[0-9a-v]{19}[0g]$/,_={machineId:void 0,processId:void 0,counter:void 0};function v(e){return t(e).slice()}function y(){_.machineId===void 0&&(_.machineId=v(3)),_.processId===void 0&&(_.processId=e()&m)}function b(){return _.counter===void 0&&(_.counter=e()&h),_.counter=_.counter+1&h,_.counter}function x(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 S(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,m))throw new a(`XID_PROCESS_ID_OUT_OF_RANGE`,`Process ID must be between 0 and ${m}`);if(e.secs!==void 0&&!r(e.secs,0,p))throw new a(`XID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be between 0 and ${p}`);if(e.counter!==void 0&&!r(e.counter,0,h))throw new a(`XID_COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${h}`)}function C(e,t,r=0){e!==void 0&&S(e);let a=e?.secs??Math.floor(Date.now()/1e3);(e?.machineId===void 0||e.processId===void 0)&&y();let o=e?.machineId??_.machineId,s=e?.processId??_.processId,c=e?.counter??b();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 x(a,o,s,c,t,r),t}let l=new Uint8Array(12);return x(a,o,s,c,l,0),d(l)}function w(e){return f(e)}function T(e){return d(e)}function E(e){let t=f(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function D(e){return typeof e==`string`&&g.test(e)}const O=Object.assign(C,{toBytes:w,fromBytes:T,timestamp:E,isValid:D,NIL:`0`.repeat(20),MAX:d(new Uint8Array(12).fill(255))});export{i as BufferError,a as InvalidInputError,o as ParseError,s as UniqueIdError,O as xid};
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
2
  //# sourceMappingURL=xid.mjs.map
@@ -1 +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 XID_BYTES = 12\nconst XID_LENGTH = 20\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 return (\n ENCODING[bytes[0] >> 3] +\n ENCODING[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f] +\n ENCODING[(bytes[1] >> 1) & 0x1f] +\n ENCODING[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f] +\n ENCODING[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f] +\n ENCODING[(bytes[3] >> 2) & 0x1f] +\n ENCODING[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f] +\n ENCODING[bytes[4] & 0x1f] +\n ENCODING[bytes[5] >> 3] +\n ENCODING[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f] +\n ENCODING[(bytes[6] >> 1) & 0x1f] +\n ENCODING[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f] +\n ENCODING[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f] +\n ENCODING[(bytes[8] >> 2) & 0x1f] +\n ENCODING[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f] +\n ENCODING[bytes[9] & 0x1f] +\n ENCODING[bytes[10] >> 3] +\n ENCODING[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f] +\n ENCODING[(bytes[11] >> 1) & 0x1f] +\n ENCODING[(bytes[11] << 4) & 0x1f]\n )\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 } 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]$/\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) 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 const bytes = new Uint8Array(XID_BYTES)\n writeXidBytesUnchecked(secs, machineId, processId, counter, bytes, 0)\n return encodeBase32Hex(bytes)\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":"8OAEA,MAAM,EAAW,mCAIX,EAAW,IAAI,WAAW,KAAK,EACrC,EAAS,KAAK,GAAG,EACjB,IAAK,IAAI,EAAI,EAAG,EAAI,GAAiB,GAAK,EACxC,EAAS,EAAS,WAAW,CAAC,GAAK,EAIrC,SAAgB,EAAgB,EAA2B,CACzD,GAAI,EAAM,SAAWA,GACnB,MAAM,IAAI,EACR,2BACA,2CAAqD,EAAM,QAC7D,EAGF,OACE,EAAS,EAAM,IAAM,GACrB,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,EAAU,EAAM,IAAM,EAAK,IAC3B,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,EAAU,EAAM,IAAM,EAAK,IAC3B,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,EAAS,EAAM,GAAK,IACpB,EAAS,EAAM,IAAM,GACrB,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,EAAU,EAAM,IAAM,EAAK,IAC3B,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,EAAU,EAAM,IAAM,EAAK,IAC3B,GAAW,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAC/C,EAAS,EAAM,GAAK,IACpB,EAAS,EAAM,KAAO,GACtB,GAAW,EAAM,KAAO,EAAM,EAAM,KAAO,GAAM,IACjD,EAAU,EAAM,KAAO,EAAK,IAC5B,EAAU,EAAM,KAAO,EAAK,GAEhC,CAGA,SAAgB,EAAgB,EAAwB,CACtD,GAAI,EAAG,SAAW,GAChB,MAAM,IAAI,EAAW,qBAAsB,yCAAoD,EAAG,QAAQ,EAG5G,IAAM,EAAS,IAAI,WAAW,EAAU,EACxC,IAAK,IAAI,EAAI,EAAG,EAAI,GAAY,GAAK,EAAG,CACtC,IAAM,EAAQ,EAAS,EAAG,WAAW,CAAC,GACtC,GAAI,IAAU,IACZ,MAAM,IAAI,EAAW,mBAAoB,0BAA0B,EAAG,IAAI,EAE5E,EAAO,GAAK,CACd,CAEA,GAAI,EAAO,MAAQ,GAAK,EAAO,MAAQ,GACrC,MAAM,IAAI,EAAW,oBAAqB,+CAA+C,EAG3F,OAAO,IAAI,WAAW,CACnB,EAAO,IAAM,EAAM,EAAO,IAAM,EAChC,EAAO,IAAM,EAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EACnD,EAAO,IAAM,EAAM,EAAO,IAAM,EAChC,EAAO,IAAM,EAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EACnD,EAAO,IAAM,EAAK,EAAO,GACzB,EAAO,IAAM,EAAM,EAAO,IAAM,EAChC,EAAO,IAAM,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EACrD,EAAO,KAAO,EAAM,EAAO,KAAO,EAClC,EAAO,KAAO,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EACtD,EAAO,KAAO,EAAK,EAAO,IAC1B,EAAO,KAAO,EAAM,EAAO,KAAO,EAClC,EAAO,KAAO,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,CACzD,CAAC,CACH,CCxEA,MAEM,EAAW,WACX,EAAiB,MACjB,EAAc,SACd,EAAY,qBA+BZ,EAAkB,CAAE,UAAW,IAAA,GAAW,UAAW,IAAA,GAAW,QAAS,IAAA,EAAU,EAGzF,SAAS,EAAY,EAA2B,CAC9C,OAAO,EAAY,CAAK,CAAC,CAAC,MAAM,CAClC,CAEA,SAAS,GAA2B,CAC9B,EAAM,YAAc,IAAA,KAAW,EAAM,UAAY,EAAY,CAAgB,GAC7E,EAAM,YAAc,IAAA,KAAW,EAAM,UAAY,EAAa,EAAI,EACxE,CAEA,SAAS,GAAsB,CAG7B,OAFI,EAAM,UAAY,IAAA,KAAW,EAAM,QAAU,EAAa,EAAI,GAClE,EAAM,QAAW,EAAM,QAAU,EAAK,EAC/B,EAAM,OACf,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACM,CACN,EAAiB,EAAK,EAAQ,CAAI,EAClC,EAAI,IAAI,EAAU,SAAS,EAAG,CAAgB,EAAG,EAAS,CAAC,EAC3D,EAAI,EAAS,GAAK,IAAc,EAChC,EAAI,EAAS,GAAK,EAAY,IAC9B,EAAI,EAAS,GAAK,IAAY,GAC9B,EAAI,EAAS,IAAO,IAAY,EAAK,IACrC,EAAI,EAAS,IAAM,EAAU,GAC/B,CAEA,SAAS,EAAgB,EAA2B,CAClD,GAAI,EAAQ,YAAc,IAAA,IAAa,EAAQ,UAAU,OAAS,EAChE,MAAM,IAAI,EACR,iCACA,8CACF,EAEF,GAAI,EAAQ,YAAc,IAAA,IAAa,CAAC,EAAiB,EAAQ,UAAW,EAAG,CAAc,EAC3F,MAAM,IAAI,EAAkB,8BAA+B,oCAAoC,GAAgB,EAEjH,GAAI,EAAQ,OAAS,IAAA,IAAa,CAAC,EAAiB,EAAQ,KAAM,EAAG,CAAQ,EAC3E,MAAM,IAAI,EAAkB,6BAA8B,mCAAmC,GAAU,EAEzG,GAAI,EAAQ,UAAY,IAAA,IAAa,CAAC,EAAiB,EAAQ,QAAS,EAAG,CAAW,EACpF,MAAM,IAAI,EAAkB,2BAA4B,iCAAiC,GAAa,CAE1G,CAIA,SAAS,EAA4C,EAAsB,EAAY,EAAS,EAAkB,CAC5G,IAAY,IAAA,IAAW,EAAgB,CAAO,EAElD,IAAM,EAAO,GAAS,MAAQ,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,GACtD,GAAS,YAAc,IAAA,IAAa,EAAQ,YAAc,IAAA,KAC5D,EAAmB,EAErB,IAAM,EAAY,GAAS,WAAa,EAAM,UACxC,EAAY,GAAS,WAAa,EAAM,UACxC,EAAU,GAAS,SAAW,EAAY,EAEhD,GAAI,IAAQ,IAAA,GAAW,CACrB,GAAI,CAAC,EAAgB,EAAK,EAAQ,EAAS,EACzC,MAAM,IAAI,EACR,2BACA,kBAAkB,EAAO,GAAG,EAAS,GAAG,yBAC1C,EAGF,OADA,EAAuB,EAAM,EAAW,EAAW,EAAS,EAAK,CAAM,EAChE,CACT,CAEA,IAAM,EAAQ,IAAI,WAAW,EAAS,EAEtC,OADA,EAAuB,EAAM,EAAW,EAAW,EAAS,EAAO,CAAC,EAC7D,EAAgB,CAAK,CAC9B,CAEA,SAAS,EAAQ,EAAwB,CACvC,OAAO,EAAgB,CAAE,CAC3B,CAEA,SAAS,EAAU,EAA2B,CAC5C,OAAO,EAAgB,CAAK,CAC9B,CAEA,SAAS,EAAU,EAAoB,CACrC,IAAM,EAAQ,EAAgB,CAAE,EAChC,QAAU,EAAM,IAAM,GAAO,EAAM,IAAM,GAAO,EAAM,IAAM,EAAK,EAAM,MAAQ,GAAK,GACtF,CAEA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAU,KAAK,CAAE,CACpD,CASA,MAAa,EAAW,OAAO,OAAO,EAAO,CAC3C,UACA,YACA,YACA,UACA,IAAK,IAAI,OAAO,EAAE,EAClB,IAAK,EAAgB,IAAI,WAAW,EAAE,CAAA,CAAE,KAAK,GAAI,CAAC,CACpD,CAAC"}
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":"8OAEA,MAAM,EAAW,mCACX,EAAiB,WAAW,KAAK,EAAW,GAAc,EAAU,WAAW,CAAC,CAAC,EAGjF,EAAc,MAAc,EAAU,EACtC,EAAiB,OAAmB,EAEpC,EAAW,IAAI,WAAW,KAAK,EACrC,EAAS,KAAK,GAAG,EACjB,IAAK,IAAI,EAAI,EAAG,EAAI,GAAiB,GAAK,EACxC,EAAS,EAAS,WAAW,CAAC,GAAK,EAIrC,SAAgB,EAAgB,EAA2B,CACzD,GAAI,EAAM,SAAWA,GACnB,MAAM,IAAI,EACR,2BACA,2CAAqD,EAAM,QAC7D,EAuBF,MApBA,GAAQ,GAAK,EAAe,EAAM,IAAM,GACxC,EAAQ,GAAK,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAClE,EAAQ,GAAK,EAAgB,EAAM,IAAM,EAAK,IAC9C,EAAQ,GAAK,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAClE,EAAQ,GAAK,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAClE,EAAQ,GAAK,EAAgB,EAAM,IAAM,EAAK,IAC9C,EAAQ,GAAK,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAClE,EAAQ,GAAK,EAAe,EAAM,GAAK,IACvC,EAAQ,GAAK,EAAe,EAAM,IAAM,GACxC,EAAQ,GAAK,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IAClE,EAAQ,IAAM,EAAgB,EAAM,IAAM,EAAK,IAC/C,EAAQ,IAAM,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IACnE,EAAQ,IAAM,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IACnE,EAAQ,IAAM,EAAgB,EAAM,IAAM,EAAK,IAC/C,EAAQ,IAAM,GAAiB,EAAM,IAAM,EAAM,EAAM,IAAM,GAAM,IACnE,EAAQ,IAAM,EAAe,EAAM,GAAK,IACxC,EAAQ,IAAM,EAAe,EAAM,KAAO,GAC1C,EAAQ,IAAM,GAAiB,EAAM,KAAO,EAAM,EAAM,KAAO,GAAM,IACrE,EAAQ,IAAM,EAAgB,EAAM,KAAO,EAAK,IAChD,EAAQ,IAAM,EAAgB,EAAM,KAAO,EAAK,IACzC,OAAO,aAAa,GAAG,CAAO,CACvC,CAGA,SAAgB,EAAoB,EAA0B,EAAyB,CACrF,IAAM,EAAO,IAAY,GACnB,EAAU,IAAY,EAAK,IAC3B,EAAM,EAAU,IAOtB,MANA,GAAe,GAAK,GAAiB,GAAoB,EAAM,GAAQ,GAAM,IAC7E,EAAe,GAAK,EAAe,EAAO,IAC1C,EAAe,GAAK,EAAe,GAAU,GAC7C,EAAe,GAAK,GAAiB,GAAU,EAAM,GAAO,GAAM,IAClE,EAAe,GAAK,EAAgB,GAAO,EAAK,IAChD,EAAe,GAAK,EAAgB,GAAO,EAAK,IACzC,OAAO,aAAa,GAAG,CAAc,CAC9C,CAGA,SAAgB,EAAgB,EAAwB,CACtD,GAAI,EAAG,SAAW,GAChB,MAAM,IAAI,EAAW,qBAAsB,yCAAoD,EAAG,QAAQ,EAG5G,IAAM,EAAS,IAAI,WAAW,EAAU,EACxC,IAAK,IAAI,EAAI,EAAG,EAAI,GAAY,GAAK,EAAG,CACtC,IAAM,EAAQ,EAAS,EAAG,WAAW,CAAC,GACtC,GAAI,IAAU,IACZ,MAAM,IAAI,EAAW,mBAAoB,0BAA0B,EAAG,IAAI,EAE5E,EAAO,GAAK,CACd,CAEA,GAAI,EAAO,MAAQ,GAAK,EAAO,MAAQ,GACrC,MAAM,IAAI,EAAW,oBAAqB,+CAA+C,EAG3F,OAAO,IAAI,WAAW,CACnB,EAAO,IAAM,EAAM,EAAO,IAAM,EAChC,EAAO,IAAM,EAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EACnD,EAAO,IAAM,EAAM,EAAO,IAAM,EAChC,EAAO,IAAM,EAAM,EAAO,IAAM,EAAM,EAAO,IAAM,EACnD,EAAO,IAAM,EAAK,EAAO,GACzB,EAAO,IAAM,EAAM,EAAO,IAAM,EAChC,EAAO,IAAM,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EACrD,EAAO,KAAO,EAAM,EAAO,KAAO,EAClC,EAAO,KAAO,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,EACtD,EAAO,KAAO,EAAK,EAAO,IAC1B,EAAO,KAAO,EAAM,EAAO,KAAO,EAClC,EAAO,KAAO,EAAM,EAAO,KAAO,EAAM,EAAO,KAAO,CACzD,CAAC,CACH,CCxFA,MAEM,EAAW,WACX,EAAiB,MACjB,EAAc,SACd,EAAY,qBACZ,EAAe,IAAI,WAAW,EAAS,EAC7C,IAAI,EAAmB,GACnB,EAAe,GA+BnB,MAAM,EAAkB,CAAE,UAAW,IAAA,GAAW,UAAW,IAAA,GAAW,QAAS,IAAA,EAAU,EAGzF,SAAS,EAAY,EAA2B,CAC9C,OAAO,EAAY,CAAK,CAAC,CAAC,MAAM,CAClC,CAEA,SAAS,GAA2B,CAC9B,EAAM,YAAc,IAAA,KAAW,EAAM,UAAY,EAAY,CAAgB,GAC7E,EAAM,YAAc,IAAA,KAAW,EAAM,UAAY,EAAa,EAAI,EACxE,CAEA,SAAS,GAAsB,CAG7B,OAFI,EAAM,UAAY,IAAA,KAAW,EAAM,QAAU,EAAa,EAAI,GAClE,EAAM,QAAW,EAAM,QAAU,EAAK,EAC/B,EAAM,OACf,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACM,CACN,EAAiB,EAAK,EAAQ,CAAI,EAClC,EAAI,IAAI,EAAU,SAAS,EAAG,CAAgB,EAAG,EAAS,CAAC,EAC3D,EAAI,EAAS,GAAK,IAAc,EAChC,EAAI,EAAS,GAAK,EAAY,IAC9B,EAAI,EAAS,GAAK,IAAY,GAC9B,EAAI,EAAS,IAAO,IAAY,EAAK,IACrC,EAAI,EAAS,IAAM,EAAU,GAC/B,CAEA,SAAS,EAAgB,EAA2B,CAClD,GAAI,EAAQ,YAAc,IAAA,IAAa,EAAQ,UAAU,OAAS,EAChE,MAAM,IAAI,EACR,iCACA,8CACF,EAEF,GAAI,EAAQ,YAAc,IAAA,IAAa,CAAC,EAAiB,EAAQ,UAAW,EAAG,CAAc,EAC3F,MAAM,IAAI,EAAkB,8BAA+B,oCAAoC,GAAgB,EAEjH,GAAI,EAAQ,OAAS,IAAA,IAAa,CAAC,EAAiB,EAAQ,KAAM,EAAG,CAAQ,EAC3E,MAAM,IAAI,EAAkB,6BAA8B,mCAAmC,GAAU,EAEzG,GAAI,EAAQ,UAAY,IAAA,IAAa,CAAC,EAAiB,EAAQ,QAAS,EAAG,CAAW,EACpF,MAAM,IAAI,EAAkB,2BAA4B,iCAAiC,GAAa,CAE1G,CAIA,SAAS,EAA4C,EAAsB,EAAY,EAAS,EAAkB,CAChH,GAAI,IAAY,IAAA,IAAa,IAAQ,IAAA,GAAW,CAC9C,EAAmB,EACnB,IAAM,EAAO,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EACnC,EAAU,EAAY,EAM5B,OALI,IAAS,IACX,EAAuB,EAAM,EAAM,UAAY,EAAM,UAAY,EAAS,EAAc,CAAC,EACzF,EAAe,EAAgB,CAAY,CAAC,CAAC,MAAM,EAAG,EAAE,EACxD,EAAmB,GAEd,EAAe,EAAoB,EAAM,UAAa,IAAM,CAAO,CAC5E,CAEI,IAAY,IAAA,IAAW,EAAgB,CAAO,EAElD,IAAM,EAAO,GAAS,MAAQ,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,GACtD,GAAS,YAAc,IAAA,IAAa,EAAQ,YAAc,IAAA,KAC5D,EAAmB,EAErB,IAAM,EAAY,GAAS,WAAa,EAAM,UACxC,EAAY,GAAS,WAAa,EAAM,UACxC,EAAU,GAAS,SAAW,EAAY,EAEhD,GAAI,IAAQ,IAAA,GAAW,CACrB,GAAI,CAAC,EAAgB,EAAK,EAAQ,EAAS,EACzC,MAAM,IAAI,EACR,2BACA,kBAAkB,EAAO,GAAG,EAAS,GAAG,yBAC1C,EAGF,OADA,EAAuB,EAAM,EAAW,EAAW,EAAS,EAAK,CAAM,EAChE,CACT,CAGA,OADA,EAAuB,EAAM,EAAW,EAAW,EAAS,EAAc,CAAC,EACpE,EAAgB,CAAY,CACrC,CAEA,SAAS,EAAQ,EAAwB,CACvC,OAAO,EAAgB,CAAE,CAC3B,CAEA,SAAS,EAAU,EAA2B,CAC5C,OAAO,EAAgB,CAAK,CAC9B,CAEA,SAAS,EAAU,EAAoB,CACrC,IAAM,EAAQ,EAAgB,CAAE,EAChC,QAAU,EAAM,IAAM,GAAO,EAAM,IAAM,GAAO,EAAM,IAAM,EAAK,EAAM,MAAQ,GAAK,GACtF,CAEA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAU,KAAK,CAAE,CACpD,CASA,MAAa,EAAW,OAAO,OAAO,EAAO,CAC3C,UACA,YACA,YACA,UACA,IAAK,IAAI,OAAO,EAAE,EAClB,IAAK,EAAgB,IAAI,WAAW,EAAE,CAAA,CAAE,KAAK,GAAI,CAAC,CACpD,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "uniku",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime",
6
6
  "author": {
7
7
  "name": "Alberto Schiabel",
@@ -14,7 +14,7 @@
14
14
  "url": "git+https://github.com/jkomyno/uniku.git",
15
15
  "directory": "packages/uniku"
16
16
  },
17
- "homepage": "https://github.com/jkomyno/uniku#readme",
17
+ "homepage": "https://jkomyno.github.io/uniku/",
18
18
  "bugs": {
19
19
  "url": "https://github.com/jkomyno/uniku/issues"
20
20
  },
@@ -161,7 +161,8 @@
161
161
  "smoke:runtime:bun": "bun scripts/runtime-smoke.mjs",
162
162
  "smoke:runtime:deno": "deno run --node-modules-dir=manual scripts/runtime-smoke.mjs",
163
163
  "bench": "vitest bench --run --project unit",
164
- "bench:compat": "vitest bench ./__tests__/bench/compat.bench.ts --run --project unit",
164
+ "bench:compat": "bun scripts/bench-compat.ts",
165
+ "bench:cold-start": "node scripts/bench-cold-start.mjs",
165
166
  "bench:summary": "bun scripts/bench-summary.ts",
166
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.'",
167
168
  "bundle:summary": "bun scripts/bundle-summary.ts",
@@ -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. It consumes `size`
11
- // bytes directly without the shared helper's UUID/CUID2-oriented branches.
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
- let pool: Uint8Array | undefined
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 (!pool || pool.length < size) {
20
- pool = new Uint8Array(size)
21
- crypto.getRandomValues(pool)
22
- poolOffset = 0
23
- } else if (poolOffset + bytes > pool.length) {
24
- crypto.getRandomValues(pool)
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
- let id = ''
33
- for (let i = poolOffset - size; i < poolOffset; i++) {
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
 
@@ -1,8 +1,11 @@
1
1
  import { BufferError, ParseError } from '../errors'
2
2
 
3
3
  const ENCODING = '0123456789abcdefghijklmnopqrstuv'
4
+ const ENCODING_CODES = Uint8Array.from(ENCODING, (character) => character.charCodeAt(0))
4
5
  const XID_BYTES = 12
5
6
  const XID_LENGTH = 20
7
+ const ENCODED = new Array<number>(XID_LENGTH)
8
+ const COUNTER_SUFFIX = new Array<number>(6)
6
9
 
7
10
  const DECODING = new Uint8Array(65536)
8
11
  DECODING.fill(255)
@@ -19,28 +22,41 @@ export function encodeBase32Hex(bytes: Uint8Array): string {
19
22
  )
20
23
  }
21
24
 
22
- return (
23
- ENCODING[bytes[0] >> 3] +
24
- ENCODING[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f] +
25
- ENCODING[(bytes[1] >> 1) & 0x1f] +
26
- ENCODING[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f] +
27
- ENCODING[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f] +
28
- ENCODING[(bytes[3] >> 2) & 0x1f] +
29
- ENCODING[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f] +
30
- ENCODING[bytes[4] & 0x1f] +
31
- ENCODING[bytes[5] >> 3] +
32
- ENCODING[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f] +
33
- ENCODING[(bytes[6] >> 1) & 0x1f] +
34
- ENCODING[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f] +
35
- ENCODING[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f] +
36
- ENCODING[(bytes[8] >> 2) & 0x1f] +
37
- ENCODING[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f] +
38
- ENCODING[bytes[9] & 0x1f] +
39
- ENCODING[bytes[10] >> 3] +
40
- ENCODING[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f] +
41
- ENCODING[(bytes[11] >> 1) & 0x1f] +
42
- ENCODING[(bytes[11] << 4) & 0x1f]
43
- )
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)
44
60
  }
45
61
 
46
62
  /** Decode a canonical lowercase base32hex XID string to its 12 bytes. */
package/src/xid/xid.ts CHANGED
@@ -2,7 +2,7 @@ import { writeTimestamp32 } from '../common/bytes'
2
2
  import { randomBytes, randomUint32 } from '../common/random'
3
3
  import { isIntegerInRange, isWritableRange } from '../common/validation'
4
4
  import { BufferError, InvalidInputError } from '../errors'
5
- import { decodeBase32Hex, encodeBase32Hex } from './base32hex'
5
+ import { decodeBase32Hex, encodeBase32Hex, encodeCounterSuffix } from './base32hex'
6
6
 
7
7
  const XID_BYTES = 12
8
8
  const MACHINE_ID_BYTES = 3
@@ -10,6 +10,9 @@ const MAX_SECS = 0xffffffff
10
10
  const MAX_PROCESS_ID = 0xffff
11
11
  const MAX_COUNTER = 0xffffff
12
12
  const XID_REGEX = /^[0-9a-v]{19}[0g]$/
13
+ const stringBuffer = new Uint8Array(XID_BYTES)
14
+ let cachedPrefixSecs = -1
15
+ let cachedPrefix = ''
13
16
 
14
17
  export type XidOptions = {
15
18
  /** First three bytes used as the XID machine identity. */
@@ -96,6 +99,18 @@ function validateOptions(options: XidOptions): void {
96
99
  function xidFn(options?: XidOptions, buf?: undefined, offset?: number): string
97
100
  function xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf
98
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
+
99
114
  if (options !== undefined) validateOptions(options)
100
115
 
101
116
  const secs = options?.secs ?? Math.floor(Date.now() / 1000)
@@ -117,9 +132,8 @@ function xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?:
117
132
  return buf
118
133
  }
119
134
 
120
- const bytes = new Uint8Array(XID_BYTES)
121
- writeXidBytesUnchecked(secs, machineId, processId, counter, bytes, 0)
122
- return encodeBase32Hex(bytes)
135
+ writeXidBytesUnchecked(secs, machineId, processId, counter, stringBuffer, 0)
136
+ return encodeBase32Hex(stringBuffer)
123
137
  }
124
138
 
125
139
  function toBytes(id: string): Uint8Array {