uniku 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +87 -22
  2. package/build/{bytes-CbbFzz-v.mjs → bytes-xqWxFYsM.mjs} +1 -1
  3. package/build/{bytes-CbbFzz-v.mjs.map → bytes-xqWxFYsM.mjs.map} +1 -1
  4. package/build/cuid/v2.d.mts +2 -0
  5. package/build/cuid/v2.mjs +1 -0
  6. package/build/cuid2/cuid2.d.mts +5 -1
  7. package/build/cuid2/cuid2.d.mts.map +1 -1
  8. package/build/cuid2/cuid2.mjs +1 -1
  9. package/build/cuid2/cuid2.mjs.map +1 -1
  10. package/build/errors-Dgoyi-ci.d.mts +38 -0
  11. package/build/errors-Dgoyi-ci.d.mts.map +1 -0
  12. package/build/errors.d.mts +2 -38
  13. package/build/errors.mjs +1 -1
  14. package/build/errors.mjs.map +1 -1
  15. package/build/generators.d.mts +17 -0
  16. package/build/generators.d.mts.map +1 -0
  17. package/build/generators.mjs +2 -0
  18. package/build/generators.mjs.map +1 -0
  19. package/build/ksuid/ksuid.d.mts +3 -5
  20. package/build/ksuid/ksuid.d.mts.map +1 -1
  21. package/build/ksuid/ksuid.mjs +1 -1
  22. package/build/ksuid/ksuid.mjs.map +1 -1
  23. package/build/nanoid/nanoid.d.mts +3 -6
  24. package/build/nanoid/nanoid.d.mts.map +1 -1
  25. package/build/nanoid/nanoid.mjs +1 -1
  26. package/build/nanoid/nanoid.mjs.map +1 -1
  27. package/build/objectid/objectid.d.mts +3 -5
  28. package/build/objectid/objectid.d.mts.map +1 -1
  29. package/build/objectid/objectid.mjs +1 -1
  30. package/build/objectid/objectid.mjs.map +1 -1
  31. package/build/{random-C_nRoaUe.mjs → random-Chp-Nkzi.mjs} +1 -1
  32. package/build/{random-C_nRoaUe.mjs.map → random-Chp-Nkzi.mjs.map} +1 -1
  33. package/build/tsid/tsid.d.mts +82 -0
  34. package/build/tsid/tsid.d.mts.map +1 -0
  35. package/build/tsid/tsid.mjs +2 -0
  36. package/build/tsid/tsid.mjs.map +1 -0
  37. package/build/typeid/typeid.d.mts +1 -1
  38. package/build/typeid/typeid.d.mts.map +1 -1
  39. package/build/typeid/typeid.mjs.map +1 -1
  40. package/build/ulid/ulid.d.mts +3 -5
  41. package/build/ulid/ulid.d.mts.map +1 -1
  42. package/build/ulid/ulid.mjs +1 -1
  43. package/build/ulid/ulid.mjs.map +1 -1
  44. package/build/uuid/v4.d.mts +3 -6
  45. package/build/uuid/v4.d.mts.map +1 -1
  46. package/build/uuid/v4.mjs +1 -1
  47. package/build/uuid/v4.mjs.map +1 -1
  48. package/build/uuid/v7.d.mts +6 -5
  49. package/build/uuid/v7.d.mts.map +1 -1
  50. package/build/uuid/v7.mjs +1 -1
  51. package/build/uuid/v7.mjs.map +1 -1
  52. package/build/uuid-CnQIYoQi.mjs +2 -0
  53. package/build/uuid-CnQIYoQi.mjs.map +1 -0
  54. package/build/validation-CTNpXm94.mjs +2 -0
  55. package/build/validation-CTNpXm94.mjs.map +1 -0
  56. package/package.json +27 -5
  57. package/src/common/validation.ts +7 -0
  58. package/src/cuid/v2.ts +19 -0
  59. package/src/cuid2/cuid2.ts +11 -3
  60. package/src/generators.ts +17 -0
  61. package/src/ksuid/ksuid.ts +19 -17
  62. package/src/objectid/objectid.ts +25 -17
  63. package/src/tsid/crockford64.ts +75 -0
  64. package/src/tsid/tsid.ts +340 -0
  65. package/src/ulid/crockford.ts +2 -2
  66. package/src/ulid/ulid.ts +35 -24
  67. package/src/uuid/common/uuid.ts +12 -1
  68. package/src/uuid/v4.ts +25 -28
  69. package/src/uuid/v7.ts +75 -47
  70. package/build/errors.d.mts.map +0 -1
  71. package/build/uuid-C9VcXh5N.mjs +0 -2
  72. package/build/uuid-C9VcXh5N.mjs.map +0 -1
package/README.md CHANGED
@@ -24,20 +24,21 @@ console.log(first < second && second < third) // true
24
24
 
25
25
  ## At a Glance
26
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) |
28
- |--------------------|:-----:|:----:|:---------:|:------:|:----:|:-----:|:-----:|:-----:|
29
- | UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
30
- | UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
31
- | TypeID | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
32
- | Nanoid | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
33
- | ULID | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
34
- | CUID2 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
35
- | KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
36
- | ObjectID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
37
- | Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
38
- | ESM-only | ✅ | ✅¹ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
39
- | Edge/Workers | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ |
40
- | Byte ↔ String | ✅ | ✅ | ✅ | - | ⚠️² | - | ✅ | ✅ |
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
+ | Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
39
+ | ESM-only | ✅ | ✅¹ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
40
+ | Edge/Workers | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ⚠️ |
41
+ | Byte ↔ String | ✅ | ✅ | ✅ | - | ⚠️² | - | ✅ | ✅ | ✅ |
41
42
 
42
43
  > **Notes:**
43
44
  > - Byte ↔ String conversion doesn't make sense for nanoid and cuid2, since they are string-native formats with no canonical binary representation.
@@ -65,6 +66,7 @@ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
65
66
  | CUID2 | **8× faster** |
66
67
  | KSUID | **1.5× faster** |
67
68
  | ObjectID | **1.1× faster** |
69
+ | TSID | **1.7× faster** |
68
70
  | UUID v7 | **1.1× faster** |
69
71
  | Nanoid | **~comparable speed** |
70
72
  | Nanoid (10 chars) | npm is 1.1× faster |
@@ -85,6 +87,7 @@ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
85
87
  | Distributed systems needing sortable, URL-safe IDs | **ULID** | Millisecond ordering + 80-bit entropy |
86
88
  | Very high-volume distributed systems | **KSUID** | Time-ordered with 128-bit entropy |
87
89
  | MongoDB `_id` compatibility | **ObjectID** | Drop-in match for MongoDB's native document ID format |
90
+ | Native 64-bit sortable integer ID | **TSID** | Fits a database `BIGINT` primary key, no UUID-sized overhead |
88
91
 
89
92
  ### Detailed Guide
90
93
 
@@ -123,6 +126,12 @@ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
123
126
  - Counter never resets on a new timestamp (unlike ULID/UUIDv7/KSUID sequences)
124
127
  - Best for: MongoDB `_id` fields, systems already speaking the ObjectID wire format
125
128
 
