uniku 0.0.4 → 0.0.6

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
@@ -1,51 +1,107 @@
1
1
  # uniku
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/uniku.svg)](https://www.npmjs.com/package/uniku)
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)
4
6
  [![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/)
5
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/uniku)](https://bundlephobia.com/package/uniku)
7
9
 
8
- Minimal, tree-shakeable UUID and ULID utilities for every JavaScript runtime.
10
+ One library. Every ID format. Every runtime. Zero dependencies.
9
11
 
10
- > *uniku* means "unique" in Maltese.
12
+ > **uniku** */uˈniːku/* — Maltese for "unique"
11
13
 
12
- ## Why uniku?
14
+ ```ts
15
+ import { uuidv7 } from 'uniku/uuid/v7'
13
16
 
14
- - **ESM-only** - No CommonJS or legacy support.
15
- - **Universal runtime support** - Works everywhere: Node.js, Cloudflare Workers, Vercel Edge, Deno, browsers. Uses the WebCrypto API (`globalThis.crypto`), not `node:crypto`.
16
- - **Truly tree-shakeable** - Separate entry points mean you only bundle what you import. No barrel exports that defeat dead-code elimination.
17
- - **Byte-level access** - Convert between UUID strings and `Uint8Array` with built-in `toBytes()` and `fromBytes()` helpers.
18
- - **Modern standards** - Includes UUID v7 (RFC 9562) for time-ordered, sortable identifiers with monotonic sequencing.
19
- - **Lightweight** - Designed for use in performance-sensitive contexts like ORMs.
17
+ const id = uuidv7()
18
+ // => "018e5e5c-7c8a-7000-8000-000000000000"
20
19
 
21
- ## Supported ID generation algorithms
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
+ ```
22
24
 
23
- - UUID v4 ([RFC 4122](../../rfcs/rfc4122.txt))
24
- - UUID v7 ([RFC 9562](../../rfcs/rfc9562.txt), time-ordered, monotonic sequencing)
25
- - ULID ([spec](../../rfcs/ulid.txt), time-ordered, monotonic sequencing)
26
- - CUID2 ([spec](../../rfcs/cuid2.txt), secure, non-time-ordered)
27
- - Nanoid ([spec](../../rfcs/nanoid.txt), URL-friendly, custom alphabet support)
25
+ ## At a Glance
26
+
27
+ | | uniku | uuid | nanoid | ulid | cuid2 |
28
+ |--------------------|:-----:|:----:|:------:|:----:|:-----:|
29
+ | UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ |
30
+ | UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ |
31
+ | ULID | ✅ | ❌ | ❌ | ✅ | ❌ |
32
+ | CUID2 | ✅ | ❌ | ❌ | ❌ | ✅ |
33
+ | Nanoid | ✅ | ❌ | ✅ | ❌ | ❌ |
34
+ | Tree-shakeable | ✅ | ❌ | ✅ | ✅ | ✅ |
35
+ | ESM-only | ✅ | ❌ | ✅ | ✅ | ✅ |
36
+ | Edge/Workers | ✅ | ⚠️ | ✅ | ⚠️ | ✅ |
37
+ | Byte ↔ String | ✅ | ✅ | - | ✅ | - |
38
+
39
+ > **Note**: Byte ↔ String conversion doesn't make sense for nanoid and cuid2, since they are string-native formats with no canonical binary representation.
40
+
41
+ ### Works Everywhere
42
+
43
+ <p align="center">
44
+ <img src="https://img.shields.io/badge/Node.js-339933?logo=nodedotjs&logoColor=white" alt="Node.js">
45
+ <img src="https://img.shields.io/badge/Deno-000000?logo=deno&logoColor=white" alt="Deno">
46
+ <img src="https://img.shields.io/badge/Bun-000000?logo=bun&logoColor=white" alt="Bun">
47
+ <img src="https://img.shields.io/badge/Cloudflare_Workers-F38020?logo=cloudflare&logoColor=white" alt="Cloudflare Workers">
48
+ <img src="https://img.shields.io/badge/Vercel_Edge-000000?logo=vercel&logoColor=white" alt="Vercel Edge">
49
+ <img src="https://img.shields.io/badge/Browsers-4285F4?logo=googlechrome&logoColor=white" alt="Browsers">
50
+ </p>
51
+
52
+ Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
53
+
54
+ ## Performance
55
+
56
+ | Generator | uniku vs npm |
57
+ |-----------|-------------:|
58
+ | ULID | **77× faster** |
59
+ | CUID2 | **8× faster** |
60
+ | UUID v4 | **1.5× faster** |
61
+ | Nanoid | **1.1× faster** |
62
+ | UUID v7 | npm 1.6× faster* |
63
+
64
+ <sub>*UUID v7 tradeoff: uniku prioritizes strict monotonic sequencing for database use cases.</sub>
65
+
66
+ ## Which ID Should I Use?
67
+
68
+ | Use Case | Recommended | Why |
69
+ |----------|-------------|-----|
70
+ | Database primary keys | **UUID v7** or **ULID** | Time-ordered for index efficiency |
71
+ | URL shorteners | **Nanoid** | Compact, URL-safe characters |
72
+ | Prevent enumeration | **CUID2** | Non-sequential, secure |
73
+ | Maximum compatibility | **UUID v4** | Universal standard |
74
+ | Distributed systems | **ULID** | Sortable + high entropy |
28
75
 
29
76
  ## Installation
30
77
 
31
- With pnpm:
32
-
33
78
  ```bash
79
+ # pnpm (recommended)
34
80
  pnpm add uniku
35
- ```
36
81
 
37
- With Bun:
82
+ # bun
83
+ bun add uniku
38
84
 
39
- ```bash
40
- bun install uniku
41
- ```
85
+ # npm
86
+ npm install uniku
42
87
 
43
- With npm:
88
+ # yarn
89
+ yarn add uniku
44
90
 
45
- ```bash
46
- npm install uniku
91
+ # deno
92
+ deno install npm:uniku
47
93
  ```
48
94
 
95
+ ### Bundle Impact
96
+
97
+ | Import | Minified + gzipped |
98
+ |--------|-------------------:|
99
+ | `uniku/uuid/v4` | ~930 B |
100
+ | `uniku/uuid/v7` | ~1.1 KB |
101
+ | `uniku/ulid` | ~1.5 KB |
102
+ | `uniku/cuid2` | ~1.1 KB* |
103
+ | `uniku/nanoid` | ~967 B |
104
+
49
105
  ## Quick Start
50
106
 
51
107
  ### UUID v4 (random)
@@ -100,7 +156,6 @@ const shortId = cuid2({ length: 10 })
100
156
  // => "tz4a98xxat"
101
157
 
102
158
  // Validation (type guard)
103
- const maybeId: unknown = getUserInput()
104
159
  if (cuid2.isValid(maybeId)) {
105
160
  console.log(maybeId) // TypeScript knows this is a string
106
161
  }
@@ -121,10 +176,36 @@ const shortId = nanoid(10)
121
176
  // Custom alphabet and size via options
122
177
  const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })
123
178
  // => "4f90d13a42bc"
179
+ ```
180
+
181
+ ## Migrating to uniku
182
+
183
+ ### From `uuid`
124
184
 
125
- // Validation (default alphabet only)
126
- nanoid.isValid(id)
127
- // => true
185
+ ```diff
186
+ - import { v4 as uuidv4 } from 'uuid'
187
+ + import { uuidv4 } from 'uniku/uuid/v4'
188
+ ```
189
+
190
+ ### From `nanoid`
191
+
192
+ ```diff
193
+ - import { nanoid } from 'nanoid'
194
+ + import { nanoid } from 'uniku/nanoid'
195
+ ```
196
+
197
+ ### From `ulid`
198
+
199
+ ```diff
200
+ - import { ulid } from 'ulid'
201
+ + import { ulid } from 'uniku/ulid'
202
+ ```
203
+
204
+ ### From `@paralleldrive/cuid2`
205
+
206
+ ```diff
207
+ - import { createId } from '@paralleldrive/cuid2'
208
+ + import { cuid2 } from 'uniku/cuid2'
128
209
  ```
129
210
 
130
211
  ## API Reference
@@ -134,47 +215,32 @@ nanoid.isValid(id)
134
215
  ```ts
135
216
  uuidv4(options?: Version4Options): string
136
217
  uuidv4(options: Version4Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
137
-
138
218
  uuidv4.toBytes(id: string): Uint8Array
139
219
  uuidv4.fromBytes(bytes: Uint8Array): string
140
220
  ```
141
221
 
142
- **Options:**
143
- - `random?: Uint8Array` - 16 bytes of random data
144
-
145
222
  ### `uuidv7` (from `uniku/uuid/v7`)
146
223
 
147
224
  ```ts
148
225
  uuidv7(options?: Version7Options): string
149
226
  uuidv7(options: Version7Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
150
-
151
227
  uuidv7.toBytes(id: string): Uint8Array
152
228
  uuidv7.fromBytes(bytes: Uint8Array): string
153
229
  uuidv7.timestamp(id: string): number
154
230
  uuidv7.isValid(id: string): boolean
155
231
  ```
156
232
 
157
- **Options:**
158
- - `msecs?: number` - Timestamp in milliseconds (defaults to `Date.now()`)
159
- - `seq?: number` - Sequence number for monotonicity
160
- - `random?: Uint8Array` - 16 bytes of random data
161
-
162
233
  ### `ulid` (from `uniku/ulid`)
163
234
 
164
235
  ```ts
165
236
  ulid(options?: UlidOptions): string
166
237
  ulid(options: UlidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
167
-
168
238
  ulid.toBytes(id: string): Uint8Array
169
239
  ulid.fromBytes(bytes: Uint8Array): string
170
240
  ulid.timestamp(id: string): number
171
241
  ulid.isValid(id: string): boolean
172
242
  ```
173
243
 
174
- **Options:**
175
- - `msecs?: number` - Timestamp in milliseconds (defaults to `Date.now()`)
176
- - `random?: Uint8Array` - 16 bytes of random data (only first 10 bytes used)
177
-
178
244
  ### `cuid2` (from `uniku/cuid2`)
179
245
 
180
246
  ```ts
@@ -182,12 +248,6 @@ cuid2(options?: Cuid2Options): string
182
248
  cuid2.isValid(id: unknown): id is string
183
249
  ```
184
250
 
185
- **Options:**
186
- - `length?: number` - ID length (2-32, default 24)
187
- - `random?: Uint8Array` - Custom random bytes for testing
188
-
189
- Note: Unlike UUID and ULID, CUID2 does not provide `toBytes`/`fromBytes` because it is a string-native format with no canonical binary representation.
190
-
191
251
  ### `nanoid` (from `uniku/nanoid`)
192
252
 
193
253
  ```ts
@@ -197,17 +257,10 @@ nanoid(options: NanoidOptions): string
197
257
  nanoid.isValid(id: unknown): id is string
198
258
  ```
199
259
 
200
- **Options:**
201
- - `size?: number` - ID length (default 21, max 2048)
202
- - `alphabet?: string` - Custom alphabet (2-256 printable ASCII characters)
203
- - `random?: Uint8Array` - Custom random bytes for testing
204
-
205
- Note: `isValid()` only validates IDs against the default URL-safe alphabet (`A-Za-z0-9_-`). IDs generated with custom alphabets cannot be validated with this method.
206
-
207
260
  ## Documentation
208
261
 
209
262
  For advanced usage, examples, and contributing guidelines, see the [full documentation on GitHub](https://github.com/jkomyno/uniku).
210
263
 
211
264
  ## License
212
265
 
213
- MIT - see [LICENSE](https://github.com/jkomyno/uniku/blob/main/LICENSE)
266
+ MIT — see [LICENSE](https://github.com/jkomyno/uniku/blob/main/LICENSE)
@@ -1 +1 @@
1
- {"version":3,"file":"cuid2.d.mts","names":["cuid2: Cuid2"],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":[],"mappings":";KAEY,YAAA;EAAZ;AAeA;AA2MA;;;;;;;;;WA9MW;;KAGC,KAAA;aACC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0MAA,OAAO"}
1
+ {"version":3,"file":"cuid2.d.mts","names":["cuid2: Cuid2"],"sources":["../../src/cuid2/cuid2.ts"],"sourcesContent":[],"mappings":";KAGY,YAAA;EAAZ;AAeA;AAyMA;;;;;;;;;WA5MW;;KAGC,KAAA;aACC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAwMAA,OAAO"}
@@ -1,134 +1,2 @@
1
- import { sha3_512 } from "@noble/hashes/sha3.js";
2
-
3
- //#region src/cuid2/cuid2.ts
4
- const DEFAULT_LENGTH = 24;
5
- const MAX_LENGTH = 32;
6
- const MIN_LENGTH = 2;
7
- const INITIAL_COUNT_MAX = 476782367;
8
- const CUID2_REGEX = /^[a-z][0-9a-z]+$/;
9
- const ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
10
- const LETTER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
11
- const textEncoder = new TextEncoder();
12
- const RANDOM_BUFFER_SIZE = 256;
13
- const randomBuffer = new Uint8Array(RANDOM_BUFFER_SIZE);
14
- let randomBufferOffset = RANDOM_BUFFER_SIZE;
15
- /**
16
- * Module-level state for counter and fingerprint.
17
- * Counter is initialized lazily on first call to prevent unnecessary crypto operations.
18
- * Fingerprint is also generated lazily on first call.
19
- */
20
- const state = {
21
- counter: void 0,
22
- fingerprint: void 0
23
- };
24
- /**
25
- * Initialize counter using crypto for consistency with other entropy sources.
26
- */
27
- function initializeCounter() {
28
- const buffer = new Uint8Array(4);
29
- globalThis.crypto.getRandomValues(buffer);
30
- return ((buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]) >>> 0) % (INITIAL_COUNT_MAX + 1);
31
- }
32
- function bufToBigInt(buf) {
33
- let value = 0n;
34
- for (const byte of buf) value = value * 256n + BigInt(byte);
35
- return value;
36
- }
37
- function bigIntToBase36(value) {
38
- if (value === 0n) return "0";
39
- const chars = [];
40
- while (value > 0n) {
41
- chars.push(ALPHABET[Number(value % 36n)]);
42
- value = value / 36n;
43
- }
44
- return chars.reverse().join("");
45
- }
46
- function randomLetter(random) {
47
- return LETTER_ALPHABET[Math.floor(random() * 26)];
48
- }
49
- function createEntropy(length, random) {
50
- const chars = new Array(length);
51
- for (let i = 0; i < length; i++) chars[i] = ALPHABET[Math.floor(random() * 36)];
52
- return chars.join("");
53
- }
54
- function hash(input) {
55
- return sha3_512(textEncoder.encode(input));
56
- }
57
- const BIG_LENGTH = 32;
58
- function createFingerprint() {
59
- const random = getCryptoRandom;
60
- return bigIntToBase36(bufToBigInt(hash(Object.keys(globalThis).toString() + createEntropy(BIG_LENGTH, random)))).slice(1, BIG_LENGTH + 1);
61
- }
62
- /**
63
- * Get a random number in [0, 1) using batched CSPRNG.
64
- * Uses a pre-allocated buffer to reduce crypto.getRandomValues calls.
65
- */
66
- function getCryptoRandom() {
67
- if (randomBufferOffset >= RANDOM_BUFFER_SIZE) {
68
- globalThis.crypto.getRandomValues(randomBuffer);
69
- randomBufferOffset = 0;
70
- }
71
- const value = (randomBuffer[randomBufferOffset] * 16777216 + randomBuffer[randomBufferOffset + 1] * 65536 + randomBuffer[randomBufferOffset + 2] * 256 + randomBuffer[randomBufferOffset + 3]) / 4294967296;
72
- randomBufferOffset += 4;
73
- return value;
74
- }
75
- function getRandomFn(random) {
76
- if (random) {
77
- if (random.length === 0) throw new Error("Random byte array cannot be empty");
78
- let index = 0;
79
- return () => {
80
- const value = random[index % random.length] / 256;
81
- index += 1;
82
- return value;
83
- };
84
- }
85
- return getCryptoRandom;
86
- }
87
- function cuid2Fn(options) {
88
- const length = options?.length ?? DEFAULT_LENGTH;
89
- if (length < MIN_LENGTH || length > MAX_LENGTH) throw new RangeError(`CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`);
90
- const random = getRandomFn(options?.random);
91
- if (state.counter === void 0) state.counter = initializeCounter();
92
- if (state.fingerprint === void 0) state.fingerprint = createFingerprint();
93
- const firstLetter = randomLetter(random);
94
- const time = Date.now().toString(36);
95
- state.counter += 1;
96
- const count = state.counter.toString(36);
97
- return firstLetter + bigIntToBase36(bufToBigInt(hash(time + createEntropy(length, random) + count + state.fingerprint))).slice(1, length);
98
- }
99
- function isValid(id) {
100
- return typeof id === "string" && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id);
101
- }
102
- /**
103
- * Generate a CUID2 string.
104
- *
105
- * CUID2 is a secure, collision-resistant identifier that hashes multiple
106
- * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),
107
- * CUID2 prevents enumeration attacks by making IDs non-predictable.
108
- *
109
- * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native
110
- * format with no canonical binary representation (unlike UUID's 16-byte format).
111
- *
112
- * @example
113
- * ```ts
114
- * import { cuid2 } from 'uniku/cuid2'
115
- *
116
- * const id = cuid2()
117
- * // => "pfh0haxfpzowht3oi213cqos"
118
- *
119
- * // Custom length
120
- * const shortId = cuid2({ length: 10 })
121
- * // => "tz4a98xxat"
122
- *
123
- * // Validation (type guard)
124
- * const maybeId: unknown = getUserInput()
125
- * if (cuid2.isValid(maybeId)) {
126
- * console.log(maybeId.length) // TypeScript knows maybeId is string
127
- * }
128
- * ```
129
- */
130
- const cuid2 = Object.assign(cuid2Fn, { isValid });
131
-
132
- //#endregion
133
- export { cuid2 };
1
+ import{sha3_512 as e}from"@noble/hashes/sha3.js";function t(e){let t=4+e*128,n,r=!1;if(typeof SharedArrayBuffer<`u`)try{n=new SharedArrayBuffer(t),r=!0}catch{n=new ArrayBuffer(t)}else n=new ArrayBuffer(t);let i=new Int32Array(n,0,1),a=new Uint8Array(n,4);return crypto.getRandomValues(a),{buffer:n,bytes:a,offset:i,useAtomics:r}}function n(e,t){if(e.useAtomics){let r=Atomics.add(e.offset,0,t);return r+t<=e.bytes.length?r:Atomics.compareExchange(e.offset,0,r+t,t)===r+t?(crypto.getRandomValues(e.bytes),0):n(e,t)}else{let n=e.offset[0];return n+t>e.bytes.length&&(crypto.getRandomValues(e.bytes),n=0),e.offset[0]=n+t,n}}function r(e,t){let r=n(e,t);return e.bytes.subarray(r,r+t)}const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder;let s;function c(){return s||=t(4),s}const l={counter:void 0,fingerprint:void 0};function u(){let e=r(c(),4);return((e[0]<<24|e[1]<<16|e[2]<<8|e[3])>>>0)%476782368}function d(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function f(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function p(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function m(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function h(t){return e(o.encode(t))}function g(){let e=_;return f(d(h(Object.keys(globalThis).toString()+m(32,e)))).slice(1,33)}function _(){let e=c(),t=n(e,4),r=e.bytes;return(r[t]*16777216+r[t+1]*65536+r[t+2]*256+r[t+3])/4294967296}function v(e){if(e){if(e.length===0)throw Error(`Random byte array cannot be empty`);let t=0;return()=>{let n=e[t%e.length]/256;return t+=1,n}}return _}function y(e){let t=e?.length??24;if(t<2||t>32)throw RangeError(`CUID2 length must be between 2 and 32. Received: ${t}`);let n=v(e?.random);l.counter===void 0&&(l.counter=u()),l.fingerprint===void 0&&(l.fingerprint=g());let r=p(n),i=Date.now().toString(36);l.counter+=1;let a=l.counter.toString(36);return r+f(d(h(i+m(t,n)+a+l.fingerprint))).slice(1,t)}function b(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const x=Object.assign(y,{isValid:b});export{x as cuid2};
134
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'\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// Pre-allocated buffer for batched random byte generation (64 random numbers per batch)\nconst RANDOM_BUFFER_SIZE = 256\nconst randomBuffer = new Uint8Array(RANDOM_BUFFER_SIZE)\nlet randomBufferOffset = RANDOM_BUFFER_SIZE // Force initial fill\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 const buffer = new Uint8Array(4)\n globalThis.crypto.getRandomValues(buffer)\n return (((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]) >>> 0) % (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 batched CSPRNG.\n * Uses a pre-allocated buffer to reduce crypto.getRandomValues calls.\n */\nfunction getCryptoRandom(): number {\n if (randomBufferOffset >= RANDOM_BUFFER_SIZE) {\n globalThis.crypto.getRandomValues(randomBuffer)\n randomBufferOffset = 0\n }\n const value =\n (randomBuffer[randomBufferOffset] * 0x1000000 +\n randomBuffer[randomBufferOffset + 1] * 0x10000 +\n randomBuffer[randomBufferOffset + 2] * 0x100 +\n randomBuffer[randomBufferOffset + 3]) /\n 0x100000000\n randomBufferOffset += 4\n return value\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new Error('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 RangeError(`CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`)\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"],"mappings":"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"}
1
+ {"version":3,"file":"cuid2.mjs","names":["buffer: SharedArrayBuffer | ArrayBuffer","pool","pool: RandomPool | undefined","state: { counter: number | undefined; fingerprint: string | undefined }","chars: string[]","cuid2: Cuid2"],"sources":["../../src/common/random-pool.ts","../../src/cuid2/cuid2.ts"],"sourcesContent":["/**\n * Thread-safe random byte pool utilities using Atomics (when SharedArrayBuffer is available).\n * Falls back to regular pooling in environments without SharedArrayBuffer.\n *\n * This module provides STATELESS utilities - each consumer creates and manages its own pool.\n * Pass the pool state to each function (C-style) for explicit, predictable behavior.\n *\n * @example\n * ```ts\n * // Each module creates its own pool\n * let pool: RandomPool | undefined\n *\n * function getBytes(count: number): number {\n * if (!pool) pool = createPool(count)\n * return getPooledBytes(pool, count)\n * }\n * ```\n */\n\nconst POOL_SIZE_MULTIPLIER = 128\nconst POOL_HEADER_SIZE = 4 // 4 bytes for Int32 offset counter\n\n/**\n * Pool state - passed to all pool functions.\n * Each module creates and owns its own pool instance.\n */\nexport type RandomPool = {\n buffer: SharedArrayBuffer | ArrayBuffer\n bytes: Uint8Array\n offset: Int32Array\n useAtomics: boolean\n}\n\n/**\n * Create a new random pool. Uses SharedArrayBuffer + Atomics when available\n * for thread-safety in Web Worker scenarios.\n *\n * @param minSize - Minimum bytes needed per request (pool = minSize * 128)\n */\nexport function createPool(minSize: number): RandomPool {\n const poolSize = minSize * POOL_SIZE_MULTIPLIER\n const totalSize = POOL_HEADER_SIZE + poolSize\n\n let buffer: SharedArrayBuffer | ArrayBuffer\n let useAtomics = false\n\n // Try SharedArrayBuffer first (thread-safe with Atomics)\n if (typeof SharedArrayBuffer !== 'undefined') {\n try {\n buffer = new SharedArrayBuffer(totalSize)\n useAtomics = true\n } catch {\n // SharedArrayBuffer may be disabled (security restrictions)\n buffer = new ArrayBuffer(totalSize)\n }\n } else {\n buffer = new ArrayBuffer(totalSize)\n }\n\n // First 4 bytes = atomic offset counter, rest = random bytes\n const offset = new Int32Array(buffer, 0, 1)\n const bytes = new Uint8Array(buffer, POOL_HEADER_SIZE)\n crypto.getRandomValues(bytes)\n\n return { buffer, bytes, offset, useAtomics }\n}\n\n/**\n * Get pooled random bytes. Thread-safe when SharedArrayBuffer is available.\n * Returns the starting index in the pool for the requested bytes.\n *\n * @param pool - The pool state (from createPool)\n * @param count - Number of bytes needed\n * @returns Starting offset in pool.bytes\n *\n * @example\n * ```ts\n * const offset = getPooledBytes(pool, 21)\n * for (let i = offset; i < offset + 21; i++) {\n * // Use pool.bytes[i]\n * }\n * ```\n */\nexport function getPooledBytes(pool: RandomPool, count: number): number {\n if (pool.useAtomics) {\n // Thread-safe path using Atomics\n const startOffset = Atomics.add(pool.offset, 0, count)\n\n if (startOffset + count <= pool.bytes.length) {\n return startOffset\n }\n\n // Pool exhausted - need to refill\n // Use compareExchange to ensure only one thread refills\n const resetResult = Atomics.compareExchange(pool.offset, 0, startOffset + count, count)\n if (resetResult === startOffset + count) {\n // We won the race - refill the pool\n crypto.getRandomValues(pool.bytes)\n return 0\n }\n\n // Another thread is refilling or already refilled - retry\n return getPooledBytes(pool, count)\n } else {\n // Single-threaded path\n let currentOffset = pool.offset[0]\n\n if (currentOffset + count > pool.bytes.length) {\n crypto.getRandomValues(pool.bytes)\n currentOffset = 0\n }\n\n pool.offset[0] = currentOffset + count\n return currentOffset\n }\n}\n\n/**\n * Get a Uint8Array slice of random bytes from the pool.\n *\n * Note: Returns a subarray VIEW into the pool. If you need the bytes to persist\n * beyond the current synchronous operation, use getRandomBytesCopy() instead.\n *\n * @param pool - The pool state\n * @param count - Number of bytes needed\n */\nexport function getRandomBytes(pool: RandomPool, count: number): Uint8Array {\n const offset = getPooledBytes(pool, count)\n return pool.bytes.subarray(offset, offset + count)\n}\n\n/**\n * Get a COPY of random bytes from the pool.\n * Use this when bytes need to persist (e.g., returned to caller who may use them later).\n *\n * @param pool - The pool state\n * @param count - Number of bytes needed\n */\nexport function getRandomBytesCopy(pool: RandomPool, count: number): Uint8Array {\n const offset = getPooledBytes(pool, count)\n return pool.bytes.slice(offset, offset + count)\n}\n\n/**\n * Fill the provided buffer with random bytes from the pool.\n *\n * @param pool - The pool state\n * @param out - Buffer to fill\n */\nexport function fillRandomBytes(pool: RandomPool, out: Uint8Array): void {\n const offset = getPooledBytes(pool, out.length)\n out.set(pool.bytes.subarray(offset, offset + out.length))\n}\n","import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { createPool, getPooledBytes, getRandomBytes, type RandomPool } from '../common/random-pool'\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// CUID2's own pool - lazily initialized on first use\n// Uses 4 bytes per random() call\nlet pool: RandomPool | undefined\n\nfunction ensurePool(): RandomPool {\n if (!pool) pool = createPool(4)\n return pool\n}\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 const buffer = getRandomBytes(ensurePool(), 4)\n return (((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]) >>> 0) % (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 * Thread-safe when SharedArrayBuffer is available.\n */\nfunction getCryptoRandom(): number {\n const p = ensurePool()\n const offset = getPooledBytes(p, 4)\n const bytes = p.bytes\n const value =\n (bytes[offset] * 0x1000000 + bytes[offset + 1] * 0x10000 + bytes[offset + 2] * 0x100 + bytes[offset + 3]) /\n 0x100000000\n return value\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new Error('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 RangeError(`CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`)\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"],"mappings":"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"}
@@ -4,7 +4,8 @@ declare const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx
4
4
  type NanoidOptions = {
5
5
  /**
6
6
  * Random bytes for deterministic output (testing).
7
- * Must provide enough bytes for rejection sampling (~size * 2).
7
+ * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `size` bytes needed.
8
+ * For other alphabets: ~size * 2 bytes needed (rejection sampling).
8
9
  */
9
10
  random?: Uint8Array;
10
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"nanoid.d.mts","names":["nanoid: Nanoid"],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":[],"mappings":";;AACa,cAAA,YAAA,GAAA,kEAAA;AAMD,KAAA,aAAA,GAAA;EAiBZ;AAsHA;;;WAlIW;;;;;;;;;;;KAYC,MAAA;;;;;;YAMA;;;;;;;;;;cAgHCA,QAAQ"}
1
+ {"version":3,"file":"nanoid.d.mts","names":["nanoid: Nanoid"],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":[],"mappings":";;AACa,cAAA,YAAA,GAAA,kEAAA;AAwBD,KAAA,aAAA,GAAA;EAkBZ;AAmJA;;;;WA/JW;;;;;;;;;;;KAYC,MAAA;;;;;;YAMA;;;;;;;;;;cA6ICA,QAAQ"}
@@ -1,73 +1,2 @@
1
- //#region src/nanoid/nanoid.ts
2
- /** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */
3
- const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
4
- const DEFAULT_SIZE = 21;
5
- const MAX_SIZE = 2048;
6
- const NANOID_REGEX = /^[A-Za-z0-9_-]+$/;
7
- /**
8
- * Validate alphabet: 2-256 printable ASCII chars, no duplicates
9
- */
10
- function validateAlphabet(alphabet) {
11
- if (alphabet.length < 2) throw new Error("Alphabet must contain at least 2 characters");
12
- if (alphabet.length > 256) throw new Error("Alphabet must not exceed 256 characters");
13
- const seen = /* @__PURE__ */ new Set();
14
- for (const char of alphabet) {
15
- const code = char.charCodeAt(0);
16
- if (code < 32 || code > 126) throw new Error("Alphabet must contain only printable ASCII characters (32-126)");
17
- if (seen.has(char)) throw new Error(`Duplicate character in alphabet: "${char}"`);
18
- seen.add(char);
19
- }
20
- }
21
- /**
22
- * Validate size parameter
23
- */
24
- function validateSize(size) {
25
- if (!Number.isInteger(size) || size < 0) throw new RangeError("Size must be a non-negative integer");
26
- if (size > MAX_SIZE) throw new RangeError(`Size must not exceed ${MAX_SIZE}`);
27
- }
28
- function nanoidFn(sizeOrOptions) {
29
- let size = DEFAULT_SIZE;
30
- let alphabet = URL_ALPHABET;
31
- let randomBytes;
32
- if (typeof sizeOrOptions === "number") size = sizeOrOptions;
33
- else if (sizeOrOptions) {
34
- size = sizeOrOptions.size ?? DEFAULT_SIZE;
35
- alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET;
36
- randomBytes = sizeOrOptions.random;
37
- if (sizeOrOptions.alphabet) validateAlphabet(alphabet);
38
- }
39
- validateSize(size);
40
- if (size === 0) return "";
41
- const mask = (2 << 31 - Math.clz32(alphabet.length - 1 | 1)) - 1;
42
- const step = Math.ceil(1.6 * mask * size / alphabet.length);
43
- let id = "";
44
- let randomOffset = 0;
45
- while (id.length < size) {
46
- let bytes;
47
- if (randomBytes) {
48
- if (randomBytes.length - randomOffset < step) throw new Error(`Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`);
49
- bytes = randomBytes.subarray(randomOffset, randomOffset + step);
50
- randomOffset += step;
51
- } else bytes = globalThis.crypto.getRandomValues(new Uint8Array(step));
52
- for (let i = 0; i < bytes.length && id.length < size; i++) {
53
- const index = bytes[i] & mask;
54
- if (index < alphabet.length) id += alphabet[index];
55
- }
56
- }
57
- return id;
58
- }
59
- /**
60
- * Validate a nanoid string against the default URL-safe alphabet.
61
- * Note: Does not validate IDs generated with custom alphabets.
62
- */
63
- function isValid(id) {
64
- return typeof id === "string" && id.length > 0 && NANOID_REGEX.test(id);
65
- }
66
- /**
67
- * Generate a URL-friendly unique string ID.
68
- */
69
- const nanoid = Object.assign(nanoidFn, { isValid });
70
-
71
- //#endregion
72
- export { URL_ALPHABET, nanoid };
1
+ const e=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,t=2048,n=/^[A-Za-z0-9_-]+$/;let r,i=0;function a(e){let t=e*128;!r||r.length<t?(r=new Uint8Array(t),crypto.getRandomValues(r),i=0):i+e>r.length&&(crypto.getRandomValues(r),i=0),i+=e}function o(e){if(e.length<2)throw Error(`Alphabet must contain at least 2 characters`);if(e.length>256)throw Error(`Alphabet must not exceed 256 characters`);let t=new Set;for(let n of e){let e=n.charCodeAt(0);if(e<32||e>126)throw Error(`Alphabet must contain only printable ASCII characters (32-126)`);if(t.has(n))throw Error(`Duplicate character in alphabet: "${n}"`);t.add(n)}}function s(e){if(!Number.isInteger(e)||e<0)throw RangeError(`Size must be a non-negative integer`);if(e>t)throw RangeError(`Size must not exceed ${t}`)}function c(t){if(t===void 0){a(21);let t=``;for(let n=i-21;n<i;n++)t+=e[r[n]&63];return t}let n=21,c=e,l;if(typeof t==`number`?n=t:(n=t.size??21,c=t.alphabet??e,l=t.random,t.alphabet!==void 0&&o(c)),s(n),n===0)return``;let u=c.length;if(!(u&u-1)){let e=u-1;if(l&&l.length<n)throw Error(`Insufficient random bytes: need ${n}, have ${l.length}`);let t=l?.subarray(0,n)??globalThis.crypto.getRandomValues(new Uint8Array(n)),r=``;for(let i=0;i<n;i++)r+=c[t[i]&e];return r}let d=(2<<31-Math.clz32(u-1|1))-1,f=Math.ceil(1.6*d*n/u),p=``,m=0;for(;p.length<n;){let e;if(l){if(l.length-m<f)throw Error(`Insufficient random bytes: need at least ${f} more, have ${l.length-m}`);e=l.subarray(m,m+f),m+=f}else e=globalThis.crypto.getRandomValues(new Uint8Array(f));for(let t=0;t<e.length&&p.length<n;t++){let n=e[t]&d;n<u&&(p+=c[n])}}return p}function l(e){return typeof e==`string`&&e.length>0&&n.test(e)}const u=Object.assign(c,{isValid:l});export{e as URL_ALPHABET,u as nanoid};
73
2
  //# sourceMappingURL=nanoid.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"nanoid.mjs","names":["randomBytes: Uint8Array | undefined","bytes: Uint8Array","nanoid: Nanoid"],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["/** 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\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * Must provide enough bytes for rejection sampling (~size * 2).\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 nanoid with default settings */\n (): string\n /** Generate nanoid with custom size */\n (size: number): string\n /** Generate nanoid with options */\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 Error('Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new Error('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 Error('Alphabet must contain only printable ASCII characters (32-126)')\n }\n if (seen.has(char)) {\n throw new Error(`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 RangeError('Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new RangeError(`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 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 if (sizeOrOptions) {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabet.length)\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 Error(\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 < alphabet.length) {\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 */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n"],"mappings":";;AACA,MAAa,eAAe;AAE5B,MAAM,eAAe;AACrB,MAAM,WAAW;AACjB,MAAM,eAAe;;;;AAoCrB,SAAS,iBAAiB,UAAwB;AAChD,KAAI,SAAS,SAAS,EACpB,OAAM,IAAI,MAAM,8CAA8C;AAEhE,KAAI,SAAS,SAAS,IACpB,OAAM,IAAI,MAAM,0CAA0C;CAE5D,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,UAAU;EAC3B,MAAM,OAAO,KAAK,WAAW,EAAE;AAC/B,MAAI,OAAO,MAAM,OAAO,IACtB,OAAM,IAAI,MAAM,iEAAiE;AAEnF,MAAI,KAAK,IAAI,KAAK,CAChB,OAAM,IAAI,MAAM,qCAAqC,KAAK,GAAG;AAE/D,OAAK,IAAI,KAAK;;;;;;AAOlB,SAAS,aAAa,MAAoB;AACxC,KAAI,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,EACpC,OAAM,IAAI,WAAW,sCAAsC;AAE7D,KAAI,OAAO,SACT,OAAM,IAAI,WAAW,wBAAwB,WAAW;;AAQ5D,SAAS,SAAS,eAAgD;CAChE,IAAI,OAAO;CACX,IAAI,WAAW;CACf,IAAIA;AAEJ,KAAI,OAAO,kBAAkB,SAC3B,QAAO;UACE,eAAe;AACxB,SAAO,cAAc,QAAQ;AAC7B,aAAW,cAAc,YAAY;AACrC,gBAAc,cAAc;AAC5B,MAAI,cAAc,SAChB,kBAAiB,SAAS;;AAI9B,cAAa,KAAK;AAElB,KAAI,SAAS,EAAG,QAAO;CAGvB,MAAM,QAAQ,KAAM,KAAK,KAAK,MAAO,SAAS,SAAS,IAAK,EAAE,IAAK;CAEnE,MAAM,OAAO,KAAK,KAAM,MAAM,OAAO,OAAQ,SAAS,OAAO;CAE7D,IAAI,KAAK;CACT,IAAI,eAAe;AAEnB,QAAO,GAAG,SAAS,MAAM;EACvB,IAAIC;AACJ,MAAI,aAAa;AACf,OAAI,YAAY,SAAS,eAAe,KACtC,OAAM,IAAI,MACR,4CAA4C,KAAK,cAAc,YAAY,SAAS,eACrF;AAEH,WAAQ,YAAY,SAAS,cAAc,eAAe,KAAK;AAC/D,mBAAgB;QAEhB,SAAQ,WAAW,OAAO,gBAAgB,IAAI,WAAW,KAAK,CAAC;AAGjE,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,UAAU,GAAG,SAAS,MAAM,KAAK;GACzD,MAAM,QAAQ,MAAM,KAAK;AACzB,OAAI,QAAQ,SAAS,OACnB,OAAM,SAAS;;;AAMrB,QAAO;;;;;;AAOT,SAAS,QAAQ,IAA2B;AAC1C,QAAO,OAAO,OAAO,YAAY,GAAG,SAAS,KAAK,aAAa,KAAK,GAAG;;;;;AAMzE,MAAaC,SAAiB,OAAO,OAAO,UAAU,EACpD,SACD,CAAC"}
1
+ {"version":3,"file":"nanoid.mjs","names":["pool: Uint8Array | undefined","id","randomBytes: Uint8Array | undefined","mask","bytes: Uint8Array","nanoid: Nanoid"],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["/** 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// Simple random pool for ultra-fast path (npm nanoid style - no thread-safety overhead)\nconst POOL_SIZE_MULTIPLIER = 128\nlet pool: Uint8Array | undefined\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = bytes * POOL_SIZE_MULTIPLIER\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\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 nanoid with default settings */\n (): string\n /** Generate nanoid with custom size */\n (size: number): string\n /** Generate nanoid with options */\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 Error('Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new Error('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 Error('Alphabet must contain only printable ASCII characters (32-126)')\n }\n if (seen.has(char)) {\n throw new Error(`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 RangeError('Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new RangeError(`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 fillPool(DEFAULT_SIZE)\n let id = ''\n for (let i = poolOffset - DEFAULT_SIZE; i < poolOffset; i++) {\n id += URL_ALPHABET[pool![i] & 63]\n }\n return id\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 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 Error(`Insufficient random bytes: need ${size}, have ${randomBytes.length}`)\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 Error(\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 */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n"],"mappings":"AACA,MAAa,EAAe,mEAGtB,EAAW,KACX,EAAe,mBAIrB,IAAIA,EACA,EAAa,EAEjB,SAAS,EAAS,EAAqB,CACrC,IAAM,EAAO,EAAQ,IACjB,CAAC,GAAQ,EAAK,OAAS,GACzB,EAAO,IAAI,WAAW,EAAK,CAC3B,OAAO,gBAAgB,EAAK,CAC5B,EAAa,GACJ,EAAa,EAAQ,EAAK,SACnC,OAAO,gBAAgB,EAAK,CAC5B,EAAa,GAEf,GAAc,EAsChB,SAAS,EAAiB,EAAwB,CAChD,GAAI,EAAS,OAAS,EACpB,MAAU,MAAM,8CAA8C,CAEhE,GAAI,EAAS,OAAS,IACpB,MAAU,MAAM,0CAA0C,CAE5D,IAAM,EAAO,IAAI,IACjB,IAAK,IAAM,KAAQ,EAAU,CAC3B,IAAM,EAAO,EAAK,WAAW,EAAE,CAC/B,GAAI,EAAO,IAAM,EAAO,IACtB,MAAU,MAAM,iEAAiE,CAEnF,GAAI,EAAK,IAAI,EAAK,CAChB,MAAU,MAAM,qCAAqC,EAAK,GAAG,CAE/D,EAAK,IAAI,EAAK,EAOlB,SAAS,EAAa,EAAoB,CACxC,GAAI,CAAC,OAAO,UAAU,EAAK,EAAI,EAAO,EACpC,MAAU,WAAW,sCAAsC,CAE7D,GAAI,EAAO,EACT,MAAU,WAAW,wBAAwB,IAAW,CAQ5D,SAAS,EAAS,EAAgD,CAGhE,GAAI,IAAkB,IAAA,GAAW,CAC/B,EAAS,GAAa,CACtB,IAAIC,EAAK,GACT,IAAK,IAAI,EAAI,EAAa,GAAc,EAAI,EAAY,IACtD,GAAM,EAAa,EAAM,GAAK,IAEhC,OAAOA,EAGT,IAAI,EAAO,GACP,EAAW,EACXC,EAeJ,GAbI,OAAO,GAAkB,SAC3B,EAAO,GAEP,EAAO,EAAc,MAAQ,GAC7B,EAAW,EAAc,UAAY,EACrC,EAAc,EAAc,OACxB,EAAc,WAAa,IAAA,IAC7B,EAAiB,EAAS,EAI9B,EAAa,EAAK,CAEd,IAAS,EAAG,MAAO,GAEvB,IAAM,EAAc,EAAS,OAI7B,GAAA,EAAK,EAAe,EAAc,GAAW,CAC3C,IAAMC,EAAO,EAAc,EAC3B,GAAI,GAAe,EAAY,OAAS,EACtC,MAAU,MAAM,mCAAmC,EAAK,SAAS,EAAY,SAAS,CAExF,IAAM,EAAQ,GAAa,SAAS,EAAG,EAAK,EAAI,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAK,CAAC,CACnGF,EAAK,GACT,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,IACxB,GAAM,EAAS,EAAM,GAAKE,GAE5B,OAAOF,EAKT,IAAM,GAAQ,GAAM,GAAK,KAAK,MAAO,EAAc,EAAK,EAAE,EAAK,EAEzD,EAAO,KAAK,KAAM,IAAM,EAAO,EAAQ,EAAY,CAErD,EAAK,GACL,EAAe,EAEnB,KAAO,EAAG,OAAS,GAAM,CACvB,IAAIG,EACJ,GAAI,EAAa,CACf,GAAI,EAAY,OAAS,EAAe,EACtC,MAAU,MACR,4CAA4C,EAAK,cAAc,EAAY,OAAS,IACrF,CAEH,EAAQ,EAAY,SAAS,EAAc,EAAe,EAAK,CAC/D,GAAgB,OAEhB,EAAQ,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAK,CAAC,CAGjE,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,QAAU,EAAG,OAAS,EAAM,IAAK,CACzD,IAAM,EAAQ,EAAM,GAAK,EACrB,EAAQ,IACV,GAAM,EAAS,KAMrB,OAAO,EAOT,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAG,OAAS,GAAK,EAAa,KAAK,EAAG,CAMzE,MAAaC,EAAiB,OAAO,OAAO,EAAU,CACpD,UACD,CAAC"}
@@ -0,0 +1,2 @@
1
+ const e=new Uint8Array(256);let t=256;function n(){t>240&&(crypto.getRandomValues(e),t=0);let n=t;return t+=16,e.subarray(n,t)}export{n as t};
2
+ //# sourceMappingURL=random-pEE8mU9d.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random-pEE8mU9d.mjs","names":[],"sources":["../src/uuid/common/random.ts"],"sourcesContent":["// Simple, fast random byte pool for UUID generation.\n// Uses plain JavaScript for maximum performance - no Atomics overhead.\n// Thread safety is not needed for typical UUID use cases.\n\nconst POOL_SIZE = 256 // uuid npm uses 256 bytes\nconst rnds8Pool = new Uint8Array(POOL_SIZE)\nlet poolPtr = POOL_SIZE // Start exhausted to trigger first fill\n\n/**\n * Generate 16 bytes of cryptographically strong random data.\n * Uses a pre-filled pool to minimize crypto API calls.\n */\nexport function rng(): Uint8Array {\n if (poolPtr > POOL_SIZE - 16) {\n crypto.getRandomValues(rnds8Pool)\n poolPtr = 0\n }\n // Return a view - consumed synchronously within the same v7 call\n const start = poolPtr\n poolPtr += 16\n return rnds8Pool.subarray(start, poolPtr)\n}\n\n/**\n * Fill the provided buffer with random bytes from the pool.\n */\nexport function randomBytes(out: Uint8Array): void {\n const count = out.length\n if (poolPtr > POOL_SIZE - count) {\n crypto.getRandomValues(rnds8Pool)\n poolPtr = 0\n }\n out.set(rnds8Pool.subarray(poolPtr, poolPtr + count))\n poolPtr += count\n}\n"],"mappings":"AAIA,MACM,EAAY,IAAI,WAAW,IAAU,CAC3C,IAAI,EAAU,IAMd,SAAgB,GAAkB,CAC5B,EAAU,MACZ,OAAO,gBAAgB,EAAU,CACjC,EAAU,GAGZ,IAAM,EAAQ,EAEd,MADA,IAAW,GACJ,EAAU,SAAS,EAAO,EAAQ"}