uniku 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -560
- package/build/nanoid/nanoid.d.mts.map +1 -1
- package/build/nanoid/nanoid.mjs +1 -1
- package/build/nanoid/nanoid.mjs.map +1 -1
- package/build/xid/xid.d.mts.map +1 -1
- package/build/xid/xid.mjs +1 -1
- package/build/xid/xid.mjs.map +1 -1
- package/package.json +4 -3
- package/src/nanoid/nanoid.ts +16 -14
- package/src/xid/base32hex.ts +38 -22
- package/src/xid/xid.ts +18 -4
package/README.md
CHANGED
|
@@ -2,618 +2,110 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/uniku)
|
|
4
4
|
[](https://npmjs.com/package/uniku)
|
|
5
|
-
[](https://bundlephobia.com/package/uniku)
|
|
6
5
|
[](https://github.com/jkomyno/uniku/actions/workflows/ci.yaml)
|
|
7
|
-
[](https://jkomyno.github.io/uniku/)
|
|
8
7
|
[](https://opensource.org/licenses/MIT)
|
|
9
8
|
|
|
10
|
-
One library. Every ID format. Every
|
|
9
|
+
One library. Every ID format. Every modern JavaScript runtime.
|
|
11
10
|
|
|
12
11
|
> **uniku** */uˈniːku/* — Maltese for "unique"
|
|
13
12
|
|
|
13
|
+
[Documentation](https://jkomyno.github.io/uniku/) · [Getting started](https://jkomyno.github.io/uniku/docs/getting-started/) · [Choosing an ID](https://jkomyno.github.io/uniku/docs/guides/choosing-an-id/) · [API reference](https://jkomyno.github.io/uniku/docs/reference/entry-points/) · [CLI](https://jkomyno.github.io/uniku/docs/cli/)
|
|
14
|
+
|
|
14
15
|
```ts
|
|
15
16
|
import { uuidv7 } from 'uniku/uuid/v7'
|
|
16
17
|
|
|
17
18
|
const id = uuidv7()
|
|
18
19
|
// => "018e5e5c-7c8a-7000-8000-000000000000"
|
|
19
|
-
|
|
20
|
-
// Time-ordered: IDs sort by creation time
|
|
21
|
-
const [first, second, third] = [uuidv7(), uuidv7(), uuidv7()]
|
|
22
|
-
console.log(first < second && second < third) // true
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## At a Glance
|
|
26
|
-
|
|
27
|
-
| | uniku | [uuid](https://github.com/uuidjs/uuid) | [typeid-js](https://github.com/jetify-com/typeid-js) | [nanoid](https://github.com/ai/nanoid) | [ulid](https://github.com/ulid/javascript) | [cuid2](https://github.com/paralleldrive/cuid2) | [ksuid](https://github.com/owpz/ksuid) | [bson](https://github.com/mongodb/js-bson) | [tsid-ts](https://github.com/yubinTW/tsid-ts) |
|
|
28
|
-
|--------------------|:-----:|:----:|:---------:|:------:|:----:|:-----:|:-----:|:-----:|:-----:|
|
|
29
|
-
| UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
30
|
-
| UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
31
|
-
| TypeID | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
32
|
-
| Nanoid | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
33
|
-
| ULID | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
34
|
-
| CUID2 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
35
|
-
| KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
36
|
-
| ObjectID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
|
37
|
-
| TSID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
38
|
-
| XID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
39
|
-
| Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
40
|
-
| ESM-only | ✅ | ✅¹ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
41
|
-
| Edge/Workers | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | ⚠️ |
|
|
42
|
-
| Byte ↔ String | ✅ | ✅ | ✅ | - | ⚠️² | - | ✅ | ✅ | ✅ |
|
|
43
|
-
|
|
44
|
-
> **Notes:**
|
|
45
|
-
> - Byte ↔ String conversion doesn't make sense for nanoid and cuid2, since they are string-native formats with no canonical binary representation.
|
|
46
|
-
> - ¹ `uuid@13` is ESM-only; earlier versions support CommonJS.
|
|
47
|
-
> - ² `ulid` only provides timestamp encoding/decoding, not full binary serialization.
|
|
48
|
-
|
|
49
|
-
### Works Everywhere
|
|
50
|
-
|
|
51
|
-
<p align="center">
|
|
52
|
-
<img src="https://img.shields.io/badge/Node.js-339933?logo=nodedotjs&logoColor=white" alt="Node.js">
|
|
53
|
-
<img src="https://img.shields.io/badge/Deno-000000?logo=deno&logoColor=white" alt="Deno">
|
|
54
|
-
<img src="https://img.shields.io/badge/Bun-000000?logo=bun&logoColor=white" alt="Bun">
|
|
55
|
-
<img src="https://img.shields.io/badge/Cloudflare_Workers-F38020?logo=cloudflare&logoColor=white" alt="Cloudflare Workers">
|
|
56
|
-
<img src="https://img.shields.io/badge/Vercel_Edge-000000?logo=vercel&logoColor=white" alt="Vercel Edge">
|
|
57
|
-
<img src="https://img.shields.io/badge/Browsers-4285F4?logo=googlechrome&logoColor=white" alt="Browsers">
|
|
58
|
-
</p>
|
|
59
|
-
|
|
60
|
-
Uses `globalThis.crypto` (Web Crypto API) — no Node.js-specific APIs.
|
|
61
|
-
|
|
62
|
-
## Performance
|
|
63
|
-
|
|
64
|
-
| Generator | uniku vs npm |
|
|
65
|
-
|-----------|-------------:|
|
|
66
|
-
| ULID | **85× faster** |
|
|
67
|
-
| CUID2 | **8× faster** |
|
|
68
|
-
| KSUID | **1.5× faster** |
|
|
69
|
-
| ObjectID | **1.1× faster** |
|
|
70
|
-
| TSID | **1.7× faster** |
|
|
71
|
-
| XID | See the current benchmark summary |
|
|
72
|
-
| UUID v7 | **1.1× faster** |
|
|
73
|
-
| Nanoid | **~comparable speed** |
|
|
74
|
-
| Nanoid (10 chars) | npm is 1.1× faster |
|
|
75
|
-
| TypeID | **2.6× faster** |
|
|
76
|
-
| UUID v4 | npm is 1.1× faster |
|
|
77
|
-
|
|
78
|
-
## Which ID Should I Use?
|
|
79
|
-
|
|
80
|
-
### Quick Recommendations
|
|
81
|
-
|
|
82
|
-
| Use Case | Recommended | Why |
|
|
83
|
-
|----------|-------------|-----|
|
|
84
|
-
| Database primary keys | **UUID v7** or **ULID** | Time-ordered for index efficiency |
|
|
85
|
-
| API/domain identifiers | **TypeID** | UUID v7 with readable type prefixes like `user_...` |
|
|
86
|
-
| URL shorteners | **Nanoid** | Compact, URL-safe characters |
|
|
87
|
-
| Prevent enumeration | **CUID2** | Non-sequential, secure |
|
|
88
|
-
| Maximum compatibility | **UUID v4** | Universal standard |
|
|
89
|
-
| Distributed systems needing sortable, URL-safe IDs | **ULID** | Millisecond ordering + 80-bit entropy |
|
|
90
|
-
| Very high-volume distributed systems | **KSUID** | Time-ordered with 128-bit entropy |
|
|
91
|
-
| MongoDB `_id` compatibility | **ObjectID** | Drop-in match for MongoDB's native document ID format |
|
|
92
|
-
| Native 64-bit sortable integer ID | **TSID** | Fits a database `BIGINT` primary key, no UUID-sized overhead |
|
|
93
|
-
| Go rs/xid compatibility | **XID** | Compact, time-ordered 12-byte identifier |
|
|
94
|
-
|
|
95
|
-
### Detailed Guide
|
|
96
|
-
|
|
97
|
-
**UUID v4** — Use when you need maximum compatibility with existing systems.
|
|
98
|
-
- 36 characters, 122-bit entropy
|
|
99
|
-
- Universally supported by databases (native `UUID` type)
|
|
100
|
-
- Not time-ordered (random distribution)
|
|
101
|
-
- Best for: Legacy systems, cross-platform compatibility
|
|
102
|
-
|
|
103
|
-
**UUID v7** — Use for database primary keys when you need RFC compliance.
|
|
104
|
-
- 36 characters, time-ordered with millisecond precision
|
|
105
|
-
- Native database support as `UUID` type
|
|
106
|
-
- Strictly monotonic within same millisecond
|
|
107
|
-
- Best for: PostgreSQL/MySQL primary keys, API identifiers
|
|
108
|
-
|
|
109
|
-
**ULID** — Use for database primary keys when you want URL-safe IDs.
|
|
110
|
-
- 26 characters, time-ordered with millisecond precision
|
|
111
|
-
- URL-safe (no hyphens), case-insensitive
|
|
112
|
-
- 80-bit random entropy per millisecond
|
|
113
|
-
- Best for: APIs, URLs, distributed systems needing sortability
|
|
114
|
-
|
|
115
|
-
**TypeID** — Use when API IDs should expose their entity type.
|
|
116
|
-
- Prefix plus 26-character UUID v7 suffix, e.g. `user_01h2xcejqtf2nbrexx3vqjhp41`
|
|
117
|
-
- Empty prefix support for canonical 26-character TypeIDs
|
|
118
|
-
- Time-ordered through UUID v7, with lowercase snake_case type prefixes
|
|
119
|
-
- Best for: Public API identifiers, logs, debugging, domain-specific IDs
|
|
120
|
-
|
|
121
|
-
**KSUID** — Use when you need highest collision resistance.
|
|
122
|
-
- 27 characters, time-ordered with second precision
|
|
123
|
-
- URL-safe, 128-bit random entropy (highest of all formats)
|
|
124
|
-
- Best for: Very high-volume distributed systems, event sourcing
|
|
125
|
-
|
|
126
|
-
**ObjectID** — Use when you need drop-in compatibility with MongoDB.
|
|
127
|
-
- 24-character lowercase hex, time-ordered with second precision
|
|
128
|
-
- 4-byte timestamp + 5-byte per-process random + 3-byte always-incrementing counter
|
|
129
|
-
- Counter never resets on a new timestamp (unlike ULID/UUIDv7/KSUID sequences)
|
|
130
|
-
- Best for: MongoDB `_id` fields, systems already speaking the ObjectID wire format
|
|
131
|
-
|
|
132
|
-
**TSID** — Use when you need a native 64-bit sortable integer ID, e.g. a database `BIGINT` primary key.
|
|
133
|
-
- 64-bit Snowflake-style value, time-ordered with millisecond precision
|
|
134
|
-
- Returns a `bigint` by default (the only uniku generator that isn't string-primary); `toString`/`fromString` convert to/from the 13-character canonical string
|
|
135
|
-
- 42-bit timestamp + 10-bit node + 12-bit per-millisecond counter, with a persistent lazily-random node ID
|
|
136
|
-
- Best for: relational database `BIGINT` primary keys, high-throughput distributed ID generation without central coordination
|
|
137
|
-
|
|
138
|
-
**Nanoid** — Use for short, URL-friendly identifiers.
|
|
139
|
-
- 21 characters (configurable), 126-bit entropy
|
|
140
|
-
- Customizable alphabet and length
|
|
141
|
-
- Not time-ordered
|
|
142
|
-
- Best for: URL shorteners, session tokens, invite codes
|
|
143
|
-
|
|
144
|
-
**CUID2** — Use when ID unpredictability matters.
|
|
145
|
-
- 24 characters (configurable), hash-based
|
|
146
|
-
- Non-sequential, prevents enumeration attacks
|
|
147
|
-
- Not time-ordered
|
|
148
|
-
- Best for: User-facing IDs where security matters
|
|
149
|
-
|
|
150
|
-
### Binary Serialization: When to Use `toBytes()`
|
|
151
|
-
|
|
152
|
-
Formats with binary representations (UUID, ULID, KSUID, ObjectID, TSID) support `toBytes()` and `fromBytes()` for efficient storage:
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
// Store as BINARY(16) instead of VARCHAR(36)
|
|
156
|
-
const bytes = uuidv7.toBytes(id) // 16 bytes
|
|
157
|
-
db.insert({ id: bytes })
|
|
158
|
-
|
|
159
|
-
// Retrieve and convert back
|
|
160
|
-
const id = uuidv7.fromBytes(row.id)
|
|
161
20
|
```
|
|
162
21
|
|
|
163
|
-
|
|
164
|
-
- **Up to 50% smaller**: E.g., 16 bytes vs 36 characters for UUID
|
|
165
|
-
- **Faster indexing**: Binary comparison is faster than string comparison
|
|
166
|
-
- **Reduced I/O**: Less data transferred between app and database
|
|
167
|
-
|
|
168
|
-
**When to use binary:**
|
|
169
|
-
- High-volume tables (millions of rows)
|
|
170
|
-
- Performance-critical queries on ID columns
|
|
171
|
-
- Storage cost is a concern
|
|
172
|
-
|
|
173
|
-
**Exception — `tsid`:** `toBytes()`/`fromBytes()` operate on `bigint`, not `string`, since `bigint` is TSID's primary type (see [`tsid`](#tsid-from-unikutsid) in the API Reference below). Every other generator's `toBytes()`/`fromBytes()` operate on its primary `string` type.
|
|
174
|
-
|
|
175
|
-
**When to keep strings:**
|
|
176
|
-
- Debugging/logging convenience
|
|
177
|
-
- API responses (humans read them)
|
|
178
|
-
- Small tables where savings are negligible
|
|
22
|
+
## Why `uniku`?
|
|
179
23
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
| KSUID | 27 chars | 20 bytes | 26% |
|
|
186
|
-
| ObjectID | 24 chars | 12 bytes | 50% |
|
|
187
|
-
| TSID | 13 chars* | 8 bytes | 38% |
|
|
188
|
-
| Nanoid | 21 chars | N/A | - |
|
|
189
|
-
| CUID2 | 24 chars | N/A | - |
|
|
24
|
+
- **One focused package** — UUID v4/v7, ULID, TypeID, CUID v2, Nanoid, KSUID, ObjectID, XID, and TSID.
|
|
25
|
+
- **Portable by default** — Runs on Node.js, Bun, Deno, browsers, edge runtimes, and Cloudflare Workers using `globalThis.crypto`.
|
|
26
|
+
- **Tree-shakeable entry points** — Import only the generator you use; the package root is intentionally not exported.
|
|
27
|
+
- **Useful at system boundaries** — Validate unknown input, convert canonical binary formats to bytes, or write directly into a caller-owned buffer.
|
|
28
|
+
- **Small dependency surface** — CUID v2 is the only generator with a runtime dependency (`@noble/hashes`).
|
|
190
29
|
|
|
191
|
-
|
|
192
|
-
>
|
|
193
|
-
> \* TSID's primary type is `bigint`, not a string; the 13-character figure is its canonical `toString()` form, shown here only for a size comparison against the other formats' native strings.
|
|
30
|
+
## Install
|
|
194
31
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
# pnpm (recommended)
|
|
199
|
-
pnpm add uniku
|
|
200
|
-
|
|
201
|
-
# bun
|
|
202
|
-
bun add uniku
|
|
203
|
-
|
|
204
|
-
# npm
|
|
32
|
+
```sh
|
|
205
33
|
npm install uniku
|
|
206
|
-
|
|
207
|
-
# yarn
|
|
208
|
-
yarn add uniku
|
|
209
|
-
|
|
210
|
-
# deno
|
|
211
|
-
deno install npm:uniku
|
|
212
34
|
```
|
|
213
35
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
| Import | Minified + gzipped |
|
|
217
|
-
|--------|-------------------:|
|
|
218
|
-
| `uniku/uuid/v4` | ~1.1 KB |
|
|
219
|
-
| `uniku/uuid/v7` | ~1.4 KB |
|
|
220
|
-
| `uniku/ulid` | ~1.8 KB |
|
|
221
|
-
| `uniku/typeid` | ~1.6 KB |
|
|
222
|
-
| `uniku/cuid/v2` | ~992 B* |
|
|
223
|
-
| `uniku/cuid2` | ~1007 B* |
|
|
224
|
-
| `uniku/nanoid` | ~1.1 KB |
|
|
225
|
-
| `uniku/ksuid` | ~1.3 KB |
|
|
226
|
-
| `uniku/objectid` | ~1.3 KB |
|
|
227
|
-
| `uniku/tsid` | ~1.4 KB |
|
|
228
|
-
| `uniku/xid` | ~1.7 KB |
|
|
229
|
-
| `uniku/generators` | ~98 B |
|
|
230
|
-
|
|
231
|
-
* The CUID2 entry point imports SHA3-512 from `@noble/hashes`; this table's entry-point size excludes that external dependency.
|
|
232
|
-
|
|
233
|
-
## Quick Start
|
|
234
|
-
|
|
235
|
-
### UUID v4 (random)
|
|
36
|
+
The [getting-started guide](https://jkomyno.github.io/uniku/docs/getting-started/) also covers pnpm, Bun, and Deno.
|
|
236
37
|
|
|
237
|
-
|
|
238
|
-
import { uuidv4 } from 'uniku/uuid/v4'
|
|
239
|
-
|
|
240
|
-
const id = uuidv4()
|
|
241
|
-
// => "550e8400-e29b-41d4-a716-446655440000"
|
|
242
|
-
|
|
243
|
-
// Convert to/from bytes
|
|
244
|
-
const bytes = uuidv4.toBytes(id)
|
|
245
|
-
const str = uuidv4.fromBytes(bytes)
|
|
246
|
-
```
|
|
38
|
+
## Quick start
|
|
247
39
|
|
|
248
|
-
|
|
40
|
+
Every generator has its own entry point and keeps related operations on the generator function:
|
|
249
41
|
|
|
250
42
|
```ts
|
|
251
43
|
import { uuidv7 } from 'uniku/uuid/v7'
|
|
252
44
|
|
|
253
45
|
const id = uuidv7()
|
|
254
|
-
|
|
46
|
+
const bytes = uuidv7.toBytes(id)
|
|
47
|
+
const restored = uuidv7.fromBytes(bytes)
|
|
255
48
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
49
|
+
if (uuidv7.isValid(restored)) {
|
|
50
|
+
console.log(uuidv7.timestamp(restored))
|
|
51
|
+
}
|
|
259
52
|
```
|
|
260
53
|
|
|
261
|
-
|
|
54
|
+
There are no barrel exports:
|
|
262
55
|
|
|
263
56
|
```ts
|
|
57
|
+
import { uuidv4 } from 'uniku/uuid/v4'
|
|
264
58
|
import { ulid } from 'uniku/ulid'
|
|
265
|
-
|
|
266
|
-
const id = ulid()
|
|
267
|
-
// => "01HW9T2W9W9YJ3JZ1H4P4M2T8Q"
|
|
268
|
-
|
|
269
|
-
// Convert to/from bytes
|
|
270
|
-
const bytes = ulid.toBytes(id)
|
|
271
|
-
const str = ulid.fromBytes(bytes)
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### TypeID (prefixed UUID v7)
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
59
|
import { typeid } from 'uniku/typeid'
|
|
278
|
-
|
|
279
|
-
const id = typeid('user')
|
|
280
|
-
// => "user_01h2xcejqtf2nbrexx3vqjhp41"
|
|
281
|
-
|
|
282
|
-
const uuid = typeid.toUuid(id)
|
|
283
|
-
const restored = typeid.fromUuid('user', uuid)
|
|
284
|
-
|
|
285
|
-
const canonical = typeid('')
|
|
286
|
-
// => "01h2xcejqtf2nbrexx3vqjhp41"
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### CUID2 (secure, non-time-ordered)
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
60
|
import { cuidv2 } from 'uniku/cuid/v2'
|
|
293
|
-
|
|
294
|
-
const id = cuidv2()
|
|
295
|
-
// => "pfh0haxfpzowht3oi213cqos"
|
|
296
|
-
|
|
297
|
-
// Custom length
|
|
298
|
-
const shortId = cuidv2({ length: 10 })
|
|
299
|
-
// => "tz4a98xxat"
|
|
300
|
-
|
|
301
|
-
// Validation (type guard)
|
|
302
|
-
if (cuidv2.isValid(maybeId)) {
|
|
303
|
-
console.log(maybeId) // TypeScript knows this is a string
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
> `uniku/cuid/v2` is the canonical entry point. The older `uniku/cuid2` import
|
|
308
|
-
> still works and re-exports the same generator, but is now deprecated — prefer
|
|
309
|
-
> `import { cuidv2 } from 'uniku/cuid/v2'`.
|
|
310
|
-
|
|
311
|
-
### Nanoid (URL-friendly, custom alphabet)
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
61
|
import { nanoid } from 'uniku/nanoid'
|
|
315
|
-
|
|
316
|
-
const id = nanoid()
|
|
317
|
-
// => "V1StGXR8_Z5jdHi6B-myT"
|
|
318
|
-
|
|
319
|
-
// Custom size
|
|
320
|
-
const shortId = nanoid(10)
|
|
321
|
-
// => "IRFa-VaY2b"
|
|
322
|
-
|
|
323
|
-
// Custom alphabet and size via options
|
|
324
|
-
const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })
|
|
325
|
-
// => "4f90d13a42bc"
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### KSUID (time-ordered, high entropy)
|
|
329
|
-
|
|
330
|
-
```ts
|
|
331
62
|
import { ksuid } from 'uniku/ksuid'
|
|
332
|
-
|
|
333
|
-
const id = ksuid()
|
|
334
|
-
// => "2QnJjKLvpSfpZqGiPPxVwWLMy2p"
|
|
335
|
-
|
|
336
|
-
// Convert to/from bytes
|
|
337
|
-
const bytes = ksuid.toBytes(id)
|
|
338
|
-
const str = ksuid.fromBytes(bytes)
|
|
339
|
-
|
|
340
|
-
// Extract timestamp (milliseconds)
|
|
341
|
-
const ts = ksuid.timestamp(id)
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### ObjectID (time-ordered, MongoDB-compatible)
|
|
345
|
-
|
|
346
|
-
```ts
|
|
347
63
|
import { objectid } from 'uniku/objectid'
|
|
348
|
-
|
|
349
|
-
const id = objectid()
|
|
350
|
-
// => "667c3f2a1e2b3c4d5e6f7081"
|
|
351
|
-
|
|
352
|
-
// Convert to/from bytes
|
|
353
|
-
const bytes = objectid.toBytes(id)
|
|
354
|
-
const str = objectid.fromBytes(bytes)
|
|
355
|
-
|
|
356
|
-
// Extract timestamp (milliseconds)
|
|
357
|
-
const ts = objectid.timestamp(id)
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### TSID (time-ordered, 64-bit integer)
|
|
361
|
-
|
|
362
|
-
```ts
|
|
363
|
-
import { tsid } from 'uniku/tsid'
|
|
364
|
-
|
|
365
|
-
// Generate a TSID bigint (the primary type, not a string)
|
|
366
|
-
const id = tsid()
|
|
367
|
-
// => 862301223059968074n
|
|
368
|
-
|
|
369
|
-
// Convert to/from the canonical 13-character string
|
|
370
|
-
const str = tsid.toString(id)
|
|
371
|
-
// => "0QXW2CK4XZM2A"
|
|
372
|
-
tsid.fromString(str) === id // true
|
|
373
|
-
|
|
374
|
-
// Extract timestamp (milliseconds, full precision)
|
|
375
|
-
const ts = tsid.timestamp(id)
|
|
376
|
-
|
|
377
|
-
// Convert to/from 8 bytes
|
|
378
|
-
const bytes = tsid.toBytes(id)
|
|
379
|
-
const restored = tsid.fromBytes(bytes)
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### XID (time-ordered, rs/xid-compatible)
|
|
383
|
-
|
|
384
|
-
```ts
|
|
385
64
|
import { xid } from 'uniku/xid'
|
|
386
|
-
|
|
387
|
-
const id = xid()
|
|
388
|
-
// => "9m4e2mr0ui3e8a215n4g"
|
|
389
|
-
|
|
390
|
-
const bytes = xid.toBytes(id)
|
|
391
|
-
const restored = xid.fromBytes(bytes)
|
|
392
|
-
const timestamp = xid.timestamp(id)
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
XID caches a random identity per runtime and uses a shared, always-incrementing counter. Supply `machineId`, `processId`, `secs`, and `counter` for deterministic output.
|
|
396
|
-
|
|
397
|
-
## Migrating to uniku
|
|
398
|
-
|
|
399
|
-
### From `uuid`
|
|
400
|
-
|
|
401
|
-
```diff
|
|
402
|
-
- import { v4 as uuidv4 } from 'uuid'
|
|
403
|
-
+ import { uuidv4 } from 'uniku/uuid/v4'
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### From `nanoid`
|
|
407
|
-
|
|
408
|
-
```diff
|
|
409
|
-
- import { nanoid } from 'nanoid'
|
|
410
|
-
+ import { nanoid } from 'uniku/nanoid'
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### From `ulid`
|
|
414
|
-
|
|
415
|
-
```diff
|
|
416
|
-
- import { ulid } from 'ulid'
|
|
417
|
-
+ import { ulid } from 'uniku/ulid'
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### From `@paralleldrive/cuid2`
|
|
421
|
-
|
|
422
|
-
```diff
|
|
423
|
-
- import { createId } from '@paralleldrive/cuid2'
|
|
424
|
-
+ import { cuid2 } from 'uniku/cuid2'
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### From `@owpz/ksuid`
|
|
428
|
-
|
|
429
|
-
```diff
|
|
430
|
-
- import { KSUID } from '@owpz/ksuid'
|
|
431
|
-
+ import { ksuid } from 'uniku/ksuid'
|
|
432
|
-
|
|
433
|
-
- const id = KSUID.random().toString()
|
|
434
|
-
+ const id = ksuid()
|
|
435
|
-
|
|
436
|
-
- const parsed = KSUID.parse(str)
|
|
437
|
-
- const timestamp = parsed.timestamp
|
|
438
|
-
+ const timestamp = ksuid.timestamp(str)
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### From `bson`
|
|
442
|
-
|
|
443
|
-
```diff
|
|
444
|
-
- import { ObjectId } from 'bson'
|
|
445
|
-
+ import { objectid } from 'uniku/objectid'
|
|
446
|
-
|
|
447
|
-
- const id = new ObjectId().toHexString()
|
|
448
|
-
+ const id = objectid()
|
|
449
|
-
|
|
450
|
-
- const timestamp = new ObjectId(str).getTimestamp().getTime()
|
|
451
|
-
+ const timestamp = objectid.timestamp(str)
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
## API Reference
|
|
455
|
-
|
|
456
|
-
### `uuidv4` (from `uniku/uuid/v4`)
|
|
457
|
-
|
|
458
|
-
```ts
|
|
459
|
-
uuidv4(options?: UuidV4Options): string
|
|
460
|
-
uuidv4(options: UuidV4Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
461
|
-
uuidv4.toBytes(id: string): Uint8Array
|
|
462
|
-
uuidv4.fromBytes(bytes: Uint8Array): string
|
|
463
|
-
uuidv4.isValid(id: unknown): id is string
|
|
464
|
-
uuidv4.NIL // "00000000-0000-0000-0000-000000000000"
|
|
465
|
-
uuidv4.MAX // "ffffffff-ffff-ffff-ffff-ffffffffffff"
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
### `uuidv7` (from `uniku/uuid/v7`)
|
|
469
|
-
|
|
470
|
-
```ts
|
|
471
|
-
uuidv7(options?: UuidV7Options): string
|
|
472
|
-
uuidv7(options: UuidV7Options | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
473
|
-
uuidv7.toBytes(id: string): Uint8Array
|
|
474
|
-
uuidv7.fromBytes(bytes: Uint8Array): string
|
|
475
|
-
uuidv7.timestamp(id: string): number
|
|
476
|
-
uuidv7.isValid(id: unknown): id is string
|
|
477
|
-
uuidv7.NIL // "00000000-0000-0000-0000-000000000000"
|
|
478
|
-
uuidv7.MAX // "ffffffff-ffff-ffff-ffff-ffffffffffff"
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
### `ulid` (from `uniku/ulid`)
|
|
482
|
-
|
|
483
|
-
```ts
|
|
484
|
-
ulid(options?: UlidOptions): string
|
|
485
|
-
ulid(options: UlidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
486
|
-
ulid.toBytes(id: string): Uint8Array
|
|
487
|
-
ulid.fromBytes(bytes: Uint8Array): string
|
|
488
|
-
ulid.timestamp(id: string): number
|
|
489
|
-
ulid.isValid(id: unknown): id is string
|
|
490
|
-
ulid.NIL // "00000000000000000000000000"
|
|
491
|
-
ulid.MAX // "7ZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### `typeid` (from `uniku/typeid`)
|
|
495
|
-
|
|
496
|
-
```ts
|
|
497
|
-
typeid(prefix: string, options?: TypeidOptions): string
|
|
498
|
-
typeid.toBytes(id: string): Uint8Array
|
|
499
|
-
typeid.fromBytes(prefix: string, bytes: Uint8Array): string
|
|
500
|
-
typeid.toUuid(id: string): string
|
|
501
|
-
typeid.fromUuid(prefix: string, uuid: string): string
|
|
502
|
-
typeid.timestamp(id: string): number
|
|
503
|
-
typeid.prefix(id: string): string
|
|
504
|
-
typeid.suffix(id: string): string
|
|
505
|
-
typeid.isValid(id: unknown): id is string
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
Use an empty prefix (`typeid('')`) to generate Jetify-compatible prefixless TypeIDs.
|
|
509
|
-
|
|
510
|
-
### `cuidv2` (from `uniku/cuid/v2`)
|
|
511
|
-
|
|
512
|
-
```ts
|
|
513
|
-
cuidv2(options?: CuidV2Options): string
|
|
514
|
-
cuidv2.isValid(id: unknown): id is string
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
> Also available (deprecated) as `cuid2` from `uniku/cuid2` — the same generator
|
|
518
|
-
> under the pre-versioned entry point.
|
|
519
|
-
|
|
520
|
-
### `nanoid` (from `uniku/nanoid`)
|
|
521
|
-
|
|
522
|
-
```ts
|
|
523
|
-
nanoid(): string
|
|
524
|
-
nanoid(size: number): string
|
|
525
|
-
nanoid(options: NanoidOptions): string
|
|
526
|
-
nanoid.isValid(id: unknown): id is string
|
|
527
|
-
|
|
528
|
-
// Constant
|
|
529
|
-
URL_ALPHABET // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### `ksuid` (from `uniku/ksuid`)
|
|
533
|
-
|
|
534
|
-
```ts
|
|
535
|
-
ksuid(options?: KsuidOptions): string
|
|
536
|
-
ksuid(options: KsuidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
537
|
-
ksuid.toBytes(id: string): Uint8Array
|
|
538
|
-
ksuid.fromBytes(bytes: Uint8Array): string
|
|
539
|
-
ksuid.timestamp(id: string): number
|
|
540
|
-
ksuid.isValid(id: unknown): id is string
|
|
541
|
-
ksuid.NIL // "000000000000000000000000000"
|
|
542
|
-
ksuid.MAX // "aWgEPTl1tmebfsQzFP4bxwgy80V"
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
### `objectid` (from `uniku/objectid`)
|
|
546
|
-
|
|
547
|
-
```ts
|
|
548
|
-
objectid(options?: ObjectIdOptions): string
|
|
549
|
-
objectid(options: ObjectIdOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
550
|
-
objectid.toBytes(id: string): Uint8Array
|
|
551
|
-
objectid.fromBytes(bytes: Uint8Array): string
|
|
552
|
-
objectid.timestamp(id: string): number
|
|
553
|
-
objectid.isValid(id: unknown): id is string
|
|
554
|
-
objectid.NIL // "000000000000000000000000"
|
|
555
|
-
objectid.MAX // "ffffffffffffffffffffffff"
|
|
65
|
+
import { tsid } from 'uniku/tsid'
|
|
556
66
|
```
|
|
557
67
|
|
|
558
|
-
|
|
68
|
+
## Choose an ID
|
|
559
69
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
tsid.MAX // 18446744073709551615n
|
|
571
|
-
```
|
|
70
|
+
| Situation | Start with | Why |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| Database primary keys | UUID v7 or ULID | Time-ordered values improve index locality |
|
|
73
|
+
| Public API resources | TypeID | UUID v7 with a readable, domain-specific prefix |
|
|
74
|
+
| Short URLs and invite codes | Nanoid | Compact and URL-safe |
|
|
75
|
+
| Values that should resist enumeration | CUID v2 | Non-sequential and secure |
|
|
76
|
+
| MongoDB `_id` compatibility | ObjectID | Matches MongoDB's 12-byte ObjectID format |
|
|
77
|
+
| Go rs/xid compatibility | XID | Matches rs/xid's text and binary representation |
|
|
78
|
+
| Native `BIGINT` storage | TSID | A sortable 64-bit integer rather than a UUID-shaped string |
|
|
79
|
+
| Broad standards compatibility | UUID v4 | The conventional random UUID format |
|
|
572
80
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
### `xid` (from `uniku/xid`)
|
|
576
|
-
|
|
577
|
-
```ts
|
|
578
|
-
xid(options?: XidOptions): string
|
|
579
|
-
xid(options: XidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array
|
|
580
|
-
xid.toBytes(id: string): Uint8Array
|
|
581
|
-
xid.fromBytes(bytes: Uint8Array): string
|
|
582
|
-
xid.timestamp(id: string): number
|
|
583
|
-
xid.isValid(id: unknown): id is string
|
|
584
|
-
xid.NIL // "00000000000000000000"
|
|
585
|
-
xid.MAX // "vvvvvvvvvvvvvvvvvvvg"
|
|
586
|
-
```
|
|
81
|
+
The [choosing guide](https://jkomyno.github.io/uniku/docs/guides/choosing-an-id/) covers ordering, timestamp leakage, storage boundaries, and format-specific trade-offs.
|
|
587
82
|
|
|
588
83
|
## Documentation
|
|
589
84
|
|
|
590
|
-
|
|
591
|
-
|
|
85
|
+
- [Getting started](https://jkomyno.github.io/uniku/docs/getting-started/) — installation, direct imports, validation, and byte helpers
|
|
86
|
+
- [Generator reference](https://jkomyno.github.io/uniku/docs/reference/entry-points/) — generated from the public TypeScript signatures and JSDoc
|
|
87
|
+
- [Migration guide](https://jkomyno.github.io/uniku/docs/migration/from-other-libraries/) — move from `uuid`, `nanoid`, `ulid`, CUID2, KSUID, BSON, TSID, or XID libraries
|
|
88
|
+
- [Performance and bundle size](https://jkomyno.github.io/uniku/docs/guides/performance/) — benchmark methodology, current measurements, and reproduction commands
|
|
89
|
+
- [Stability contract](https://github.com/jkomyno/uniku/blob/main/docs/STABILITY.md) — the entry points and compatibility policy planned for `uniku@1.0.0`
|
|
592
90
|
|
|
593
|
-
##
|
|
91
|
+
## CLI companion
|
|
594
92
|
|
|
595
|
-
|
|
93
|
+
`@uniku/cli` generates, validates, and inspects IDs from a terminal or shell pipeline. Install the current standalone binary:
|
|
596
94
|
|
|
597
|
-
|
|
598
|
-
-
|
|
599
|
-
- [TypeID-JS](https://github.com/jetify-com/typeid-js): official JavaScript implementation of TypeID
|
|
600
|
-
- [@paralleldrive/cuid2](https://github.com/paralleldrive/cuid2): secure, collision-resistant IDs
|
|
601
|
-
- [@owpz/ksuid](https://github.com/owpz/ksuid): K-Sortable Unique Identifier
|
|
602
|
-
- [bson](https://github.com/mongodb/js-bson): official MongoDB BSON library, including the ObjectId implementation
|
|
603
|
-
- [nanoid](https://github.com/ai/nanoid): tiny, URL-friendly unique string ID generator
|
|
604
|
-
- [tsid-ts](https://github.com/yubinTW/tsid-ts): TypeScript implementation of TSID, a Snowflake-style 64-bit Time-Sorted Unique Identifier
|
|
95
|
+
```sh
|
|
96
|
+
curl -fsSL https://raw.githubusercontent.com/jkomyno/uniku/main/install.sh | sh
|
|
605
97
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
98
|
+
uniku uuid -v 7
|
|
99
|
+
uniku validate 018e5e5c-7c8a-7000-8000-000000000000
|
|
100
|
+
uniku inspect 018e5e5c-7c8a-7000-8000-000000000000
|
|
101
|
+
```
|
|
609
102
|
|
|
610
|
-
|
|
103
|
+
See the [CLI documentation](https://jkomyno.github.io/uniku/docs/cli/) for package-manager installs and the complete command reference.
|
|
611
104
|
|
|
612
|
-
|
|
105
|
+
## Contributing
|
|
613
106
|
|
|
614
|
-
|
|
615
|
-
- X: [@jkomyno](https://x.com/jkomyno)
|
|
107
|
+
See [CONTRIBUTING.md](https://github.com/jkomyno/uniku/blob/main/CONTRIBUTING.md) for the development workflow, tests, benchmarks, and preview releases.
|
|
616
108
|
|
|
617
109
|
## License
|
|
618
110
|
|
|
619
|
-
MIT
|
|
111
|
+
MIT © [Alberto Schiabel](https://github.com/jkomyno)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,KAqCD,aAAA;EArCZ;;;;AAAa;EA2CX,MAAA,GAAS,UAAA;EANC;;;;EAWV,QAAA;;;;EAIA,IAAA;AAAA;AAAA,KAGU,MAAA;;GAIT,IAAA;GAEA,OAAA,EAAS,aAAA;;;;AAKY;EAAtB,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;AAyLH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAR,MAAA,EAAQ,MAAA"}
|
package/build/nanoid/nanoid.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{InvalidInputError as e,UniqueIdError as t}from"../errors.mjs";const n=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,r=2048,i=/^[A-Za-z0-9_-]
|
|
1
|
+
import{InvalidInputError as e,UniqueIdError as t}from"../errors.mjs";const n=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,r=2048,i=/^[A-Za-z0-9_-]+$/,a=new TextDecoder;let o,s=``,c=0;function l(e){let t=Math.min(e*128,65536);if((!o||o.length<t)&&(o=new Uint8Array(t)),c+e>s.length){crypto.getRandomValues(o);for(let e=0;e<o.length;e++)o[e]=n.charCodeAt(o[e]&63);s=a.decode(o),c=0}}function u(e){l(e);let t=s.substring(c,c+e);return c+=e,t}function d(t){if(t.length<2)throw new e(`NANOID_ALPHABET_TOO_SHORT`,`Alphabet must contain at least 2 characters`);if(t.length>256)throw new e(`NANOID_ALPHABET_TOO_LONG`,`Alphabet must not exceed 256 characters`);let n=new Set;for(let r of t){let t=r.charCodeAt(0);if(t<32||t>126)throw new e(`NANOID_ALPHABET_INVALID_CHAR`,`Alphabet must contain only printable ASCII characters (32-126)`);if(n.has(r))throw new e(`NANOID_ALPHABET_DUPLICATE`,`Duplicate character in alphabet: "${r}"`);n.add(r)}}function f(t){if(!Number.isInteger(t)||t<0)throw new e(`NANOID_SIZE_INVALID`,`Size must be a non-negative integer`);if(t>r)throw new e(`NANOID_SIZE_TOO_LARGE`,`Size must not exceed ${r}`)}function p(t){if(t===void 0)return u(21);let r=21,i=n,a;if(typeof t==`number`?r=t:(r=t.size??21,i=t.alphabet??`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,a=t.random,t.alphabet!==void 0&&d(i)),f(r),r===0)return``;if(i===`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`&&a===void 0)return u(r);let o=i.length;if(!(o&o-1)){let t=o-1;if(a&&a.length<r)throw new e(`NANOID_RANDOM_BYTES_INSUFFICIENT`,`Insufficient random bytes: need ${r}, have ${a.length}`);let n=a?.subarray(0,r)??globalThis.crypto.getRandomValues(new Uint8Array(r)),s=``;for(let e=0;e<r;e++)s+=i[n[e]&t];return s}let s=(2<<31-Math.clz32(o-1|1))-1,c=Math.ceil(1.6*s*r/o),l=``,p=0;for(;l.length<r;){let t;if(a){if(a.length-p<c)throw new e(`NANOID_RANDOM_BYTES_INSUFFICIENT`,`Insufficient random bytes: need at least ${c} more, have ${a.length-p}`);t=a.subarray(p,p+c),p+=c}else t=globalThis.crypto.getRandomValues(new Uint8Array(c));for(let e=0;e<t.length&&l.length<r;e++){let n=t[e]&s;n<o&&(l+=i[n])}}return l}function m(e){return typeof e==`string`&&e.length>0&&i.test(e)}const h=Object.assign(p,{isValid:m});export{e as InvalidInputError,n as URL_ALPHABET,t as UniqueIdError,h as nanoid};
|
|
2
2
|
//# sourceMappingURL=nanoid.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. It consumes `size`\n// bytes directly without the shared helper's UUID/CUID2-oriented branches.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nlet pool: Uint8Array | undefined\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!pool || pool.length < size) {\n pool = new Uint8Array(size)\n crypto.getRandomValues(pool)\n poolOffset = 0\n } else if (poolOffset + bytes > pool.length) {\n crypto.getRandomValues(pool)\n poolOffset = 0\n }\n poolOffset += bytes\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n let id = ''\n for (let i = poolOffset - size; i < poolOffset; i++) {\n id += URL_ALPHABET[pool![i] & 63]\n }\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `size` bytes needed.\n * For other alphabets: ~size * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_SHORT', 'Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_LONG', 'Alphabet must not exceed 256 characters')\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'NANOID_ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('NANOID_ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`)\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate size parameter\n */\nfunction validateSize(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('NANOID_SIZE_INVALID', 'Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('NANOID_SIZE_TOO_LARGE', `Size must not exceed ${MAX_SIZE}`)\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom size\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Size must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested size\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. Translate random bytes\n// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nconst ASCII_DECODER = new TextDecoder()\nlet poolBytes: Uint8Array | undefined\nlet characterPool = ''\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!poolBytes || poolBytes.length < size) {\n poolBytes = new Uint8Array(size)\n }\n if (poolOffset + bytes > characterPool.length) {\n crypto.getRandomValues(poolBytes)\n for (let i = 0; i < poolBytes.length; i++) {\n poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)\n }\n characterPool = ASCII_DECODER.decode(poolBytes)\n poolOffset = 0\n }\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n const id = characterPool.substring(poolOffset, poolOffset + size)\n poolOffset += size\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `size` bytes needed.\n * For other alphabets: ~size * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_SHORT', 'Alphabet must contain at least 2 characters')\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('NANOID_ALPHABET_TOO_LONG', 'Alphabet must not exceed 256 characters')\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'NANOID_ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('NANOID_ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`)\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate size parameter\n */\nfunction validateSize(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('NANOID_SIZE_INVALID', 'Size must be a non-negative integer')\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('NANOID_SIZE_TOO_LARGE', `Size must not exceed ${MAX_SIZE}`)\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateSize(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'NANOID_RANDOM_BYTES_INSUFFICIENT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom size\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Size must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested size\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
package/build/xid/xid.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xid.d.mts","names":[],"sources":["../../src/xid/xid.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"xid.d.mts","names":[],"sources":["../../src/xid/xid.ts"],"mappings":";;;KAgBY,UAAA;4DAEV,SAAA,GAAY,UAAA,EAFd;EAIE,SAAA;EAEA,IAAA;EAEA,OAAA;AAAA;AAAA,KAGU,GAAA;EAAA;EAAA,cAEI,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,UAAA,cAAwB,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;EAAA,CACpG,OAAA,GAAU,UAAA,EAAY,GAAA,cAAiB,MAAA;EACxC,OAAA,CAAQ,EAAA,WAAa,UAAA;EACrB,SAAA,CAAU,KAAA,EAAO,UAAA;EACjB,SAAA,CAAU,EAAA;EACV,OAAA,CAAQ,EAAA,YAAc,EAAA;EACtB,GAAA;EACA,GAAA;AAAA;;;;;;;;cA8HW,GAAA,EAAK,GAAA"}
|
package/build/xid/xid.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{n as e,t}from"../random-Chp-Nkzi.mjs";import{n,t as r}from"../validation-CTNpXm94.mjs";import{BufferError as i,InvalidInputError as a,ParseError as o,UniqueIdError as s}from"../errors.mjs";import{n as c}from"../bytes-xqWxFYsM.mjs";const l=`0123456789abcdefghijklmnopqrstuv`,u=new Uint8Array(65536);
|
|
1
|
+
import{n as e,t}from"../random-Chp-Nkzi.mjs";import{n,t as r}from"../validation-CTNpXm94.mjs";import{BufferError as i,InvalidInputError as a,ParseError as o,UniqueIdError as s}from"../errors.mjs";import{n as c}from"../bytes-xqWxFYsM.mjs";const l=`0123456789abcdefghijklmnopqrstuv`,u=Uint8Array.from(l,e=>e.charCodeAt(0)),d=Array(20),f=[,,,,,,],p=new Uint8Array(65536);p.fill(255);for(let e=0;e<32;e+=1)p[l.charCodeAt(e)]=e;function m(e){if(e.length!==12)throw new i(`XID_BYTES_INVALID_LENGTH`,`XID bytes must be exactly 12 bytes, got ${e.length}`);return d[0]=u[e[0]>>3],d[1]=u[(e[0]<<2|e[1]>>6)&31],d[2]=u[e[1]>>1&31],d[3]=u[(e[1]<<4|e[2]>>4)&31],d[4]=u[(e[2]<<1|e[3]>>7)&31],d[5]=u[e[3]>>2&31],d[6]=u[(e[3]<<3|e[4]>>5)&31],d[7]=u[e[4]&31],d[8]=u[e[5]>>3],d[9]=u[(e[5]<<2|e[6]>>6)&31],d[10]=u[e[6]>>1&31],d[11]=u[(e[6]<<4|e[7]>>4)&31],d[12]=u[(e[7]<<1|e[8]>>7)&31],d[13]=u[e[8]>>2&31],d[14]=u[(e[8]<<3|e[9]>>5)&31],d[15]=u[e[9]&31],d[16]=u[e[10]>>3],d[17]=u[(e[10]<<2|e[11]>>6)&31],d[18]=u[e[11]>>1&31],d[19]=u[e[11]<<4&31],String.fromCharCode(...d)}function h(e,t){let n=t>>>16,r=t>>>8&255,i=t&255;return f[0]=u[(e<<3|n>>5)&31],f[1]=u[n&31],f[2]=u[r>>3],f[3]=u[(r<<2|i>>6)&31],f[4]=u[i>>1&31],f[5]=u[i<<4&31],String.fromCharCode(...f)}function g(e){if(e.length!==20)throw new o(`XID_INVALID_LENGTH`,`XID string must be 20 characters, got ${e.length}`);let t=new Uint8Array(20);for(let n=0;n<20;n+=1){let r=p[e.charCodeAt(n)];if(r===255)throw new o(`XID_INVALID_CHAR`,`Invalid XID character: ${e[n]}`);t[n]=r}if(t[19]!==0&&t[19]!==16)throw new o(`XID_NON_CANONICAL`,`XID trailing bits must be canonically encoded`);return new Uint8Array([t[0]<<3|t[1]>>2,t[1]<<6|t[2]<<1|t[3]>>4,t[3]<<4|t[4]>>1,t[4]<<7|t[5]<<2|t[6]>>3,t[6]<<5|t[7],t[8]<<3|t[9]>>2,t[9]<<6|t[10]<<1|t[11]>>4,t[11]<<4|t[12]>>1,t[12]<<7|t[13]<<2|t[14]>>3,t[14]<<5|t[15],t[16]<<3|t[17]>>2,t[17]<<6|t[18]<<1|t[19]>>4])}const _=4294967295,v=65535,y=16777215,b=/^[0-9a-v]{19}[0g]$/,x=new Uint8Array(12);let S=-1,C=``;const w={machineId:void 0,processId:void 0,counter:void 0};function T(e){return t(e).slice()}function E(){w.machineId===void 0&&(w.machineId=T(3)),w.processId===void 0&&(w.processId=e()&v)}function D(){return w.counter===void 0&&(w.counter=e()&y),w.counter=w.counter+1&y,w.counter}function O(e,t,n,r,i,a){c(i,a,e),i.set(t.subarray(0,3),a+4),i[a+7]=n>>>8,i[a+8]=n&255,i[a+9]=r>>>16,i[a+10]=r>>>8&255,i[a+11]=r&255}function k(e){if(e.machineId!==void 0&&e.machineId.length<3)throw new a(`XID_MACHINE_ID_BYTES_TOO_SHORT`,`Machine ID bytes length must be >= 3 for XID`);if(e.processId!==void 0&&!r(e.processId,0,v))throw new a(`XID_PROCESS_ID_OUT_OF_RANGE`,`Process ID must be between 0 and ${v}`);if(e.secs!==void 0&&!r(e.secs,0,_))throw new a(`XID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be between 0 and ${_}`);if(e.counter!==void 0&&!r(e.counter,0,y))throw new a(`XID_COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${y}`)}function A(e,t,r=0){if(e===void 0&&t===void 0){E();let e=Math.floor(Date.now()/1e3),t=D();return e!==S&&(O(e,w.machineId,w.processId,t,x,0),C=m(x).slice(0,14),S=e),C+h(w.processId&255,t)}e!==void 0&&k(e);let a=e?.secs??Math.floor(Date.now()/1e3);(e?.machineId===void 0||e.processId===void 0)&&E();let o=e?.machineId??w.machineId,s=e?.processId??w.processId,c=e?.counter??D();if(t!==void 0){if(!n(t,r,12))throw new i(`XID_BUFFER_OUT_OF_BOUNDS`,`XID byte range ${r}:${r+11} is out of buffer bounds`);return O(a,o,s,c,t,r),t}return O(a,o,s,c,x,0),m(x)}function j(e){return g(e)}function M(e){return m(e)}function N(e){let t=g(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function P(e){return typeof e==`string`&&b.test(e)}const F=Object.assign(A,{toBytes:j,fromBytes:M,timestamp:N,isValid:P,NIL:`0`.repeat(20),MAX:m(new Uint8Array(12).fill(255))});export{i as BufferError,a as InvalidInputError,o as ParseError,s as UniqueIdError,F as xid};
|
|
2
2
|
//# sourceMappingURL=xid.mjs.map
|
package/build/xid/xid.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xid.mjs","names":["XID_BYTES"],"sources":["../../src/xid/base32hex.ts","../../src/xid/xid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\nconst ENCODING = '0123456789abcdefghijklmnopqrstuv'\nconst XID_BYTES = 12\nconst XID_LENGTH = 20\n\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255)\nfor (let i = 0; i < ENCODING.length; i += 1) {\n DECODING[ENCODING.charCodeAt(i)] = i\n}\n\n/** Encode the 12 canonical XID bytes as lowercase base32hex. */\nexport function encodeBase32Hex(bytes: Uint8Array): string {\n if (bytes.length !== XID_BYTES) {\n throw new BufferError(\n 'XID_BYTES_INVALID_LENGTH',\n `XID bytes must be exactly ${XID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n return (\n ENCODING[bytes[0] >> 3] +\n ENCODING[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f] +\n ENCODING[(bytes[1] >> 1) & 0x1f] +\n ENCODING[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f] +\n ENCODING[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f] +\n ENCODING[(bytes[3] >> 2) & 0x1f] +\n ENCODING[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f] +\n ENCODING[bytes[4] & 0x1f] +\n ENCODING[bytes[5] >> 3] +\n ENCODING[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f] +\n ENCODING[(bytes[6] >> 1) & 0x1f] +\n ENCODING[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f] +\n ENCODING[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f] +\n ENCODING[(bytes[8] >> 2) & 0x1f] +\n ENCODING[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f] +\n ENCODING[bytes[9] & 0x1f] +\n ENCODING[bytes[10] >> 3] +\n ENCODING[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f] +\n ENCODING[(bytes[11] >> 1) & 0x1f] +\n ENCODING[(bytes[11] << 4) & 0x1f]\n )\n}\n\n/** Decode a canonical lowercase base32hex XID string to its 12 bytes. */\nexport function decodeBase32Hex(id: string): Uint8Array {\n if (id.length !== XID_LENGTH) {\n throw new ParseError('XID_INVALID_LENGTH', `XID string must be ${XID_LENGTH} characters, got ${id.length}`)\n }\n\n const values = new Uint8Array(XID_LENGTH)\n for (let i = 0; i < XID_LENGTH; i += 1) {\n const value = DECODING[id.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('XID_INVALID_CHAR', `Invalid XID character: ${id[i]}`)\n }\n values[i] = value\n }\n\n if (values[19] !== 0 && values[19] !== 16) {\n throw new ParseError('XID_NON_CANONICAL', 'XID trailing bits must be canonically encoded')\n }\n\n return new Uint8Array([\n (values[0] << 3) | (values[1] >> 2),\n (values[1] << 6) | (values[2] << 1) | (values[3] >> 4),\n (values[3] << 4) | (values[4] >> 1),\n (values[4] << 7) | (values[5] << 2) | (values[6] >> 3),\n (values[6] << 5) | values[7],\n (values[8] << 3) | (values[9] >> 2),\n (values[9] << 6) | (values[10] << 1) | (values[11] >> 4),\n (values[11] << 4) | (values[12] >> 1),\n (values[12] << 7) | (values[13] << 2) | (values[14] >> 3),\n (values[14] << 5) | values[15],\n (values[16] << 3) | (values[17] >> 2),\n (values[17] << 6) | (values[18] << 1) | (values[19] >> 4),\n ])\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase32Hex, encodeBase32Hex } from './base32hex'\n\nconst XID_BYTES = 12\nconst MACHINE_ID_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_PROCESS_ID = 0xffff\nconst MAX_COUNTER = 0xffffff\nconst XID_REGEX = /^[0-9a-v]{19}[0g]$/\n\nexport type XidOptions = {\n /** First three bytes used as the XID machine identity. */\n machineId?: Uint8Array\n /** 16-bit process identity. */\n processId?: number\n /** Unix timestamp in seconds. Defaults to the current second. */\n secs?: number\n /** 24-bit counter. Explicit values do not consume shared state. */\n counter?: number\n}\n\nexport type Xid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: XidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n NIL: string\n MAX: string\n}\n\ntype XidState = {\n machineId: Uint8Array | undefined\n processId: number | undefined\n counter: number | undefined\n}\n\nconst state: XidState = { machineId: undefined, processId: undefined, counter: undefined }\n\n/** Copy pooled bytes before another random draw can refill their backing pool. */\nfunction freshRandom(count: number): Uint8Array {\n return randomBytes(count).slice()\n}\n\nfunction initializeIdentity(): void {\n if (state.machineId === undefined) state.machineId = freshRandom(MACHINE_ID_BYTES)\n if (state.processId === undefined) state.processId = randomUint32() & MAX_PROCESS_ID\n}\n\nfunction nextCounter(): number {\n if (state.counter === undefined) state.counter = randomUint32() & MAX_COUNTER\n state.counter = (state.counter + 1) & MAX_COUNTER\n return state.counter\n}\n\nfunction writeXidBytesUnchecked(\n secs: number,\n machineId: Uint8Array,\n processId: number,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n writeTimestamp32(buf, offset, secs)\n buf.set(machineId.subarray(0, MACHINE_ID_BYTES), offset + 4)\n buf[offset + 7] = processId >>> 8\n buf[offset + 8] = processId & 0xff\n buf[offset + 9] = counter >>> 16\n buf[offset + 10] = (counter >>> 8) & 0xff\n buf[offset + 11] = counter & 0xff\n}\n\nfunction validateOptions(options: XidOptions): void {\n if (options.machineId !== undefined && options.machineId.length < MACHINE_ID_BYTES) {\n throw new InvalidInputError(\n 'XID_MACHINE_ID_BYTES_TOO_SHORT',\n `Machine ID bytes length must be >= ${MACHINE_ID_BYTES} for XID`,\n )\n }\n if (options.processId !== undefined && !isIntegerInRange(options.processId, 0, MAX_PROCESS_ID)) {\n throw new InvalidInputError('XID_PROCESS_ID_OUT_OF_RANGE', `Process ID must be between 0 and ${MAX_PROCESS_ID}`)\n }\n if (options.secs !== undefined && !isIntegerInRange(options.secs, 0, MAX_SECS)) {\n throw new InvalidInputError('XID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)\n }\n if (options.counter !== undefined && !isIntegerInRange(options.counter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('XID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)\n }\n}\n\nfunction xidFn(options?: XidOptions, buf?: undefined, offset?: number): string\nfunction xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf\nfunction xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?: TBuf, offset = 0): string | TBuf {\n if (options !== undefined) validateOptions(options)\n\n const secs = options?.secs ?? Math.floor(Date.now() / 1000)\n if (options?.machineId === undefined || options.processId === undefined) {\n initializeIdentity()\n }\n const machineId = options?.machineId ?? state.machineId!\n const processId = options?.processId ?? state.processId!\n const counter = options?.counter ?? nextCounter()\n\n if (buf !== undefined) {\n if (!isWritableRange(buf, offset, XID_BYTES)) {\n throw new BufferError(\n 'XID_BUFFER_OUT_OF_BOUNDS',\n `XID byte range ${offset}:${offset + 11} is out of buffer bounds`,\n )\n }\n writeXidBytesUnchecked(secs, machineId, processId, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(XID_BYTES)\n writeXidBytesUnchecked(secs, machineId, processId, counter, bytes, 0)\n return encodeBase32Hex(bytes)\n}\n\nfunction toBytes(id: string): Uint8Array {\n return decodeBase32Hex(id)\n}\n\nfunction fromBytes(bytes: Uint8Array): string {\n return encodeBase32Hex(bytes)\n}\n\nfunction timestamp(id: string): number {\n const bytes = decodeBase32Hex(id)\n return (((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0) * 1000\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && XID_REGEX.test(id)\n}\n\n/**\n * Generate a 20-character rs/xid-compatible identifier.\n *\n * XID embeds seconds, a lazily-random per-runtime identity, and a shared\n * always-incrementing counter. In Cloudflare Workers, time is frozen during a\n * request, so the counter preserves ordering for IDs made in that request.\n */\nexport const xid: Xid = Object.assign(xidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(20),\n MAX: encodeBase32Hex(new Uint8Array(12).fill(0xff)),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"xid.mjs","names":["XID_BYTES"],"sources":["../../src/xid/base32hex.ts","../../src/xid/xid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\nconst ENCODING = '0123456789abcdefghijklmnopqrstuv'\nconst ENCODING_CODES = Uint8Array.from(ENCODING, (character) => character.charCodeAt(0))\nconst XID_BYTES = 12\nconst XID_LENGTH = 20\nconst ENCODED = new Array<number>(XID_LENGTH)\nconst COUNTER_SUFFIX = new Array<number>(6)\n\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255)\nfor (let i = 0; i < ENCODING.length; i += 1) {\n DECODING[ENCODING.charCodeAt(i)] = i\n}\n\n/** Encode the 12 canonical XID bytes as lowercase base32hex. */\nexport function encodeBase32Hex(bytes: Uint8Array): string {\n if (bytes.length !== XID_BYTES) {\n throw new BufferError(\n 'XID_BYTES_INVALID_LENGTH',\n `XID bytes must be exactly ${XID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n ENCODED[0] = ENCODING_CODES[bytes[0] >> 3]\n ENCODED[1] = ENCODING_CODES[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f]\n ENCODED[2] = ENCODING_CODES[(bytes[1] >> 1) & 0x1f]\n ENCODED[3] = ENCODING_CODES[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f]\n ENCODED[4] = ENCODING_CODES[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f]\n ENCODED[5] = ENCODING_CODES[(bytes[3] >> 2) & 0x1f]\n ENCODED[6] = ENCODING_CODES[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f]\n ENCODED[7] = ENCODING_CODES[bytes[4] & 0x1f]\n ENCODED[8] = ENCODING_CODES[bytes[5] >> 3]\n ENCODED[9] = ENCODING_CODES[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f]\n ENCODED[10] = ENCODING_CODES[(bytes[6] >> 1) & 0x1f]\n ENCODED[11] = ENCODING_CODES[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f]\n ENCODED[12] = ENCODING_CODES[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f]\n ENCODED[13] = ENCODING_CODES[(bytes[8] >> 2) & 0x1f]\n ENCODED[14] = ENCODING_CODES[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f]\n ENCODED[15] = ENCODING_CODES[bytes[9] & 0x1f]\n ENCODED[16] = ENCODING_CODES[bytes[10] >> 3]\n ENCODED[17] = ENCODING_CODES[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f]\n ENCODED[18] = ENCODING_CODES[(bytes[11] >> 1) & 0x1f]\n ENCODED[19] = ENCODING_CODES[(bytes[11] << 4) & 0x1f]\n return String.fromCharCode(...ENCODED)\n}\n\n/** Encode the six XID characters affected by the 24-bit counter. */\nexport function encodeCounterSuffix(lastIdentityByte: number, counter: number): string {\n const high = counter >>> 16\n const middle = (counter >>> 8) & 0xff\n const low = counter & 0xff\n COUNTER_SUFFIX[0] = ENCODING_CODES[((lastIdentityByte << 3) | (high >> 5)) & 0x1f]\n COUNTER_SUFFIX[1] = ENCODING_CODES[high & 0x1f]\n COUNTER_SUFFIX[2] = ENCODING_CODES[middle >> 3]\n COUNTER_SUFFIX[3] = ENCODING_CODES[((middle << 2) | (low >> 6)) & 0x1f]\n COUNTER_SUFFIX[4] = ENCODING_CODES[(low >> 1) & 0x1f]\n COUNTER_SUFFIX[5] = ENCODING_CODES[(low << 4) & 0x1f]\n return String.fromCharCode(...COUNTER_SUFFIX)\n}\n\n/** Decode a canonical lowercase base32hex XID string to its 12 bytes. */\nexport function decodeBase32Hex(id: string): Uint8Array {\n if (id.length !== XID_LENGTH) {\n throw new ParseError('XID_INVALID_LENGTH', `XID string must be ${XID_LENGTH} characters, got ${id.length}`)\n }\n\n const values = new Uint8Array(XID_LENGTH)\n for (let i = 0; i < XID_LENGTH; i += 1) {\n const value = DECODING[id.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('XID_INVALID_CHAR', `Invalid XID character: ${id[i]}`)\n }\n values[i] = value\n }\n\n if (values[19] !== 0 && values[19] !== 16) {\n throw new ParseError('XID_NON_CANONICAL', 'XID trailing bits must be canonically encoded')\n }\n\n return new Uint8Array([\n (values[0] << 3) | (values[1] >> 2),\n (values[1] << 6) | (values[2] << 1) | (values[3] >> 4),\n (values[3] << 4) | (values[4] >> 1),\n (values[4] << 7) | (values[5] << 2) | (values[6] >> 3),\n (values[6] << 5) | values[7],\n (values[8] << 3) | (values[9] >> 2),\n (values[9] << 6) | (values[10] << 1) | (values[11] >> 4),\n (values[11] << 4) | (values[12] >> 1),\n (values[12] << 7) | (values[13] << 2) | (values[14] >> 3),\n (values[14] << 5) | values[15],\n (values[16] << 3) | (values[17] >> 2),\n (values[17] << 6) | (values[18] << 1) | (values[19] >> 4),\n ])\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase32Hex, encodeBase32Hex, encodeCounterSuffix } from './base32hex'\n\nconst XID_BYTES = 12\nconst MACHINE_ID_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_PROCESS_ID = 0xffff\nconst MAX_COUNTER = 0xffffff\nconst XID_REGEX = /^[0-9a-v]{19}[0g]$/\nconst stringBuffer = new Uint8Array(XID_BYTES)\nlet cachedPrefixSecs = -1\nlet cachedPrefix = ''\n\nexport type XidOptions = {\n /** First three bytes used as the XID machine identity. */\n machineId?: Uint8Array\n /** 16-bit process identity. */\n processId?: number\n /** Unix timestamp in seconds. Defaults to the current second. */\n secs?: number\n /** 24-bit counter. Explicit values do not consume shared state. */\n counter?: number\n}\n\nexport type Xid = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: XidOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n NIL: string\n MAX: string\n}\n\ntype XidState = {\n machineId: Uint8Array | undefined\n processId: number | undefined\n counter: number | undefined\n}\n\nconst state: XidState = { machineId: undefined, processId: undefined, counter: undefined }\n\n/** Copy pooled bytes before another random draw can refill their backing pool. */\nfunction freshRandom(count: number): Uint8Array {\n return randomBytes(count).slice()\n}\n\nfunction initializeIdentity(): void {\n if (state.machineId === undefined) state.machineId = freshRandom(MACHINE_ID_BYTES)\n if (state.processId === undefined) state.processId = randomUint32() & MAX_PROCESS_ID\n}\n\nfunction nextCounter(): number {\n if (state.counter === undefined) state.counter = randomUint32() & MAX_COUNTER\n state.counter = (state.counter + 1) & MAX_COUNTER\n return state.counter\n}\n\nfunction writeXidBytesUnchecked(\n secs: number,\n machineId: Uint8Array,\n processId: number,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\n writeTimestamp32(buf, offset, secs)\n buf.set(machineId.subarray(0, MACHINE_ID_BYTES), offset + 4)\n buf[offset + 7] = processId >>> 8\n buf[offset + 8] = processId & 0xff\n buf[offset + 9] = counter >>> 16\n buf[offset + 10] = (counter >>> 8) & 0xff\n buf[offset + 11] = counter & 0xff\n}\n\nfunction validateOptions(options: XidOptions): void {\n if (options.machineId !== undefined && options.machineId.length < MACHINE_ID_BYTES) {\n throw new InvalidInputError(\n 'XID_MACHINE_ID_BYTES_TOO_SHORT',\n `Machine ID bytes length must be >= ${MACHINE_ID_BYTES} for XID`,\n )\n }\n if (options.processId !== undefined && !isIntegerInRange(options.processId, 0, MAX_PROCESS_ID)) {\n throw new InvalidInputError('XID_PROCESS_ID_OUT_OF_RANGE', `Process ID must be between 0 and ${MAX_PROCESS_ID}`)\n }\n if (options.secs !== undefined && !isIntegerInRange(options.secs, 0, MAX_SECS)) {\n throw new InvalidInputError('XID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)\n }\n if (options.counter !== undefined && !isIntegerInRange(options.counter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('XID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)\n }\n}\n\nfunction xidFn(options?: XidOptions, buf?: undefined, offset?: number): string\nfunction xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf\nfunction xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?: TBuf, offset = 0): string | TBuf {\n if (options === undefined && buf === undefined) {\n initializeIdentity()\n const secs = Math.floor(Date.now() / 1000)\n const counter = nextCounter()\n if (secs !== cachedPrefixSecs) {\n writeXidBytesUnchecked(secs, state.machineId!, state.processId!, counter, stringBuffer, 0)\n cachedPrefix = encodeBase32Hex(stringBuffer).slice(0, 14)\n cachedPrefixSecs = secs\n }\n return cachedPrefix + encodeCounterSuffix(state.processId! & 0xff, counter)\n }\n\n if (options !== undefined) validateOptions(options)\n\n const secs = options?.secs ?? Math.floor(Date.now() / 1000)\n if (options?.machineId === undefined || options.processId === undefined) {\n initializeIdentity()\n }\n const machineId = options?.machineId ?? state.machineId!\n const processId = options?.processId ?? state.processId!\n const counter = options?.counter ?? nextCounter()\n\n if (buf !== undefined) {\n if (!isWritableRange(buf, offset, XID_BYTES)) {\n throw new BufferError(\n 'XID_BUFFER_OUT_OF_BOUNDS',\n `XID byte range ${offset}:${offset + 11} is out of buffer bounds`,\n )\n }\n writeXidBytesUnchecked(secs, machineId, processId, counter, buf, offset)\n return buf\n }\n\n writeXidBytesUnchecked(secs, machineId, processId, counter, stringBuffer, 0)\n return encodeBase32Hex(stringBuffer)\n}\n\nfunction toBytes(id: string): Uint8Array {\n return decodeBase32Hex(id)\n}\n\nfunction fromBytes(bytes: Uint8Array): string {\n return encodeBase32Hex(bytes)\n}\n\nfunction timestamp(id: string): number {\n const bytes = decodeBase32Hex(id)\n return (((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0) * 1000\n}\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && XID_REGEX.test(id)\n}\n\n/**\n * Generate a 20-character rs/xid-compatible identifier.\n *\n * XID embeds seconds, a lazily-random per-runtime identity, and a shared\n * always-incrementing counter. In Cloudflare Workers, time is frozen during a\n * request, so the counter preserves ordering for IDs made in that request.\n */\nexport const xid: Xid = Object.assign(xidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(20),\n MAX: encodeBase32Hex(new Uint8Array(12).fill(0xff)),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "uniku",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alberto Schiabel",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "git+https://github.com/jkomyno/uniku.git",
|
|
15
15
|
"directory": "packages/uniku"
|
|
16
16
|
},
|
|
17
|
-
"homepage": "https://github.
|
|
17
|
+
"homepage": "https://jkomyno.github.io/uniku/",
|
|
18
18
|
"bugs": {
|
|
19
19
|
"url": "https://github.com/jkomyno/uniku/issues"
|
|
20
20
|
},
|
|
@@ -161,7 +161,8 @@
|
|
|
161
161
|
"smoke:runtime:bun": "bun scripts/runtime-smoke.mjs",
|
|
162
162
|
"smoke:runtime:deno": "deno run --node-modules-dir=manual scripts/runtime-smoke.mjs",
|
|
163
163
|
"bench": "vitest bench --run --project unit",
|
|
164
|
-
"bench:compat": "
|
|
164
|
+
"bench:compat": "bun scripts/bench-compat.ts",
|
|
165
|
+
"bench:cold-start": "node scripts/bench-cold-start.mjs",
|
|
165
166
|
"bench:summary": "bun scripts/bench-summary.ts",
|
|
166
167
|
"bench:compare": "git show origin/gh-benchmarks:baseline.json > baseline.json 2>/dev/null && bun scripts/bench-compare.ts || echo 'No baseline found. Run benchmarks on main first.'",
|
|
167
168
|
"bundle:summary": "bun scripts/bundle-summary.ts",
|
package/src/nanoid/nanoid.ts
CHANGED
|
@@ -7,32 +7,34 @@ const DEFAULT_SIZE = 21
|
|
|
7
7
|
const MAX_SIZE = 2048
|
|
8
8
|
const NANOID_REGEX = /^[A-Za-z0-9_-]+$/
|
|
9
9
|
|
|
10
|
-
// Keep Nanoid's private pool for its default hot path.
|
|
11
|
-
//
|
|
10
|
+
// Keep Nanoid's private pool for its default hot path. Translate random bytes
|
|
11
|
+
// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.
|
|
12
12
|
const POOL_SIZE_MULTIPLIER = 128
|
|
13
13
|
const MAX_POOL_SIZE = 65_536
|
|
14
|
-
|
|
14
|
+
const ASCII_DECODER = new TextDecoder()
|
|
15
|
+
let poolBytes: Uint8Array | undefined
|
|
16
|
+
let characterPool = ''
|
|
15
17
|
let poolOffset = 0
|
|
16
18
|
|
|
17
19
|
function fillPool(bytes: number): void {
|
|
18
20
|
const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
if (!poolBytes || poolBytes.length < size) {
|
|
22
|
+
poolBytes = new Uint8Array(size)
|
|
23
|
+
}
|
|
24
|
+
if (poolOffset + bytes > characterPool.length) {
|
|
25
|
+
crypto.getRandomValues(poolBytes)
|
|
26
|
+
for (let i = 0; i < poolBytes.length; i++) {
|
|
27
|
+
poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)
|
|
28
|
+
}
|
|
29
|
+
characterPool = ASCII_DECODER.decode(poolBytes)
|
|
25
30
|
poolOffset = 0
|
|
26
31
|
}
|
|
27
|
-
poolOffset += bytes
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function defaultAlphabetFromPool(size: number): string {
|
|
31
35
|
fillPool(size)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
id += URL_ALPHABET[pool![i] & 63]
|
|
35
|
-
}
|
|
36
|
+
const id = characterPool.substring(poolOffset, poolOffset + size)
|
|
37
|
+
poolOffset += size
|
|
36
38
|
return id
|
|
37
39
|
}
|
|
38
40
|
|
package/src/xid/base32hex.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { BufferError, ParseError } from '../errors'
|
|
2
2
|
|
|
3
3
|
const ENCODING = '0123456789abcdefghijklmnopqrstuv'
|
|
4
|
+
const ENCODING_CODES = Uint8Array.from(ENCODING, (character) => character.charCodeAt(0))
|
|
4
5
|
const XID_BYTES = 12
|
|
5
6
|
const XID_LENGTH = 20
|
|
7
|
+
const ENCODED = new Array<number>(XID_LENGTH)
|
|
8
|
+
const COUNTER_SUFFIX = new Array<number>(6)
|
|
6
9
|
|
|
7
10
|
const DECODING = new Uint8Array(65536)
|
|
8
11
|
DECODING.fill(255)
|
|
@@ -19,28 +22,41 @@ export function encodeBase32Hex(bytes: Uint8Array): string {
|
|
|
19
22
|
)
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
25
|
+
ENCODED[0] = ENCODING_CODES[bytes[0] >> 3]
|
|
26
|
+
ENCODED[1] = ENCODING_CODES[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f]
|
|
27
|
+
ENCODED[2] = ENCODING_CODES[(bytes[1] >> 1) & 0x1f]
|
|
28
|
+
ENCODED[3] = ENCODING_CODES[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f]
|
|
29
|
+
ENCODED[4] = ENCODING_CODES[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f]
|
|
30
|
+
ENCODED[5] = ENCODING_CODES[(bytes[3] >> 2) & 0x1f]
|
|
31
|
+
ENCODED[6] = ENCODING_CODES[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f]
|
|
32
|
+
ENCODED[7] = ENCODING_CODES[bytes[4] & 0x1f]
|
|
33
|
+
ENCODED[8] = ENCODING_CODES[bytes[5] >> 3]
|
|
34
|
+
ENCODED[9] = ENCODING_CODES[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f]
|
|
35
|
+
ENCODED[10] = ENCODING_CODES[(bytes[6] >> 1) & 0x1f]
|
|
36
|
+
ENCODED[11] = ENCODING_CODES[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f]
|
|
37
|
+
ENCODED[12] = ENCODING_CODES[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f]
|
|
38
|
+
ENCODED[13] = ENCODING_CODES[(bytes[8] >> 2) & 0x1f]
|
|
39
|
+
ENCODED[14] = ENCODING_CODES[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f]
|
|
40
|
+
ENCODED[15] = ENCODING_CODES[bytes[9] & 0x1f]
|
|
41
|
+
ENCODED[16] = ENCODING_CODES[bytes[10] >> 3]
|
|
42
|
+
ENCODED[17] = ENCODING_CODES[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f]
|
|
43
|
+
ENCODED[18] = ENCODING_CODES[(bytes[11] >> 1) & 0x1f]
|
|
44
|
+
ENCODED[19] = ENCODING_CODES[(bytes[11] << 4) & 0x1f]
|
|
45
|
+
return String.fromCharCode(...ENCODED)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Encode the six XID characters affected by the 24-bit counter. */
|
|
49
|
+
export function encodeCounterSuffix(lastIdentityByte: number, counter: number): string {
|
|
50
|
+
const high = counter >>> 16
|
|
51
|
+
const middle = (counter >>> 8) & 0xff
|
|
52
|
+
const low = counter & 0xff
|
|
53
|
+
COUNTER_SUFFIX[0] = ENCODING_CODES[((lastIdentityByte << 3) | (high >> 5)) & 0x1f]
|
|
54
|
+
COUNTER_SUFFIX[1] = ENCODING_CODES[high & 0x1f]
|
|
55
|
+
COUNTER_SUFFIX[2] = ENCODING_CODES[middle >> 3]
|
|
56
|
+
COUNTER_SUFFIX[3] = ENCODING_CODES[((middle << 2) | (low >> 6)) & 0x1f]
|
|
57
|
+
COUNTER_SUFFIX[4] = ENCODING_CODES[(low >> 1) & 0x1f]
|
|
58
|
+
COUNTER_SUFFIX[5] = ENCODING_CODES[(low << 4) & 0x1f]
|
|
59
|
+
return String.fromCharCode(...COUNTER_SUFFIX)
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
/** Decode a canonical lowercase base32hex XID string to its 12 bytes. */
|
package/src/xid/xid.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { writeTimestamp32 } from '../common/bytes'
|
|
|
2
2
|
import { randomBytes, randomUint32 } from '../common/random'
|
|
3
3
|
import { isIntegerInRange, isWritableRange } from '../common/validation'
|
|
4
4
|
import { BufferError, InvalidInputError } from '../errors'
|
|
5
|
-
import { decodeBase32Hex, encodeBase32Hex } from './base32hex'
|
|
5
|
+
import { decodeBase32Hex, encodeBase32Hex, encodeCounterSuffix } from './base32hex'
|
|
6
6
|
|
|
7
7
|
const XID_BYTES = 12
|
|
8
8
|
const MACHINE_ID_BYTES = 3
|
|
@@ -10,6 +10,9 @@ const MAX_SECS = 0xffffffff
|
|
|
10
10
|
const MAX_PROCESS_ID = 0xffff
|
|
11
11
|
const MAX_COUNTER = 0xffffff
|
|
12
12
|
const XID_REGEX = /^[0-9a-v]{19}[0g]$/
|
|
13
|
+
const stringBuffer = new Uint8Array(XID_BYTES)
|
|
14
|
+
let cachedPrefixSecs = -1
|
|
15
|
+
let cachedPrefix = ''
|
|
13
16
|
|
|
14
17
|
export type XidOptions = {
|
|
15
18
|
/** First three bytes used as the XID machine identity. */
|
|
@@ -96,6 +99,18 @@ function validateOptions(options: XidOptions): void {
|
|
|
96
99
|
function xidFn(options?: XidOptions, buf?: undefined, offset?: number): string
|
|
97
100
|
function xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf
|
|
98
101
|
function xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?: TBuf, offset = 0): string | TBuf {
|
|
102
|
+
if (options === undefined && buf === undefined) {
|
|
103
|
+
initializeIdentity()
|
|
104
|
+
const secs = Math.floor(Date.now() / 1000)
|
|
105
|
+
const counter = nextCounter()
|
|
106
|
+
if (secs !== cachedPrefixSecs) {
|
|
107
|
+
writeXidBytesUnchecked(secs, state.machineId!, state.processId!, counter, stringBuffer, 0)
|
|
108
|
+
cachedPrefix = encodeBase32Hex(stringBuffer).slice(0, 14)
|
|
109
|
+
cachedPrefixSecs = secs
|
|
110
|
+
}
|
|
111
|
+
return cachedPrefix + encodeCounterSuffix(state.processId! & 0xff, counter)
|
|
112
|
+
}
|
|
113
|
+
|
|
99
114
|
if (options !== undefined) validateOptions(options)
|
|
100
115
|
|
|
101
116
|
const secs = options?.secs ?? Math.floor(Date.now() / 1000)
|
|
@@ -117,9 +132,8 @@ function xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?:
|
|
|
117
132
|
return buf
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return encodeBase32Hex(bytes)
|
|
135
|
+
writeXidBytesUnchecked(secs, machineId, processId, counter, stringBuffer, 0)
|
|
136
|
+
return encodeBase32Hex(stringBuffer)
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
function toBytes(id: string): Uint8Array {
|