129
+ **TSID** — Use when you need a native 64-bit sortable integer ID, e.g. a database `BIGINT` primary key.
130
+ - 64-bit Snowflake-style value, time-ordered with millisecond precision
131
+ - Returns a `bigint` by default (the only uniku generator that isn't string-primary); `toString`/`fromString` convert to/from the 13-character canonical string
132
+ - 42-bit timestamp + 10-bit node + 12-bit per-millisecond counter, with a persistent lazily-random node ID
133
+ - Best for: relational database `BIGINT` primary keys, high-throughput distributed ID generation without central coordination
134
+
126
135
  **Nanoid** — Use for short, URL-friendly identifiers.
127
136
  - 21 characters (configurable), 126-bit entropy
128
137
  - Customizable alphabet and length
@@ -137,7 +146,7 @@ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
137
146
 
138
147
  ### Binary Serialization: When to Use `toBytes()`
139
148
 
140
- Formats with binary representations (UUID, ULID, KSUID, ObjectID) support `toBytes()` and `fromBytes()` for efficient storage:
149
+ Formats with binary representations (UUID, ULID, KSUID, ObjectID, TSID) support `toBytes()` and `fromBytes()` for efficient storage:
141
150
 
142
151
  ```ts
143
152
  // Store as BINARY(16) instead of VARCHAR(36)
@@ -158,6 +167,8 @@ const id = uuidv7.fromBytes(row.id)
158
167
  - Performance-critical queries on ID columns
159
168
  - Storage cost is a concern
160
169
 
170
+ **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.
171
+
161
172
  **When to keep strings:**
162
173
  - Debugging/logging convenience
163
174
  - API responses (humans read them)
@@ -170,10 +181,13 @@ const id = uuidv7.fromBytes(row.id)
170
181
  | TypeID | prefix + 27 chars | 16 bytes | prefix-dependent |
171
182
  | KSUID | 27 chars | 20 bytes | 26% |
172
183
  | ObjectID | 24 chars | 12 bytes | 50% |
184
+ | TSID | 13 chars* | 8 bytes | 38% |
173
185
  | Nanoid | 21 chars | N/A | - |
174
186
  | CUID2 | 24 chars | N/A | - |
175
187
 
176
188
  > **Note**: Nanoid and CUID2 don't have binary representations because they're string-native formats with no canonical byte encoding.
189
+ >
190
+ > \* 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.
177
191
 
178
192
  ## Installation
179
193
 
@@ -202,10 +216,13 @@ deno install npm:uniku
202
216
  | `uniku/uuid/v7` | ~1.4 KB |
203
217
  | `uniku/ulid` | ~1.8 KB |
204
218
  | `uniku/typeid` | ~1.6 KB |
219
+ | `uniku/cuid/v2` | ~992 B* |
205
220
  | `uniku/cuid2` | ~1007 B* |
206
221
  | `uniku/nanoid` | ~1.1 KB |
207
222
  | `uniku/ksuid` | ~1.3 KB |
208
223
  | `uniku/objectid` | ~1.3 KB |
224
+ | `uniku/tsid` | ~1.4 KB |
225
+ | `uniku/generators` | ~98 B |
209
226
 
210
227
  * The CUID2 entry point imports SHA3-512 from `@noble/hashes`; this table's entry-point size excludes that external dependency.
211
228
 
@@ -268,21 +285,25 @@ const canonical = typeid('')
268
285
  ### CUID2 (secure, non-time-ordered)
269
286
 
270
287
  ```ts
271
- import { cuid2 } from 'uniku/cuid2'
288
+ import { cuidv2 } from 'uniku/cuid/v2'
272
289
 
273
- const id = cuid2()
290
+ const id = cuidv2()
274
291
  // => "pfh0haxfpzowht3oi213cqos"
275
292
 
276
293
  // Custom length
277
- const shortId = cuid2({ length: 10 })
294
+ const shortId = cuidv2({ length: 10 })
278
295
  // => "tz4a98xxat"
279
296
 
280
297
  // Validation (type guard)
281
- if (cuid2.isValid(maybeId)) {
298
+ if (cuidv2.isValid(maybeId)) {
282
299
  console.log(maybeId) // TypeScript knows this is a string
283
300
  }
284
301
  ```
285
302
 
303
+ > `uniku/cuid/v2` is the canonical entry point. The older `uniku/cuid2` import
304
+ > still works and re-exports the same generator, but is now deprecated — prefer
305
+ > `import { cuidv2 } from 'uniku/cuid/v2'`.
306
+
286
307
  ### Nanoid (URL-friendly, custom alphabet)
287
308
 
288
309
  ```ts
@@ -332,6 +353,28 @@ const str = objectid.fromBytes(bytes)
332
353
  const ts = objectid.timestamp(id)
333
354
  ```
334
355
 
356
+ ### TSID (time-ordered, 64-bit integer)
357
+
358
+ ```ts
359
+ import { tsid } from 'uniku/tsid'
360
+
361
+ // Generate a TSID bigint (the primary type, not a string)
362
+ const id = tsid()
363
+ // => 862301223059968074n
364
+
365
+ // Convert to/from the canonical 13-character string
366
+ const str = tsid.toString(id)
367
+ // => "0QXW2CK4XZM2A"
368
+ tsid.fromString(str) === id // true
369
+
370
+ // Extract timestamp (milliseconds, full precision)
371
+ const ts = tsid.timestamp(id)
372
+
373
+ // Convert to/from 8 bytes
374
+ const bytes = tsid.toBytes(id)
375
+ const restored = tsid.fromBytes(bytes)
376
+ ```
377
+
335
378
  ## Migrating to uniku
336
379
 
337
380
  ### From `uuid`
@@ -445,13 +488,16 @@ typeid.isValid(id: unknown): id is string
445
488
 
446
489
  Use an empty prefix (`typeid('')`) to generate Jetify-compatible prefixless TypeIDs.
447
490
 
448
- ### `cuid2` (from `uniku/cuid2`)
491
+ ### `cuidv2` (from `uniku/cuid/v2`)
449
492
 
450
493
  ```ts
451
- cuid2(options?: Cuid2Options): string
452
- cuid2.isValid(id: unknown): id is string
494
+ cuidv2(options?: CuidV2Options): string
495
+ cuidv2.isValid(id: unknown): id is string
453
496
  ```
454
497
 
498
+ > Also available (deprecated) as `cuid2` from `uniku/cuid2` — the same generator
499
+ > under the pre-versioned entry point.
500
+
455
501
  ### `nanoid` (from `uniku/nanoid`)
456
502
 
457
503
  ```ts
@@ -490,9 +536,27 @@ objectid.NIL // "000000000000000000000000"
490
536
  objectid.MAX // "ffffffffffffffffffffffff"
491
537
  ```
492
538
 
539
+ ### `tsid` (from `uniku/tsid`)
540
+
541
+ ```ts
542
+ tsid(options?: TsidOptions): bigint
543
+ tsid(options: TsidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
544
+ tsid.toBytes(id: bigint): Uint8Array
545
+ tsid.fromBytes(bytes: Uint8Array): bigint
546
+ tsid.toString(id: bigint): string
547
+ tsid.fromString(str: string): bigint
548
+ tsid.timestamp(id: bigint, epoch?: number): number
549
+ tsid.isValid(id: unknown): id is bigint
550
+ tsid.NIL // 0n
551
+ tsid.MAX // 18446744073709551615n
552
+ ```
553
+
554
+ > Unlike every other generator listed here, `tsid()` returns `bigint` by default, and `toBytes`/`fromBytes`/`timestamp`/`isValid` all operate on that `bigint`. `toString`/`fromString` are the boundary conversions to/from the 13-character canonical Crockford Base32 string.
555
+
493
556
  ## Documentation
494
557
 
495
558
  For advanced usage, examples, and contributing guidelines, see the [full documentation on GitHub](https://github.com/jkomyno/uniku).
559
+ The [`uniku@1.x` stability contract](../../docs/STABILITY.md) defines the supported entry points, runtimes, and release gates.
496
560
 
497
561
  ## Related Projects
498
562
 
@@ -505,6 +569,7 @@ Third-party libraries that inspired this project:
505
569
  - [@owpz/ksuid](https://github.com/owpz/ksuid): K-Sortable Unique Identifier
506
570
  - [bson](https://github.com/mongodb/js-bson): official MongoDB BSON library, including the ObjectId implementation
507
571
  - [nanoid](https://github.com/ai/nanoid): tiny, URL-friendly unique string ID generator
572
+ - [tsid-ts](https://github.com/yubinTW/tsid-ts): TypeScript implementation of TSID, a Snowflake-style 64-bit Time-Sorted Unique Identifier
508
573
 
509
574
  Other:
510
575
 
@@ -1,2 +1,2 @@
1
1
  function e(e){for(let t=e.length-1;t>=0;--t){if(e[t]<255)return e[t]+=1,!0;e[t]=0}return!1}function t(e,t,n){e[t]=n/1099511627776&255,e[t+1]=n/4294967296&255,e[t+2]=n/16777216&255,e[t+3]=n/65536&255,e[t+4]=n/256&255,e[t+5]=n&255}function n(e,t,n){e[t]=n>>>24&255,e[t+1]=n>>>16&255,e[t+2]=n>>>8&255,e[t+3]=n&255}export{n,t as r,e as t};
2
- //# sourceMappingURL=bytes-CbbFzz-v.mjs.map
2
+ //# sourceMappingURL=bytes-xqWxFYsM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bytes-CbbFzz-v.mjs","names":[],"sources":["../src/common/bytes.ts"],"sourcesContent":["/**\n * Common byte manipulation utilities shared across ID generators.\n */\n\n/**\n * Increment a byte array by 1 in-place, propagating carry from LSB to MSB.\n * Mutates the input array directly - no allocation.\n *\n * Used by ULID and KSUID for monotonic ordering within the same time unit.\n *\n * @returns true if increment succeeded, false if all bytes overflowed to 0\n */\nexport function incrementBytesInPlace(bytes: Uint8Array): boolean {\n for (let i = bytes.length - 1; i >= 0; i -= 1) {\n if (bytes[i] < 255) {\n bytes[i] += 1\n return true\n }\n bytes[i] = 0\n }\n\n // All bytes overflowed to 0 - astronomically unlikely (1 in 2^80 for ULID, 1 in 2^128 for KSUID)\n return false\n}\n\n/**\n * Write a 48-bit timestamp as big-endian bytes.\n * Used by UUID v7 and ULID for millisecond-precision timestamps.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param msecs - Milliseconds since Unix epoch (must fit in 48 bits)\n */\nexport function writeTimestamp48(buf: Uint8Array, offset: number, msecs: number): void {\n buf[offset] = (msecs / 0x10000000000) & 0xff\n buf[offset + 1] = (msecs / 0x100000000) & 0xff\n buf[offset + 2] = (msecs / 0x1000000) & 0xff\n buf[offset + 3] = (msecs / 0x10000) & 0xff\n buf[offset + 4] = (msecs / 0x100) & 0xff\n buf[offset + 5] = msecs & 0xff\n}\n\n/**\n * Write a 32-bit timestamp as big-endian bytes.\n * Used by KSUID for second-precision timestamps.\n *\n * `writeTimestamp32(buf, offset, timestamp)` is equivalent to `new DataView(buf.buffer, offset, 4).setUint32(offset, timestamp, false)`,\n * but it's faster because it doesn't require creating a DataView object.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param secs - Seconds (must fit in 32 bits)\n */\nexport function writeTimestamp32(buf: Uint8Array, offset: number, secs: number): void {\n buf[offset] = (secs >>> 24) & 0xff\n buf[offset + 1] = (secs >>> 16) & 0xff\n buf[offset + 2] = (secs >>> 8) & 0xff\n buf[offset + 3] = secs & 0xff\n}\n"],"mappings":"AAYA,SAAgB,EAAsB,EAA4B,CAChE,IAAK,IAAI,EAAI,EAAM,OAAS,EAAG,GAAK,EAAG,IAAQ,CAC7C,GAAI,EAAM,GAAK,IAEb,MADA,GAAM,IAAM,EACL,GAET,EAAM,GAAK,EAIb,MAAO,GAWT,SAAgB,EAAiB,EAAiB,EAAgB,EAAqB,CACrF,EAAI,GAAW,EAAQ,cAAiB,IACxC,EAAI,EAAS,GAAM,EAAQ,WAAe,IAC1C,EAAI,EAAS,GAAM,EAAQ,SAAa,IACxC,EAAI,EAAS,GAAM,EAAQ,MAAW,IACtC,EAAI,EAAS,GAAM,EAAQ,IAAS,IACpC,EAAI,EAAS,GAAK,EAAQ,IAc5B,SAAgB,EAAiB,EAAiB,EAAgB,EAAoB,CACpF,EAAI,GAAW,IAAS,GAAM,IAC9B,EAAI,EAAS,GAAM,IAAS,GAAM,IAClC,EAAI,EAAS,GAAM,IAAS,EAAK,IACjC,EAAI,EAAS,GAAK,EAAO"}
1
+ {"version":3,"file":"bytes-xqWxFYsM.mjs","names":[],"sources":["../src/common/bytes.ts"],"sourcesContent":["/**\n * Common byte manipulation utilities shared across ID generators.\n */\n\n/**\n * Increment a byte array by 1 in-place, propagating carry from LSB to MSB.\n * Mutates the input array directly - no allocation.\n *\n * Used by ULID and KSUID for monotonic ordering within the same time unit.\n *\n * @returns true if increment succeeded, false if all bytes overflowed to 0\n */\nexport function incrementBytesInPlace(bytes: Uint8Array): boolean {\n for (let i = bytes.length - 1; i >= 0; i -= 1) {\n if (bytes[i] < 255) {\n bytes[i] += 1\n return true\n }\n bytes[i] = 0\n }\n\n // All bytes overflowed to 0 - astronomically unlikely (1 in 2^80 for ULID, 1 in 2^128 for KSUID)\n return false\n}\n\n/**\n * Write a 48-bit timestamp as big-endian bytes.\n * Used by UUID v7 and ULID for millisecond-precision timestamps.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param msecs - Milliseconds since Unix epoch (must fit in 48 bits)\n */\nexport function writeTimestamp48(buf: Uint8Array, offset: number, msecs: number): void {\n buf[offset] = (msecs / 0x10000000000) & 0xff\n buf[offset + 1] = (msecs / 0x100000000) & 0xff\n buf[offset + 2] = (msecs / 0x1000000) & 0xff\n buf[offset + 3] = (msecs / 0x10000) & 0xff\n buf[offset + 4] = (msecs / 0x100) & 0xff\n buf[offset + 5] = msecs & 0xff\n}\n\n/**\n * Write a 32-bit timestamp as big-endian bytes.\n * Used by KSUID for second-precision timestamps.\n *\n * `writeTimestamp32(buf, offset, timestamp)` is equivalent to `new DataView(buf.buffer, offset, 4).setUint32(offset, timestamp, false)`,\n * but it's faster because it doesn't require creating a DataView object.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param secs - Seconds (must fit in 32 bits)\n */\nexport function writeTimestamp32(buf: Uint8Array, offset: number, secs: number): void {\n buf[offset] = (secs >>> 24) & 0xff\n buf[offset + 1] = (secs >>> 16) & 0xff\n buf[offset + 2] = (secs >>> 8) & 0xff\n buf[offset + 3] = secs & 0xff\n}\n"],"mappings":"AAYA,SAAgB,EAAsB,EAA4B,CAChE,IAAK,IAAI,EAAI,EAAM,OAAS,EAAG,GAAK,EAAG,IAAQ,CAC7C,GAAI,EAAM,GAAK,IAEb,MADA,GAAM,IAAM,EACL,GAET,EAAM,GAAK,CACb,CAGA,MAAO,EACT,CAUA,SAAgB,EAAiB,EAAiB,EAAgB,EAAqB,CACrF,EAAI,GAAW,EAAQ,cAAiB,IACxC,EAAI,EAAS,GAAM,EAAQ,WAAe,IAC1C,EAAI,EAAS,GAAM,EAAQ,SAAa,IACxC,EAAI,EAAS,GAAM,EAAQ,MAAW,IACtC,EAAI,EAAS,GAAM,EAAQ,IAAS,IACpC,EAAI,EAAS,GAAK,EAAQ,GAC5B,CAaA,SAAgB,EAAiB,EAAiB,EAAgB,EAAoB,CACpF,EAAI,GAAW,IAAS,GAAM,IAC9B,EAAI,EAAS,GAAM,IAAS,GAAM,IAClC,EAAI,EAAS,GAAM,IAAS,EAAK,IACjC,EAAI,EAAS,GAAK,EAAO,GAC3B"}
@@ -0,0 +1,2 @@
1
+ import { Cuid2, Cuid2Options, cuid2 } from "../cuid2/cuid2.mjs";
2
+ export { type Cuid2 as CuidV2, type Cuid2Options as CuidV2Options, cuid2 as cuidv2 };
@@ -0,0 +1 @@
1
+ import{cuid2 as e}from"../cuid2/cuid2.mjs";export{e as cuidv2};
@@ -1,4 +1,4 @@
1
- import { InvalidInputError, UniqueIdError } from "../errors.mjs";
1
+ import { i as UniqueIdError, n as InvalidInputError } from "../errors-Dgoyi-ci.mjs";
2
2
 
3
3
  //#region src/cuid2/cuid2.d.ts
4
4
  type Cuid2Options = {
@@ -46,6 +46,10 @@ type Cuid2 = {
46
46
  * console.log(maybeId.length) // TypeScript knows maybeId is string
47
47
  * }
48
48
  * ```
49
+ *
50
+ * @deprecated Use `cuidv2` from `uniku/cuid/v2` instead. This entry point keeps
51
+ * working unchanged, but `uniku/cuid/v2` is the canonical versioned subpath
52
+ * (mirroring `uniku/uuid/v4` / `uniku/uuid/v7`).
49
53
  */
50
54
  declare const cuid2: Cuid2;
51
55
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"cuid2.d.mts","names":["cuid2: Cuid2"],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":[],"mappings":";;;KAIY,YAAA;;;AAAZ;AAeA;EA2LA,MAAaA,CAAAA,EAAAA,MAAO;;;;;;;WA9LT;;KAGC,KAAA;aACC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0LAA,OAAO"}
1
+ {"version":3,"file":"cuid2.d.mts","names":[],"sources":["../../src/cuid2/cuid2.ts"],"mappings":";;;KAIY,YAAA;;;AAAZ;;EAKE,MAAA;EAOS;;;;;AAAA;EAAT,MAAA,GAAS,UAAA;AAAA;AAAA,KAGC,KAAA;EAAA,CACT,OAAA,GAAU,YAAA;EACX,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;AAAA;AAiMxB;;;;AAAoB;;;;;;;;;;;;;;;;;;;;;;;cAAP,KAAA,EAAO,KAAA"}
@@ -1,2 +1,2 @@
1
- import{n as e}from"../random-C_nRoaUe.mjs";import{InvalidInputError as t,UniqueIdError as n}from"../errors.mjs";import{sha3_512 as r}from"@noble/hashes/sha3.js";const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder,s={counter:void 0,fingerprint:void 0};function c(){return e()%476782368}function l(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function u(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function d(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function f(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function p(e){return r(o.encode(e))}function m(){let e=h;return u(l(p(Object.keys(globalThis).toString()+f(32,e)))).slice(1,33)}function h(){return e()/4294967296}function g(e){if(e){if(e.length===0)throw new t(`CUID2_RANDOM_BYTES_EMPTY`,`Random byte array cannot be empty`);let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return h}function _(e){let n=e?.length??24;if(n<2||n>32)throw new t(`CUID2_LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`);let r=g(e?.random);s.counter===void 0&&(s.counter=c()),s.fingerprint===void 0&&(s.fingerprint=m());let i=d(r),a=Date.now().toString(36);s.counter+=1;let o=s.counter.toString(36);return i+u(l(p(a+f(n,r)+o+s.fingerprint))).slice(1,n)}function v(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const y=Object.assign(_,{isValid:v});export{t as InvalidInputError,n as UniqueIdError,y as cuid2};
1
+ import{n as e}from"../random-Chp-Nkzi.mjs";import{InvalidInputError as t,UniqueIdError as n}from"../errors.mjs";import{sha3_512 as r}from"@noble/hashes/sha3.js";const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder,s={counter:void 0,fingerprint:void 0};function c(){return e()%476782368}function l(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function u(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function d(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function f(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function p(e){return r(o.encode(e))}function m(){let e=h;return u(l(p(Object.keys(globalThis).toString()+f(32,e)))).slice(1,33)}function h(){return e()/4294967296}function g(e){if(e){if(e.length===0)throw new t(`CUID2_RANDOM_BYTES_EMPTY`,`Random byte array cannot be empty`);let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return h}function _(e){let n=e?.length;if(n!==void 0&&(!Number.isInteger(n)||n<2||n>32))throw new t(`CUID2_LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`);let r=n??24,i=g(e?.random);s.counter===void 0&&(s.counter=c()),s.fingerprint===void 0&&(s.fingerprint=m());let a=d(i),o=Date.now().toString(36);s.counter+=1;let h=s.counter.toString(36);return a+u(l(p(o+f(r,i)+h+s.fingerprint))).slice(1,r)}function v(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const y=Object.assign(_,{isValid:v});export{t as InvalidInputError,n as UniqueIdError,y as cuid2};
2
2
  //# sourceMappingURL=cuid2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"cuid2.mjs","names":["state: { counter: number | undefined; fingerprint: string | undefined }","chars: string[]","cuid2: Cuid2"],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":["import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { randomUint32 } from '../common/random'\nimport { InvalidInputError } from '../errors'\n\nexport type Cuid2Options = {\n /**\n * Length of the generated ID (2-32 characters).\n * Default: 24\n */\n length?: number\n /**\n * Custom random bytes for deterministic testing.\n * Must be at least 1 byte. For adequate entropy, use at least 16 bytes.\n * Note: The fingerprint always uses cryptographically secure random bytes,\n * regardless of this option.\n */\n random?: Uint8Array\n}\n\nexport type Cuid2 = {\n (options?: Cuid2Options): string\n isValid(id: unknown): id is string\n}\n\nconst DEFAULT_LENGTH = 24\nconst MAX_LENGTH = 32\nconst MIN_LENGTH = 2\n\n// Maximum initial counter value from cuid2 spec - provides ~29 bits of\n// initial entropy to prevent cross-process collisions at startup\nconst INITIAL_COUNT_MAX = 476782367\n\n// Validation regex: first char must be a-z, rest can be a-z or 0-9\nconst CUID2_REGEX = /^[a-z][0-9a-z]+$/\n\n// Base36 alphabet\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\nconst LETTER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz'\n\n// Reusable TextEncoder instance (stateless, safe to share)\nconst textEncoder = new TextEncoder()\n\n/**\n * Module-level state for counter and fingerprint.\n * Counter is initialized lazily on first call to prevent unnecessary crypto operations.\n * Fingerprint is also generated lazily on first call.\n */\nconst state: { counter: number | undefined; fingerprint: string | undefined } = {\n counter: undefined,\n fingerprint: undefined,\n}\n\n/**\n * Initialize counter using crypto for consistency with other entropy sources.\n */\nfunction initializeCounter(): number {\n return randomUint32() % (INITIAL_COUNT_MAX + 1)\n}\n\n// --- Base36 utilities ---\n\nfunction bufToBigInt(buf: Uint8Array): bigint {\n let value = 0n\n for (const byte of buf) {\n value = value * 256n + BigInt(byte)\n }\n return value\n}\n\nfunction bigIntToBase36(value: bigint): string {\n if (value === 0n) return '0'\n const chars: string[] = []\n while (value > 0n) {\n chars.push(ALPHABET[Number(value % 36n)])\n value = value / 36n\n }\n return chars.reverse().join('')\n}\n\nfunction randomLetter(random: () => number): string {\n return LETTER_ALPHABET[Math.floor(random() * 26)]\n}\n\nfunction createEntropy(length: number, random: () => number): string {\n const chars = new Array<string>(length)\n for (let i = 0; i < length; i++) {\n chars[i] = ALPHABET[Math.floor(random() * 36)]\n }\n return chars.join('')\n}\n\n// --- SHA3 hash wrapper ---\n\nfunction hash(input: string): Uint8Array {\n return sha3_512(textEncoder.encode(input))\n}\n\n// --- Fingerprint generation ---\n\nconst BIG_LENGTH = 32\n\nfunction createFingerprint(): string {\n // Always use CSPRNG for fingerprint to ensure security regardless of custom random option\n const random = getCryptoRandom\n const globals = Object.keys(globalThis).toString()\n const sourceString = globals + createEntropy(BIG_LENGTH, random)\n const hashed = hash(sourceString)\n return bigIntToBase36(bufToBigInt(hashed)).slice(1, BIG_LENGTH + 1)\n}\n\n// --- Random function factory ---\n\n/**\n * Get a random number in [0, 1) using CUID2's own random pool.\n */\nfunction getCryptoRandom(): number {\n return randomUint32() / 0x100000000\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new InvalidInputError('CUID2_RANDOM_BYTES_EMPTY', 'Random byte array cannot be empty')\n }\n let index = 0\n return () => {\n const value = random[index % random.length] / 256\n index += 1\n return value\n }\n }\n return getCryptoRandom\n}\n\n// --- Main generator ---\n\nfunction cuid2Fn(options?: Cuid2Options): string {\n const length = options?.length ?? DEFAULT_LENGTH\n\n if (length < MIN_LENGTH || length > MAX_LENGTH) {\n throw new InvalidInputError(\n 'CUID2_LENGTH_OUT_OF_RANGE',\n `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`,\n )\n }\n\n const random = getRandomFn(options?.random)\n\n // Initialize counter lazily on first call\n if (state.counter === undefined) {\n state.counter = initializeCounter()\n }\n\n // Initialize fingerprint lazily on first call (always uses CSPRNG)\n if (state.fingerprint === undefined) {\n state.fingerprint = createFingerprint()\n }\n\n const firstLetter = randomLetter(random)\n const time = Date.now().toString(36)\n state.counter += 1\n const count = state.counter.toString(36)\n const salt = createEntropy(length, random)\n\n const hashInput = time + salt + count + state.fingerprint\n const hashed = hash(hashInput)\n const base36Hash = bigIntToBase36(bufToBigInt(hashed))\n\n // Drop first char of hash to avoid histogram bias, prepend random letter\n return firstLetter + base36Hash.slice(1, length)\n}\n\n// --- Validation (type guard) ---\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id)\n}\n\n/**\n * Generate a CUID2 string.\n *\n * CUID2 is a secure, collision-resistant identifier that hashes multiple\n * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),\n * CUID2 prevents enumeration attacks by making IDs non-predictable.\n *\n * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native\n * format with no canonical binary representation (unlike UUID's 16-byte format).\n *\n * @example\n * ```ts\n * import { cuid2 } from 'uniku/cuid2'\n *\n * const id = cuid2()\n * // => \"pfh0haxfpzowht3oi213cqos\"\n *\n * // Custom length\n * const shortId = cuid2({ length: 10 })\n * // => \"tz4a98xxat\"\n *\n * // Validation (type guard)\n * const maybeId: unknown = getUserInput()\n * if (cuid2.isValid(maybeId)) {\n * console.log(maybeId.length) // TypeScript knows maybeId is string\n * }\n * ```\n */\nexport const cuid2: Cuid2 = Object.assign(cuid2Fn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"iKAwBA,MASM,EAAc,mBAGd,EAAW,uCAIX,EAAc,IAAI,YAOlBA,EAA0E,CAC9E,QAAS,IAAA,GACT,YAAa,IAAA,GACd,CAKD,SAAS,GAA4B,CACnC,OAAO,GAAc,CAAI,UAK3B,SAAS,EAAY,EAAyB,CAC5C,IAAI,EAAQ,GACZ,IAAK,IAAM,KAAQ,EACjB,EAAQ,EAAQ,KAAO,OAAO,EAAK,CAErC,OAAO,EAGT,SAAS,EAAe,EAAuB,CAC7C,GAAI,IAAU,GAAI,MAAO,IACzB,IAAMC,EAAkB,EAAE,CAC1B,KAAO,EAAQ,IACb,EAAM,KAAK,EAAS,OAAO,EAAQ,IAAI,EAAE,CACzC,GAAgB,IAElB,OAAO,EAAM,SAAS,CAAC,KAAK,GAAG,CAGjC,SAAS,EAAa,EAA8B,CAClD,MAAO,6BAAgB,KAAK,MAAM,GAAQ,CAAG,GAAG,EAGlD,SAAS,EAAc,EAAgB,EAA8B,CACnE,IAAM,EAAY,MAAc,EAAO,CACvC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAC1B,EAAM,GAAK,EAAS,KAAK,MAAM,GAAQ,CAAG,GAAG,EAE/C,OAAO,EAAM,KAAK,GAAG,CAKvB,SAAS,EAAK,EAA2B,CACvC,OAAO,EAAS,EAAY,OAAO,EAAM,CAAC,CAO5C,SAAS,GAA4B,CAEnC,IAAM,EAAS,EAIf,OAAO,EAAe,EADP,EAFC,OAAO,KAAK,WAAW,CAAC,UAAU,CACnB,EAAc,GAAY,EAAO,CAC/B,CACQ,CAAC,CAAC,MAAM,EAAG,GAAe,CAQrE,SAAS,GAA0B,CACjC,OAAO,GAAc,CAAG,WAG1B,SAAS,EAAY,EAAmC,CACtD,GAAI,EAAQ,CACV,GAAI,EAAO,SAAW,EACpB,MAAM,IAAI,EAAkB,2BAA4B,oCAAoC,CAE9F,IAAI,EAAQ,EACZ,UAAa,CACX,IAAM,EAAQ,EAAO,EAAQ,EAAO,QAAU,IAE9C,MADA,IAAS,EACF,GAGX,OAAO,EAKT,SAAS,EAAQ,EAAgC,CAC/C,IAAM,EAAS,GAAS,QAAU,GAElC,GAAI,EAAS,GAAc,EAAS,GAClC,MAAM,IAAI,EACR,4BACA,oDAA2E,IAC5E,CAGH,IAAM,EAAS,EAAY,GAAS,OAAO,CAGvC,EAAM,UAAY,IAAA,KACpB,EAAM,QAAU,GAAmB,EAIjC,EAAM,cAAgB,IAAA,KACxB,EAAM,YAAc,GAAmB,EAGzC,IAAM,EAAc,EAAa,EAAO,CAClC,EAAO,KAAK,KAAK,CAAC,SAAS,GAAG,CACpC,EAAM,SAAW,EACjB,IAAM,EAAQ,EAAM,QAAQ,SAAS,GAAG,CAQxC,OAAO,EAHY,EAAe,EADnB,EADG,EAFL,EAAc,EAAQ,EAAO,CAEV,EAAQ,EAAM,YAChB,CACuB,CAAC,CAGtB,MAAM,EAAG,EAAO,CAKlD,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAG,QAAU,GAAc,EAAG,QAAU,IAAc,EAAY,KAAK,EAAG,CA+B7G,MAAaC,EAAe,OAAO,OAAO,EAAS,CACjD,UACD,CAAC"}
1
+ {"version":3,"file":"cuid2.mjs","names":[],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":["import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { randomUint32 } from '../common/random'\nimport { InvalidInputError } from '../errors'\n\nexport type Cuid2Options = {\n /**\n * Length of the generated ID (2-32 characters).\n * Default: 24\n */\n length?: number\n /**\n * Custom random bytes for deterministic testing.\n * Must be at least 1 byte. For adequate entropy, use at least 16 bytes.\n * Note: The fingerprint always uses cryptographically secure random bytes,\n * regardless of this option.\n */\n random?: Uint8Array\n}\n\nexport type Cuid2 = {\n (options?: Cuid2Options): string\n isValid(id: unknown): id is string\n}\n\nconst DEFAULT_LENGTH = 24\nconst MAX_LENGTH = 32\nconst MIN_LENGTH = 2\n\n// Maximum initial counter value from cuid2 spec - provides ~29 bits of\n// initial entropy to prevent cross-process collisions at startup\nconst INITIAL_COUNT_MAX = 476782367\n\n// Validation regex: first char must be a-z, rest can be a-z or 0-9\nconst CUID2_REGEX = /^[a-z][0-9a-z]+$/\n\n// Base36 alphabet\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\nconst LETTER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz'\n\n// Reusable TextEncoder instance (stateless, safe to share)\nconst textEncoder = new TextEncoder()\n\n/**\n * Module-level state for counter and fingerprint.\n * Counter is initialized lazily on first call to prevent unnecessary crypto operations.\n * Fingerprint is also generated lazily on first call.\n */\nconst state: { counter: number | undefined; fingerprint: string | undefined } = {\n counter: undefined,\n fingerprint: undefined,\n}\n\n/**\n * Initialize counter using crypto for consistency with other entropy sources.\n */\nfunction initializeCounter(): number {\n return randomUint32() % (INITIAL_COUNT_MAX + 1)\n}\n\n// --- Base36 utilities ---\n\nfunction bufToBigInt(buf: Uint8Array): bigint {\n let value = 0n\n for (const byte of buf) {\n value = value * 256n + BigInt(byte)\n }\n return value\n}\n\nfunction bigIntToBase36(value: bigint): string {\n if (value === 0n) return '0'\n const chars: string[] = []\n while (value > 0n) {\n chars.push(ALPHABET[Number(value % 36n)])\n value = value / 36n\n }\n return chars.reverse().join('')\n}\n\nfunction randomLetter(random: () => number): string {\n return LETTER_ALPHABET[Math.floor(random() * 26)]\n}\n\nfunction createEntropy(length: number, random: () => number): string {\n const chars = new Array<string>(length)\n for (let i = 0; i < length; i++) {\n chars[i] = ALPHABET[Math.floor(random() * 36)]\n }\n return chars.join('')\n}\n\n// --- SHA3 hash wrapper ---\n\nfunction hash(input: string): Uint8Array {\n return sha3_512(textEncoder.encode(input))\n}\n\n// --- Fingerprint generation ---\n\nconst BIG_LENGTH = 32\n\nfunction createFingerprint(): string {\n // Always use CSPRNG for fingerprint to ensure security regardless of custom random option\n const random = getCryptoRandom\n const globals = Object.keys(globalThis).toString()\n const sourceString = globals + createEntropy(BIG_LENGTH, random)\n const hashed = hash(sourceString)\n return bigIntToBase36(bufToBigInt(hashed)).slice(1, BIG_LENGTH + 1)\n}\n\n// --- Random function factory ---\n\n/**\n * Get a random number in [0, 1) using CUID2's own random pool.\n */\nfunction getCryptoRandom(): number {\n return randomUint32() / 0x100000000\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new InvalidInputError('CUID2_RANDOM_BYTES_EMPTY', 'Random byte array cannot be empty')\n }\n let index = 0\n return () => {\n const value = random[index % random.length] / 256\n index += 1\n return value\n }\n }\n return getCryptoRandom\n}\n\n// --- Main generator ---\n\nfunction cuid2Fn(options?: Cuid2Options): string {\n const requestedLength = options?.length\n\n if (\n requestedLength !== undefined &&\n (!Number.isInteger(requestedLength) || requestedLength < MIN_LENGTH || requestedLength > MAX_LENGTH)\n ) {\n throw new InvalidInputError(\n 'CUID2_LENGTH_OUT_OF_RANGE',\n `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${requestedLength}`,\n )\n }\n const length = requestedLength ?? DEFAULT_LENGTH\n\n const random = getRandomFn(options?.random)\n\n // Initialize counter lazily on first call\n if (state.counter === undefined) {\n state.counter = initializeCounter()\n }\n\n // Initialize fingerprint lazily on first call (always uses CSPRNG)\n if (state.fingerprint === undefined) {\n state.fingerprint = createFingerprint()\n }\n\n const firstLetter = randomLetter(random)\n const time = Date.now().toString(36)\n state.counter += 1\n const count = state.counter.toString(36)\n const salt = createEntropy(length, random)\n\n const hashInput = time + salt + count + state.fingerprint\n const hashed = hash(hashInput)\n const base36Hash = bigIntToBase36(bufToBigInt(hashed))\n\n // Drop first char of hash to avoid histogram bias, prepend random letter\n return firstLetter + base36Hash.slice(1, length)\n}\n\n// --- Validation (type guard) ---\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id)\n}\n\n/**\n * Generate a CUID2 string.\n *\n * CUID2 is a secure, collision-resistant identifier that hashes multiple\n * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),\n * CUID2 prevents enumeration attacks by making IDs non-predictable.\n *\n * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native\n * format with no canonical binary representation (unlike UUID's 16-byte format).\n *\n * @example\n * ```ts\n * import { cuid2 } from 'uniku/cuid2'\n *\n * const id = cuid2()\n * // => \"pfh0haxfpzowht3oi213cqos\"\n *\n * // Custom length\n * const shortId = cuid2({ length: 10 })\n * // => \"tz4a98xxat\"\n *\n * // Validation (type guard)\n * const maybeId: unknown = getUserInput()\n * if (cuid2.isValid(maybeId)) {\n * console.log(maybeId.length) // TypeScript knows maybeId is string\n * }\n * ```\n *\n * @deprecated Use `cuidv2` from `uniku/cuid/v2` instead. This entry point keeps\n * working unchanged, but `uniku/cuid/v2` is the canonical versioned subpath\n * (mirroring `uniku/uuid/v4` / `uniku/uuid/v7`).\n */\nexport const cuid2: Cuid2 = Object.assign(cuid2Fn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"iKAwBA,MASM,EAAc,mBAGd,EAAW,uCAIX,EAAc,IAAI,YAOlB,EAA0E,CAC9E,QAAS,IAAA,GACT,YAAa,IAAA,EACf,EAKA,SAAS,GAA4B,CACnC,OAAO,EAAa,EAAK,SAC3B,CAIA,SAAS,EAAY,EAAyB,CAC5C,IAAI,EAAQ,GACZ,IAAK,IAAM,KAAQ,EACjB,EAAQ,EAAQ,KAAO,OAAO,CAAI,EAEpC,OAAO,CACT,CAEA,SAAS,EAAe,EAAuB,CAC7C,GAAI,IAAU,GAAI,MAAO,IACzB,IAAM,EAAkB,CAAC,EACzB,KAAO,EAAQ,IACb,EAAM,KAAK,EAAS,OAAO,EAAQ,GAAG,EAAE,EACxC,GAAgB,IAElB,OAAO,EAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,CAChC,CAEA,SAAS,EAAa,EAA8B,CAClD,MAAO,6BAAgB,KAAK,MAAM,EAAO,EAAI,EAAE,EACjD,CAEA,SAAS,EAAc,EAAgB,EAA8B,CACnE,IAAM,EAAY,MAAc,CAAM,EACtC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAC1B,EAAM,GAAK,EAAS,KAAK,MAAM,EAAO,EAAI,EAAE,GAE9C,OAAO,EAAM,KAAK,EAAE,CACtB,CAIA,SAAS,EAAK,EAA2B,CACvC,OAAO,EAAS,EAAY,OAAO,CAAK,CAAC,CAC3C,CAMA,SAAS,GAA4B,CAEnC,IAAM,EAAS,EAIf,OAAO,EAAe,EADP,EAFC,OAAO,KAAK,UAAU,CAAC,CAAC,SACb,EAAI,EAAc,GAAY,CAAM,CAExB,CAAC,CAAC,CAAC,CAAC,MAAM,EAAG,EAAc,CACpE,CAOA,SAAS,GAA0B,CACjC,OAAO,EAAa,EAAI,UAC1B,CAEA,SAAS,EAAY,EAAmC,CACtD,GAAI,EAAQ,CACV,GAAI,EAAO,SAAW,EACpB,MAAM,IAAI,EAAkB,2BAA4B,mCAAmC,EAE7F,IAAI,EAAQ,EACZ,UAAa,CACX,IAAM,EAAQ,EAAO,EAAQ,EAAO,QAAU,IAE9C,MADA,IAAS,EACF,CACT,CACF,CACA,OAAO,CACT,CAIA,SAAS,EAAQ,EAAgC,CAC/C,IAAM,EAAkB,GAAS,OAEjC,GACE,IAAoB,IAAA,KACnB,CAAC,OAAO,UAAU,CAAe,GAAK,EAAkB,GAAc,EAAkB,IAEzF,MAAM,IAAI,EACR,4BACA,oDAA2E,GAC7E,EAEF,IAAM,EAAS,GAAmB,GAE5B,EAAS,EAAY,GAAS,MAAM,EAGtC,EAAM,UAAY,IAAA,KACpB,EAAM,QAAU,EAAkB,GAIhC,EAAM,cAAgB,IAAA,KACxB,EAAM,YAAc,EAAkB,GAGxC,IAAM,EAAc,EAAa,CAAM,EACjC,EAAO,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EACnC,EAAM,SAAW,EACjB,IAAM,EAAQ,EAAM,QAAQ,SAAS,EAAE,EAQvC,OAAO,EAHY,EAAe,EADnB,EADG,EAFL,EAAc,EAAQ,CAEP,EAAI,EAAQ,EAAM,WAEK,CAAC,CAGtB,CAAC,CAAC,MAAM,EAAG,CAAM,CACjD,CAIA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAG,QAAU,GAAc,EAAG,QAAU,IAAc,EAAY,KAAK,CAAE,CAC5G,CAkCA,MAAa,EAAe,OAAO,OAAO,EAAS,CACjD,SACF,CAAC"}
@@ -0,0 +1,38 @@
1
+ //#region src/errors.d.ts
2
+ /**
3
+ * Base error for all uniku errors.
4
+ * Provides `_tag` for discriminated matching (compatible with Effect's `catchTag`)
5
+ * and `code` for machine-readable error identification.
6
+ */
7
+ declare abstract class UniqueIdError extends Error {
8
+ abstract readonly _tag: string;
9
+ abstract readonly code: string;
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * Thrown when generator arguments are invalid (bad size, alphabet, length, timestamp, version).
14
+ */
15
+ declare class InvalidInputError extends UniqueIdError {
16
+ readonly code: string;
17
+ readonly _tag = "InvalidInputError";
18
+ constructor(code: string, message: string);
19
+ }
20
+ /**
21
+ * Thrown when parsing/decoding an ID string that has invalid format or characters.
22
+ */
23
+ declare class ParseError extends UniqueIdError {
24
+ readonly code: string;
25
+ readonly _tag = "ParseError";
26
+ constructor(code: string, message: string);
27
+ }
28
+ /**
29
+ * Thrown when a byte array or buffer is too short or an offset is out of bounds.
30
+ */
31
+ declare class BufferError extends UniqueIdError {
32
+ readonly code: string;
33
+ readonly _tag = "BufferError";
34
+ constructor(code: string, message: string);
35
+ }
36
+ //#endregion
37
+ export { UniqueIdError as i, InvalidInputError as n, ParseError as r, BufferError as t };
38
+ //# sourceMappingURL=errors-Dgoyi-ci.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-Dgoyi-ci.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;AAKA;;;;uBAAsB,aAAA,SAAsB,KAAA;EAAA,kBACxB,IAAA;EAAA,kBACA,IAAA;EAElB,WAAA,CAAY,OAAA;AAAA;;AAAA;AASd;cAAa,iBAAA,SAA0B,aAAA;EAAA,SAInC,IAAA;EAAA,SAHO,IAAA;EAET,WAAA,CACE,IAAA,UACA,OAAA;AAAA;;;;cASS,UAAA,SAAmB,aAAA;EAAA,SAI5B,IAAA;EAAA,SAHO,IAAA;EAET,WAAA,CACE,IAAA,UACA,OAAA;AAAA;;;;cASS,WAAA,SAAoB,aAAA;EAAA,SAI7B,IAAA;EAAA,SAHO,IAAA;EAET,WAAA,CACE,IAAA,UACA,OAAA;AAAA"}
@@ -1,38 +1,2 @@
1
- //#region src/errors.d.ts
2
- /**
3
- * Base error for all uniku errors.
4
- * Provides `_tag` for discriminated matching (compatible with Effect's `catchTag`)
5
- * and `code` for machine-readable error identification.
6
- */
7
- declare abstract class UniqueIdError extends Error {
8
- abstract readonly _tag: string;
9
- abstract readonly code: string;
10
- constructor(message: string);
11
- }
12
- /**
13
- * Thrown when generator arguments are invalid (bad size, alphabet, length, timestamp, version).
14
- */
15
- declare class InvalidInputError extends UniqueIdError {
16
- readonly code: string;
17
- readonly _tag: "InvalidInputError";
18
- constructor(code: string, message: string);
19
- }
20
- /**
21
- * Thrown when parsing/decoding an ID string that has invalid format or characters.
22
- */
23
- declare class ParseError extends UniqueIdError {
24
- readonly code: string;
25
- readonly _tag: "ParseError";
26
- constructor(code: string, message: string);
27
- }
28
- /**
29
- * Thrown when a byte array or buffer is too short or an offset is out of bounds.
30
- */
31
- declare class BufferError extends UniqueIdError {
32
- readonly code: string;
33
- readonly _tag: "BufferError";
34
- constructor(code: string, message: string);
35
- }
36
- //#endregion
37
- export { BufferError, InvalidInputError, ParseError, UniqueIdError };
38
- //# sourceMappingURL=errors.d.mts.map
1
+ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "./errors-Dgoyi-ci.mjs";
2
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError };
package/build/errors.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class extends Error{constructor(e){super(e),this.name=this.constructor.name}},t=class extends e{_tag=`InvalidInputError`;constructor(e,t){super(t),this.code=e}},n=class extends e{_tag=`ParseError`;constructor(e,t){super(t),this.code=e}},r=class extends e{_tag=`BufferError`;constructor(e,t){super(t),this.code=e}};export{r as BufferError,t as InvalidInputError,n as ParseError,e as UniqueIdError};
1
+ var e=class extends Error{constructor(e){super(e),this.name=this.constructor.name}},t=class extends e{code;_tag=`InvalidInputError`;constructor(e,t){super(t),this.code=e}},n=class extends e{code;_tag=`ParseError`;constructor(e,t){super(t),this.code=e}},r=class extends e{code;_tag=`BufferError`;constructor(e,t){super(t),this.code=e}};export{r as BufferError,t as InvalidInputError,n as ParseError,e as UniqueIdError};
2
2
  //# sourceMappingURL=errors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":["code: string"],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base error for all uniku errors.\n * Provides `_tag` for discriminated matching (compatible with Effect's `catchTag`)\n * and `code` for machine-readable error identification.\n */\nexport abstract class UniqueIdError extends Error {\n abstract readonly _tag: string\n abstract readonly code: string\n\n constructor(message: string) {\n super(message)\n this.name = this.constructor.name\n }\n}\n\n/**\n * Thrown when generator arguments are invalid (bad size, alphabet, length, timestamp, version).\n */\nexport class InvalidInputError extends UniqueIdError {\n readonly _tag = 'InvalidInputError' as const\n\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n }\n}\n\n/**\n * Thrown when parsing/decoding an ID string that has invalid format or characters.\n */\nexport class ParseError extends UniqueIdError {\n readonly _tag = 'ParseError' as const\n\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n }\n}\n\n/**\n * Thrown when a byte array or buffer is too short or an offset is out of bounds.\n */\nexport class BufferError extends UniqueIdError {\n readonly _tag = 'BufferError' as const\n\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n }\n}\n"],"mappings":"AAKA,IAAsB,EAAtB,cAA4C,KAAM,CAIhD,YAAY,EAAiB,CAC3B,MAAM,EAAQ,CACd,KAAK,KAAO,KAAK,YAAY,OAOpB,EAAb,cAAuC,CAAc,CACnD,KAAgB,oBAEhB,YACE,EACA,EACA,CACA,MAAM,EAAQ,CAHL,KAAA,KAAA,IAUA,EAAb,cAAgC,CAAc,CAC5C,KAAgB,aAEhB,YACE,EACA,EACA,CACA,MAAM,EAAQ,CAHL,KAAA,KAAA,IAUA,EAAb,cAAiC,CAAc,CAC7C,KAAgB,cAEhB,YACE,EACA,EACA,CACA,MAAM,EAAQ,CAHL,KAAA,KAAA"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base error for all uniku errors.\n * Provides `_tag` for discriminated matching (compatible with Effect's `catchTag`)\n * and `code` for machine-readable error identification.\n */\nexport abstract class UniqueIdError extends Error {\n abstract readonly _tag: string\n abstract readonly code: string\n\n constructor(message: string) {\n super(message)\n this.name = this.constructor.name\n }\n}\n\n/**\n * Thrown when generator arguments are invalid (bad size, alphabet, length, timestamp, version).\n */\nexport class InvalidInputError extends UniqueIdError {\n readonly _tag = 'InvalidInputError' as const\n\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n }\n}\n\n/**\n * Thrown when parsing/decoding an ID string that has invalid format or characters.\n */\nexport class ParseError extends UniqueIdError {\n readonly _tag = 'ParseError' as const\n\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n }\n}\n\n/**\n * Thrown when a byte array or buffer is too short or an offset is out of bounds.\n */\nexport class BufferError extends UniqueIdError {\n readonly _tag = 'BufferError' as const\n\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n }\n}\n"],"mappings":"AAKA,IAAsB,EAAtB,cAA4C,KAAM,CAIhD,YAAY,EAAiB,CAC3B,MAAM,CAAO,EACb,KAAK,KAAO,KAAK,YAAY,IAC/B,CACF,EAKa,EAAb,cAAuC,CAAc,CAIxC,KAHX,KAAgB,oBAEhB,YACE,EACA,EACA,CACA,MAAM,CAAO,EAHJ,KAAA,KAAA,CAIX,CACF,EAKa,EAAb,cAAgC,CAAc,CAIjC,KAHX,KAAgB,aAEhB,YACE,EACA,EACA,CACA,MAAM,CAAO,EAHJ,KAAA,KAAA,CAIX,CACF,EAKa,EAAb,cAAiC,CAAc,CAIlC,KAHX,KAAgB,cAEhB,YACE,EACA,EACA,CACA,MAAM,CAAO,EAHJ,KAAA,KAAA,CAIX,CACF"}
@@ -0,0 +1,17 @@
1
+ //#region src/generators.d.ts
2
+ /**
3
+ * The canonical, ordered list of ID generators supported by uniku.
4
+ *
5
+ * This is the single source of truth for the set of supported ID generators.
6
+ * Tools that need to enumerate or validate ID generators (for example,
7
+ * `@uniku/cli`'s `--type` flag) should derive from this array rather than
8
+ * hand-copying the union.
9
+ */
10
+ declare const ID_GENERATORS: readonly ["uuid", "ulid", "typeid", "nanoid", "cuid", "ksuid", "objectid", "tsid"];
11
+ /**
12
+ * The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.
13
+ */
14
+ type IdGenerator = (typeof ID_GENERATORS)[number];
15
+ //#endregion
16
+ export { ID_GENERATORS, IdGenerator };
17
+ //# sourceMappingURL=generators.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generators.d.mts","names":[],"sources":["../src/generators.ts"],"mappings":";;AAWA;;;;AAA+F;AAK/F;;cALa,aAAA;;AAKqB;;KAAtB,WAAA,WAAsB,aAAA"}
@@ -0,0 +1,2 @@
1
+ const e=[`uuid`,`ulid`,`typeid`,`nanoid`,`cuid`,`ksuid`,`objectid`,`tsid`];export{e as ID_GENERATORS};
2
+ //# sourceMappingURL=generators.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generators.mjs","names":[],"sources":["../src/generators.ts"],"sourcesContent":["/**\n * The canonical, ordered list of ID generators supported by uniku.\n *\n * This is the single source of truth for the set of supported ID generators.\n * Tools that need to enumerate or validate ID generators (for example,\n * `@uniku/cli`'s `--type` flag) should derive from this array rather than\n * hand-copying the union.\n */\n// Plain `as const` (not `as const satisfies readonly string[]`): the `satisfies`\n// wrapper defeats isolatedDeclarations' trivial declaration emit for this export.\n// All members are string literals, so the shape guard adds nothing here anyway.\nexport const ID_GENERATORS = ['uuid', 'ulid', 'typeid', 'nanoid', 'cuid', 'ksuid', 'objectid', 'tsid'] as const\n\n/**\n * The union of ID generators supported by uniku, derived from {@link ID_GENERATORS}.\n */\nexport type IdGenerator = (typeof ID_GENERATORS)[number]\n"],"mappings":"AAWA,MAAa,EAAgB,CAAC,OAAQ,OAAQ,SAAU,SAAU,OAAQ,QAAS,WAAY,MAAM"}
@@ -1,4 +1,4 @@
1
- import { BufferError, InvalidInputError, ParseError, UniqueIdError } from "../errors.mjs";
1
+ import { i as UniqueIdError, n as InvalidInputError, r as ParseError, t as BufferError } from "../errors-Dgoyi-ci.mjs";
2
2
 
3
3
  //#region src/ksuid/ksuid.d.ts
4
4
  type KsuidOptions = {
@@ -20,10 +20,8 @@ type Ksuid = {
20
20
  toBytes(id: string): Uint8Array;
21
21
  fromBytes(bytes: Uint8Array): string;
22
22
  timestamp(id: string): number;
23
- isValid(id: unknown): id is string;
24
- /** The nil KSUID (all zeros) */
25
- NIL: string;
26
- /** The max KSUID (maximum valid value) */
23
+ isValid(id: unknown): id is string; /** The nil KSUID (all zeros) */
24
+ NIL: string; /** The max KSUID (maximum valid value) */
27
25
  MAX: string;
28
26
  };
29
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ksuid.d.mts","names":["ksuid: Ksuid"],"sources":["../../src/ksuid/ksuid.ts"],"sourcesContent":[],"mappings":";;;KAgCY,YAAA;;;AAAZ;EAaA,MAAY,CAAA,EATD,UASC;EAEI;;;;;EACH,IAAA,CAAA,EAAA,MAAA;CACU;AACJ,KALP,KAAA,GAKO;EAAA,EAAA,EAAA,MAAA;EAuInB,CAAA,aA1IgB,UA0II,GA1IS,UA0IT,CAAA,CAAA,OAAA,EA1I8B,YA0I9B,GAAA,SAAA,EAAA,GAAA,EA1I6D,IA0I7D,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA1IqF,IA0IrF;aAzIP;uBACU;mBACJ;;;;;;;;;;;;cAuINA,OAAO"}
1
+ {"version":3,"file":"ksuid.d.mts","names":[],"sources":["../../src/ksuid/ksuid.ts"],"mappings":";;;KAiCY,YAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,IAAA;AAAA;AAAA,KAGU,KAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,YAAA,cAA0B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACtG,OAAA,GAAU,YAAA,EAAc,GAAA,cAAiB,MAAA;EAC1C,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;cAkIW,KAAA,EAAO,KAAA"}
@@ -1,2 +1,2 @@
1
- import{r as e}from"../random-C_nRoaUe.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a}from"../bytes-CbbFzz-v.mjs";const o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,s=62n,c=(1n<<160n)-1n,l=new Uint8Array(65536);l.fill(255);for(let e=0;e<62;e+=1)l[o.charCodeAt(e)]=e;function u(e){if(e.length<20)throw new t(`KSUID_BYTES_TOO_SHORT`,`KSUID bytes must be at least 20 bytes, got ${e.length}`);let n=0n;for(let t=0;t<20;t+=1)n=n<<8n|BigInt(e[t]);let r=``;for(;n>0n;){let e=n%s;n/=s,r=o[Number(e)]+r}return r.padStart(27,`0`)}function d(e){if(e.length!==27)throw new r(`KSUID_INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`);let t=0n;for(let n=0;n<27;n+=1){let i=l[e.charCodeAt(n)];if(i===255)throw new r(`KSUID_INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`);t=t*s+BigInt(i)}if(t>c)throw new r(`KSUID_OVERFLOW`,`KSUID string exceeds 160-bit range`);let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const f=14e8;f+4294967295;const p=u(new Uint8Array(20).fill(255)),m=/^[0-9A-Za-z]{27}$/;function h(e,n,r,i=0){if(!r)r=new Uint8Array(20),i=0;else if(i<0||i+20>r.length)throw new t(`KSUID_BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${i}:${i+20-1} is out of buffer bounds`);a(r,i,e);for(let e=0;e<16;e+=1)r[i+4+e]=n[e];return r}function g(t,r,i=0){let a=t?.random;if(a&&a.length<16)throw new n(`KSUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`);let o,s=t?.secs;if(s!==void 0){if(s<f)throw new n(`KSUID_TIMESTAMP_TOO_LOW`,`Timestamp must be >= KSUID epoch`);if(s>5694967295)throw new n(`KSUID_TIMESTAMP_TOO_HIGH`,`Timestamp must be <= maximum KSUID timestamp`);o=s-f}else o=Math.floor(Date.now()/1e3)-f;let c=a??e();return r?(h(o,c,r,i),r):u(h(o,c))}function _(e){return d(e)}function v(e){return u(e)}function y(e){let t=d(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+f)*1e3}function b(e){return typeof e==`string`&&e.length===27&&m.test(e)&&e<=p}const x=Object.assign(g,{toBytes:_,fromBytes:v,timestamp:y,isValid:b,NIL:`000000000000000000000000000`,MAX:p});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,x as ksuid};
1
+ import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o}from"../bytes-xqWxFYsM.mjs";const s=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,c=62n,l=(1n<<160n)-1n,u=new Uint8Array(65536);u.fill(255);for(let e=0;e<62;e+=1)u[s.charCodeAt(e)]=e;function d(e){if(e.length<20)throw new n(`KSUID_BYTES_TOO_SHORT`,`KSUID bytes must be at least 20 bytes, got ${e.length}`);let t=0n;for(let n=0;n<20;n+=1)t=t<<8n|BigInt(e[n]);let r=``;for(;t>0n;){let e=t%c;t/=c,r=s[Number(e)]+r}return r.padStart(27,`0`)}function f(e){if(e.length!==27)throw new i(`KSUID_INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`);let t=0n;for(let n=0;n<27;n+=1){let r=u[e.charCodeAt(n)];if(r===255)throw new i(`KSUID_INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`);t=t*c+BigInt(r)}if(t>l)throw new i(`KSUID_OVERFLOW`,`KSUID string exceeds 160-bit range`);let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const p=14e8,m=d(new Uint8Array(20).fill(255)),h=/^[0-9A-Za-z]{27}$/;function g(e,t,n,r){o(n,r,e);for(let e=0;e<16;e+=1)n[r+4+e]=t[e]}function _(i,a,o=0){let s=i?.random;if(s&&s.length<16)throw new r(`KSUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`);let c,l=i?.secs;if(l!==void 0){if(!Number.isInteger(l)||l<p)throw new r(`KSUID_TIMESTAMP_TOO_LOW`,`Timestamp must be >= KSUID epoch`);if(l>5694967295)throw new r(`KSUID_TIMESTAMP_TOO_HIGH`,`Timestamp must be <= maximum KSUID timestamp`);c=l-p}else c=Math.floor(Date.now()/1e3)-p;let u=s??e();if(a){if(!t(a,o,20))throw new n(`KSUID_BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${o}:${o+20-1} is out of buffer bounds`);return g(c,u,a,o),a}let f=new Uint8Array(20);return g(c,u,f,0),d(f)}function v(e){return f(e)}function y(e){if(e.length!==20)throw new n(`KSUID_BYTES_INVALID_LENGTH`,`KSUID bytes must be exactly 20 bytes, got ${e.length}`);return d(e)}function b(e){let t=f(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+p)*1e3}function x(e){return typeof e==`string`&&e.length===27&&h.test(e)&&e<=m}const S=Object.assign(_,{toBytes:v,fromBytes:y,timestamp:b,isValid:x,NIL:`000000000000000000000000000`,MAX:m});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,S as ksuid};
2
2
  //# sourceMappingURL=ksuid.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN","timestamp","timestamp: number","ksuid: Ksuid"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_TOO_SHORT',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError(\n 'KSUID_INVALID_LENGTH',\n `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('KSUID_INVALID_CHAR', `Invalid KSUID character: ${str[i]}`)\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('KSUID_OVERFLOW', 'KSUID string exceeds 160-bit range')\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n * KSUID natively uses second precision.\n */\n secs?: number\n}\n\nexport type Ksuid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write KSUID bytes to a buffer.\n */\nfunction ksuidBytes(timestamp: number, payload: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {\n if (!buf) {\n buf = new Uint8Array(KSUID_BYTES)\n offset = 0\n } else if (offset < 0 || offset + KSUID_BYTES > buf.length) {\n throw new BufferError(\n 'KSUID_BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n\n return buf\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('KSUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID')\n }\n\n let timestamp: number\n const secs = options?.secs\n if (secs !== undefined) {\n if (secs < KSUID_EPOCH) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_LOW', 'Timestamp must be >= KSUID epoch')\n }\n\n if (secs > KSUID_MAX_SECS) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_HIGH', 'Timestamp must be <= maximum KSUID timestamp')\n }\n\n timestamp = secs - KSUID_EPOCH\n } else {\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n ksuidBytes(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = ksuidBytes(timestamp, payload)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
1
+ {"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_TOO_SHORT',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError(\n 'KSUID_INVALID_LENGTH',\n `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('KSUID_INVALID_CHAR', `Invalid KSUID character: ${str[i]}`)\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('KSUID_OVERFLOW', 'KSUID string exceeds 160-bit range')\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n * KSUID natively uses second precision.\n */\n secs?: number\n}\n\nexport type Ksuid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('KSUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID')\n }\n\n let timestamp: number\n const secs = options?.secs\n if (secs !== undefined) {\n if (!Number.isInteger(secs) || secs < KSUID_EPOCH) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_LOW', 'Timestamp must be >= KSUID epoch')\n }\n\n if (secs > KSUID_MAX_SECS) {\n throw new InvalidInputError('KSUID_TIMESTAMP_TOO_HIGH', 'Timestamp must be <= maximum KSUID timestamp')\n }\n\n timestamp = secs - KSUID_EPOCH\n } else {\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'KSUID_BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'KSUID_BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { InvalidInputError, UniqueIdError } from "../errors.mjs";
1
+ import { i as UniqueIdError, n as InvalidInputError } from "../errors-Dgoyi-ci.mjs";
2
2
 
3
3
  //#region src/nanoid/nanoid.d.ts
4
4
  /** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */
@@ -21,11 +21,8 @@ type NanoidOptions = {
21
21
  size?: number;
22
22
  };
23
23
  type Nanoid = {
24
- /** Generate nanoid with default settings */
25
- (): string;
26
- /** Generate nanoid with custom size */
27
- (size: number): string;
28
- /** Generate nanoid with options */
24
+ /** Generate nanoid with default settings */(): string; /** Generate nanoid with custom size */
25
+ (size: number): string; /** Generate nanoid with options */
29
26
  (options: NanoidOptions): string;
30
27
  /**
31
28
  * Validate a nanoid string against the default URL-safe alphabet.
@@ -1 +1 @@
1
- {"version":3,"file":"nanoid.d.mts","names":["nanoid: Nanoid"],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":[],"mappings":";;;;cAGa,YAAA;KAmCD,aAAA;EAnCZ;AAmCA;AAkBA;AAoMA;;WAhNW;;;;;;;;;;;KAYC,MAAA;;;;;;YAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8LCA,QAAQ"}
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"}