@marianmeres/uid 1.0.0
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/AGENTS.md +111 -0
- package/API.md +425 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/alphabet.d.ts +25 -0
- package/dist/alphabet.js +40 -0
- package/dist/base56.d.ts +34 -0
- package/dist/base56.js +69 -0
- package/dist/counter.d.ts +55 -0
- package/dist/counter.js +87 -0
- package/dist/mod.d.ts +20 -0
- package/dist/mod.js +20 -0
- package/dist/random.d.ts +49 -0
- package/dist/random.js +102 -0
- package/dist/reversible.d.ts +41 -0
- package/dist/reversible.js +93 -0
- package/dist/rhr.d.ts +23 -0
- package/dist/rhr.js +28 -0
- package/dist/uid.d.ts +93 -0
- package/dist/uid.js +93 -0
- package/dist/uuid.d.ts +33 -0
- package/dist/uuid.js +74 -0
- package/package.json +42 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @marianmeres/uid — Agent Guide
|
|
2
|
+
|
|
3
|
+
## Quick Reference
|
|
4
|
+
|
|
5
|
+
- **Stack**: Deno, TypeScript. Core has zero runtime deps; `rhr` is an optional
|
|
6
|
+
peer dep (`@marianmeres/random-human-readable`).
|
|
7
|
+
- **Test**: `deno task test` | **Watch**: `deno task test:watch`
|
|
8
|
+
- **Lint**: `deno lint` | **Format**: `deno fmt`
|
|
9
|
+
- **JSR check**: `deno publish --dry-run --allow-dirty` (must report no slow types)
|
|
10
|
+
- **Build npm**: `deno task npm:build` | **Publish**: `deno task publish`
|
|
11
|
+
- **Targets**: browsers, Deno, Node 19+, Bun (Web Crypto global required).
|
|
12
|
+
|
|
13
|
+
## Project Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/src
|
|
17
|
+
mod.ts — core entry point (re-exports everything except rhr)
|
|
18
|
+
uid.ts — uid() dispatcher, strategy registry, per-strategy option types
|
|
19
|
+
random.ts — crypto core: randomBytes, randomString (rejection sampling),
|
|
20
|
+
ALPHABETS, DEFAULT_LENGTH, assert helpers
|
|
21
|
+
uuid.ts — uuid (v4), uuidv7, ulid
|
|
22
|
+
base56.ts — base56 random + reversible uuid<->base56 (fixed 23 chars)
|
|
23
|
+
alphabet.ts — nanoid, hex, base32, base36, base58, base62, numeric
|
|
24
|
+
counter.ts — counter (shared, per-prefix), createCounter, resetCounters
|
|
25
|
+
reversible.ts — encodeInt / decodeInt (salt-shuffled, reversible)
|
|
26
|
+
rhr.ts — OPT-IN subpath "@marianmeres/uid/rhr": rhr() + self-registers
|
|
27
|
+
/tests — one *.test.ts per src module
|
|
28
|
+
/scripts
|
|
29
|
+
build-npm.ts — npm distribution builder (dnt via @marianmeres/npmbuild)
|
|
30
|
+
mcp.ts — MCP tool definitions (generate-uid + base56 / integer codecs)
|
|
31
|
+
mcp-include.txt — MCP discovery description (opts the package into the MCP server)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## MCP Tools (mcp.ts)
|
|
35
|
+
|
|
36
|
+
Exposed to AI agents via `@marianmeres/mcp-server` (namespaced `uid:<tool>`):
|
|
37
|
+
`generate-uid` (any strategy + options; imports `./src/rhr.ts` so `rhr` works),
|
|
38
|
+
`uuid-to-base56`, `base56-to-uuid`, `encode-int`, `decode-int`. Handlers return
|
|
39
|
+
plain strings; every param uses `.describe()`. Keep tools in sync with the public
|
|
40
|
+
API when strategies change.
|
|
41
|
+
|
|
42
|
+
## Public API
|
|
43
|
+
|
|
44
|
+
Entry `@marianmeres/uid`: `uid(strategy?, options?)` dispatcher plus standalone
|
|
45
|
+
named exports for every strategy.
|
|
46
|
+
|
|
47
|
+
| Export | Signature |
|
|
48
|
+
| ------------------------------------------------------------------------ | ------------------------------------------------------------------- |
|
|
49
|
+
| `uid` | `(strategy?: string, options?) => string` (overloaded per strategy) |
|
|
50
|
+
| `uuid` / `uuidv7` / `ulid` | `() => string` / `(timestamp?) => string` |
|
|
51
|
+
| `base56` / `base56Uuid` | `(length?) => string` / `() => string` |
|
|
52
|
+
| `uuidToBase56` / `base56ToUuid` | `(string) => string` (reversible) |
|
|
53
|
+
| `nanoid` / `hex` / `base32` / `base36` / `base58` / `base62` / `numeric` | `(length?, …) => string` |
|
|
54
|
+
| `randomString` / `randomBytes` | `(length, alphabet?) => string` / `(size) => Uint8Array` |
|
|
55
|
+
| `counter` / `createCounter` / `resetCounters` | counter helpers |
|
|
56
|
+
| `encodeInt` / `decodeInt` | reversible integer codec |
|
|
57
|
+
| `registerStrategy` / `listStrategies` | registry |
|
|
58
|
+
| `ALPHABETS` / `DEFAULT_LENGTH` | constants |
|
|
59
|
+
|
|
60
|
+
Subpath `@marianmeres/uid/rhr`: `rhr(options?) => string` (also registers the
|
|
61
|
+
`rhr` strategy on import).
|
|
62
|
+
|
|
63
|
+
## Critical Conventions
|
|
64
|
+
|
|
65
|
+
1. **Core is zero-dependency.** Never import `@marianmeres/random-human-readable`
|
|
66
|
+
from any module except `src/rhr.ts`. It is an _optional peer dep_ surfaced
|
|
67
|
+
only via the `./rhr` subpath so its word lists stay out of the core bundle.
|
|
68
|
+
2. **All randomness flows through `random.ts`** (`randomBytes` / `randomString`,
|
|
69
|
+
Web Crypto + rejection sampling). Never use `Math.random`.
|
|
70
|
+
3. **Adding a strategy** = (a) implement it as a standalone named export in the
|
|
71
|
+
relevant file, (b) `registerStrategy("name", …)` in `src/uid.ts`, (c) add a
|
|
72
|
+
typed overload signature to `uid` and an options interface. Keep both the
|
|
73
|
+
named export and the dispatcher path working.
|
|
74
|
+
4. **Explicit return types on every exported symbol** — JSR rejects slow types.
|
|
75
|
+
Verify with `deno publish --dry-run` before publishing.
|
|
76
|
+
5. **base56-from-uuid is fixed-width 23 chars** (128 bits need 23 base56 digits;
|
|
77
|
+
`56^22 < 2^128`). Do not "fix" it back to 22.
|
|
78
|
+
6. **Multi-file layout is intentional** (tree-shaking). Keep pure modules pure;
|
|
79
|
+
`counter.ts` holds the only module-level mutable state (the shared counters).
|
|
80
|
+
7. **rhr strategy is opt-in**: `uid("rhr")` throws a helpful error until
|
|
81
|
+
`import "@marianmeres/uid/rhr"` registers it. Tests must isolate this (see
|
|
82
|
+
`tests/rhr.test.ts` — dynamic import, the only file touching the subpath).
|
|
83
|
+
|
|
84
|
+
## Validation Rules (enforced at runtime)
|
|
85
|
+
|
|
86
|
+
| Input | Constraint | Throws |
|
|
87
|
+
| ----------------------------------------- | ----------------------------------------- | -------------------------- |
|
|
88
|
+
| `randomString` length, `randomBytes` size | positive integer | `TypeError` |
|
|
89
|
+
| `randomString` alphabet | string, 1–256 chars | `TypeError` / `RangeError` |
|
|
90
|
+
| `uuidv7` / `ulid` timestamp | integer in `[0, 2^48)` | `RangeError` |
|
|
91
|
+
| `uuidToBase56` input | valid 32-hex-digit UUID | `TypeError` |
|
|
92
|
+
| `counter` prefix/start/step/pad | string / int / positive int / non-neg int | `TypeError` |
|
|
93
|
+
| `encodeInt` value | safe non-negative integer | `RangeError` |
|
|
94
|
+
| reversible alphabet | ≥ 2 unique characters | `RangeError` |
|
|
95
|
+
| `uid(strategy)` unknown name | — | `Error` (lists available) |
|
|
96
|
+
| `uid("custom")` / `uid("reversible")` | required `alphabet` / `value` | `TypeError` |
|
|
97
|
+
|
|
98
|
+
## Documentation Index
|
|
99
|
+
|
|
100
|
+
- [README.md](./README.md) — user-facing overview and usage
|
|
101
|
+
- [API.md](./API.md) — complete API reference
|
|
102
|
+
|
|
103
|
+
## Before Making Changes
|
|
104
|
+
|
|
105
|
+
- [ ] Check existing patterns in the relevant `src/*.ts` and its test
|
|
106
|
+
- [ ] `deno task test` (add/extend tests for new behavior)
|
|
107
|
+
- [ ] `deno fmt` and `deno lint`
|
|
108
|
+
- [ ] `deno publish --dry-run --allow-dirty` (no slow types)
|
|
109
|
+
- [ ] `deno task npm:build` if deps, entry points, or build config changed
|
|
110
|
+
- [ ] If the public API changed: update [README.md](./README.md),
|
|
111
|
+
[API.md](./API.md), and this file
|
package/API.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
Complete reference for `@marianmeres/uid`.
|
|
4
|
+
|
|
5
|
+
- Core entry point: `@marianmeres/uid` — everything below **except** `rhr`.
|
|
6
|
+
- Opt-in subpath: `@marianmeres/uid/rhr` — the `rhr` function and strategy.
|
|
7
|
+
|
|
8
|
+
All generators are crypto-backed (`crypto.getRandomValues` / `crypto.randomUUID`)
|
|
9
|
+
and work in browsers, Deno, Node 19+, and Bun. Randomness is good quality but
|
|
10
|
+
**not** cryptographically secret — do not use for tokens, keys, or password
|
|
11
|
+
resets.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The dispatcher
|
|
16
|
+
|
|
17
|
+
### `uid(strategy?, options?)`
|
|
18
|
+
|
|
19
|
+
Generates a unique id using the named `strategy`. Defaults to `uuid` (v4). The
|
|
20
|
+
`options` shape is validated per strategy (TypeScript overloads narrow it from
|
|
21
|
+
the strategy name). The `rhr` strategy must be enabled first via
|
|
22
|
+
`import "@marianmeres/uid/rhr"`.
|
|
23
|
+
|
|
24
|
+
**Parameters:**
|
|
25
|
+
|
|
26
|
+
- `strategy` (string, optional) — one of: `uuid`, `uuidv4`, `uuidv7`, `ulid`,
|
|
27
|
+
`base56`, `nanoid`, `hex`, `base32`, `base36`, `base58`, `base62`, `numeric`,
|
|
28
|
+
`custom`, `counter`, `reversible`, `rhr`, or any name registered via
|
|
29
|
+
`registerStrategy`. Default: `"uuid"`.
|
|
30
|
+
- `options` (object, optional) — strategy-specific; see each strategy below.
|
|
31
|
+
|
|
32
|
+
**Returns:** `string`
|
|
33
|
+
|
|
34
|
+
**Throws:** `Error` if the strategy is unknown (message lists available
|
|
35
|
+
strategies), or a strategy-specific `TypeError`/`RangeError` for invalid options.
|
|
36
|
+
|
|
37
|
+
**Example:**
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { uid } from "@marianmeres/uid";
|
|
41
|
+
|
|
42
|
+
uid(); // uuid v4
|
|
43
|
+
uid("uuidv7"); // sortable uuid
|
|
44
|
+
uid("nanoid", { length: 12 }); // "V1StGXR8_Z5j"
|
|
45
|
+
uid("custom", { alphabet: "AB01", length: 6 });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Strategy | Options type | Backing function |
|
|
49
|
+
| ----------------- | ------------------- | ---------------------------------- |
|
|
50
|
+
| `uuid`/`uuidv4` | `UuidOptions` | `uuid()` |
|
|
51
|
+
| `uuidv7` | `Uuidv7Options` | `uuidv7(timestamp)` |
|
|
52
|
+
| `ulid` | `UlidOptions` | `ulid(timestamp)` |
|
|
53
|
+
| `base56` | `Base56Options` | `base56()` / `uuidToBase56()` |
|
|
54
|
+
| `nanoid` | `NanoidOptions` | `nanoid(length, alphabet)` |
|
|
55
|
+
| `hex` … `numeric` | `AlphabetOptions` | `hex(length)`, `base58(length)`, … |
|
|
56
|
+
| `custom` | `CustomOptions` | `randomString(length, alphabet)` |
|
|
57
|
+
| `counter` | `CounterOptions` | `counter(options)` |
|
|
58
|
+
| `reversible` | `ReversibleOptions` | `encodeInt(value, options)` |
|
|
59
|
+
| `rhr` | `RhrOptions` | `rhr(options)` _(opt-in)_ |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## UUID & sortable strategies
|
|
64
|
+
|
|
65
|
+
### `uuid()`
|
|
66
|
+
|
|
67
|
+
Random RFC 9562 UUID v4 via native `crypto.randomUUID()`.
|
|
68
|
+
|
|
69
|
+
**Returns:** `string` — e.g. `"1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"`
|
|
70
|
+
|
|
71
|
+
### `uuidv7(timestamp?)`
|
|
72
|
+
|
|
73
|
+
RFC 9562 UUID v7 — a 48-bit Unix-millisecond timestamp prefix plus random bits.
|
|
74
|
+
Valid UUID **and** lexicographically sortable by creation time. Ideal for
|
|
75
|
+
database primary keys.
|
|
76
|
+
|
|
77
|
+
**Parameters:**
|
|
78
|
+
|
|
79
|
+
- `timestamp` (number, optional) — Unix epoch milliseconds to embed. Must be in
|
|
80
|
+
`[0, 2^48)`. Default: `Date.now()`.
|
|
81
|
+
|
|
82
|
+
**Returns:** `string`
|
|
83
|
+
**Throws:** `RangeError` if `timestamp` is outside `[0, 2^48)`.
|
|
84
|
+
|
|
85
|
+
**Example:**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
uuidv7(); // "0192f6c4-1d2e-7a3b-8c4d-5e6f7a8b9c0d"
|
|
89
|
+
uuidv7(0); // deterministic time prefix (tests / back-dating)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `ulid(timestamp?)`
|
|
93
|
+
|
|
94
|
+
ULID — 26-character Crockford-base32 string: 48-bit ms timestamp (10 chars) +
|
|
95
|
+
80 bits of randomness (16 chars). Sorts by time like UUID v7, but shorter,
|
|
96
|
+
dash-free, and case-insensitive (not a valid UUID string).
|
|
97
|
+
|
|
98
|
+
**Parameters:**
|
|
99
|
+
|
|
100
|
+
- `timestamp` (number, optional) — Unix epoch milliseconds. Must be in
|
|
101
|
+
`[0, 2^48)`. Default: `Date.now()`.
|
|
102
|
+
|
|
103
|
+
**Returns:** `string` — e.g. `"01J9Z7Q8K3M4N5P6R7S8T9V0W1"`
|
|
104
|
+
**Throws:** `RangeError` if `timestamp` is outside `[0, 2^48)`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Base56 strategy
|
|
109
|
+
|
|
110
|
+
Human-friendly alphabet with no ambiguous characters (`0 O o 1 l I`).
|
|
111
|
+
|
|
112
|
+
### `base56(length?)`
|
|
113
|
+
|
|
114
|
+
Random base56 string.
|
|
115
|
+
|
|
116
|
+
**Parameters:** `length` (number, optional) — default `DEFAULT_LENGTH` (21).
|
|
117
|
+
**Returns:** `string`
|
|
118
|
+
|
|
119
|
+
### `uuidToBase56(uuid)`
|
|
120
|
+
|
|
121
|
+
Encodes a UUID into a fixed-width **23-character** base56 string. Reversible via
|
|
122
|
+
`base56ToUuid`. (128 bits need 23 base56 digits — `56^22 ≈ 2^127.76 < 2^128`.)
|
|
123
|
+
|
|
124
|
+
**Parameters:** `uuid` (string) — a UUID, with or without dashes.
|
|
125
|
+
**Returns:** `string` (23 chars)
|
|
126
|
+
**Throws:** `TypeError` if `uuid` is not a valid 32-hex-digit UUID.
|
|
127
|
+
|
|
128
|
+
### `base56ToUuid(encoded)`
|
|
129
|
+
|
|
130
|
+
Decodes a base56 string from `uuidToBase56` back to a canonical UUID.
|
|
131
|
+
|
|
132
|
+
**Parameters:** `encoded` (string)
|
|
133
|
+
**Returns:** `string` — canonical UUID
|
|
134
|
+
**Throws:** `Error` if `encoded` contains characters outside the base56 alphabet.
|
|
135
|
+
|
|
136
|
+
### `base56Uuid()`
|
|
137
|
+
|
|
138
|
+
Generates a fresh UUID and returns its 23-character base56 encoding. Equivalent
|
|
139
|
+
to `uuidToBase56(uuid())`.
|
|
140
|
+
|
|
141
|
+
**Returns:** `string` (23 chars)
|
|
142
|
+
|
|
143
|
+
**Example:**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const id = base56Uuid(); // "2WjkHTAH3tTuAHceeZxnuDL"
|
|
147
|
+
base56ToUuid(id); // the original uuid
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Alphabet strategies
|
|
153
|
+
|
|
154
|
+
Fixed-length random strings over a named alphabet. Each takes an optional
|
|
155
|
+
`length` (default `DEFAULT_LENGTH` = 21) and returns a `string`.
|
|
156
|
+
|
|
157
|
+
| Function | Alphabet |
|
|
158
|
+
| ---------------------------- | ------------------------------------------------------------- |
|
|
159
|
+
| `nanoid(length?, alphabet?)` | URL-safe 64-char (`A–Z a–z 0–9 _ -`); custom alphabet allowed |
|
|
160
|
+
| `hex(length?)` | `0-9 a-f` |
|
|
161
|
+
| `base32(length?)` | Crockford (no I L O U) |
|
|
162
|
+
| `base36(length?)` | `0-9 a-z` |
|
|
163
|
+
| `base58(length?)` | Bitcoin (no 0 O I l) |
|
|
164
|
+
| `base62(length?)` | `0-9 A-Z a-z` |
|
|
165
|
+
| `numeric(length?)` | `0-9` (OTP / numeric coupons) |
|
|
166
|
+
|
|
167
|
+
**Example:**
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
nanoid(); // 21-char URL-safe id
|
|
171
|
+
hex(32); // 32-char hex
|
|
172
|
+
numeric(6); // "048217"
|
|
173
|
+
nanoid(10, "ABCDEF01"); // custom alphabet
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Counter strategy
|
|
179
|
+
|
|
180
|
+
In-memory, monotonically increasing ids. State lives for the lifetime of the
|
|
181
|
+
process/page and resets on reload — **not** for cross-process uniqueness.
|
|
182
|
+
|
|
183
|
+
### `counter(options?)`
|
|
184
|
+
|
|
185
|
+
Returns the next value of a shared, per-prefix counter. Each distinct `prefix`
|
|
186
|
+
has its own sequence. `start`/`step`/`pad` are honored only on the first call
|
|
187
|
+
that creates a prefix's sequence.
|
|
188
|
+
|
|
189
|
+
**Parameters:** `options` (`CounterOptions`, optional)
|
|
190
|
+
**Returns:** `string`
|
|
191
|
+
**Throws:** `TypeError` for invalid `prefix`/`start`/`step`/`pad`.
|
|
192
|
+
|
|
193
|
+
### `createCounter(options?)`
|
|
194
|
+
|
|
195
|
+
Creates an isolated counter function with its own private state — no sharing with
|
|
196
|
+
`counter()` or other instances. Prefer this for independent sequences or
|
|
197
|
+
deterministic tests.
|
|
198
|
+
|
|
199
|
+
**Parameters:** `options` (`CounterOptions`, optional)
|
|
200
|
+
**Returns:** `() => string`
|
|
201
|
+
|
|
202
|
+
### `resetCounters(prefix?)`
|
|
203
|
+
|
|
204
|
+
Clears the shared state used by `counter()`. Mainly for tests. Does not affect
|
|
205
|
+
`createCounter` instances.
|
|
206
|
+
|
|
207
|
+
**Parameters:** `prefix` (string, optional) — reset only this prefix; omit to
|
|
208
|
+
reset all.
|
|
209
|
+
**Returns:** `void`
|
|
210
|
+
|
|
211
|
+
**Example:**
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const next = createCounter({ prefix: "row-", start: 1, pad: 4 });
|
|
215
|
+
next(); // "row-0001"
|
|
216
|
+
next(); // "row-0002"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Reversible integer codec
|
|
222
|
+
|
|
223
|
+
A small "sqids/hashids-lite": encode a non-negative integer to a short string
|
|
224
|
+
and back. A `salt` deterministically shuffles the alphabet so sequential ids
|
|
225
|
+
don't look sequential. **Obfuscation, not encryption** — reversible by anyone
|
|
226
|
+
who knows the salt and alphabet.
|
|
227
|
+
|
|
228
|
+
### `encodeInt(value, options?)`
|
|
229
|
+
|
|
230
|
+
**Parameters:**
|
|
231
|
+
|
|
232
|
+
- `value` (number) — non-negative safe integer (`0 … Number.MAX_SAFE_INTEGER`).
|
|
233
|
+
- `options` (`EncodeIntOptions`, optional)
|
|
234
|
+
|
|
235
|
+
**Returns:** `string`
|
|
236
|
+
**Throws:** `RangeError` if `value` is out of range, or `alphabet` has fewer than
|
|
237
|
+
2 characters or contains duplicates.
|
|
238
|
+
|
|
239
|
+
### `decodeInt(str, options?)`
|
|
240
|
+
|
|
241
|
+
**Parameters:**
|
|
242
|
+
|
|
243
|
+
- `str` (string) — a string produced by `encodeInt`.
|
|
244
|
+
- `options` (`DecodeIntOptions`, optional) — `alphabet`/`salt` must match those
|
|
245
|
+
used to encode.
|
|
246
|
+
|
|
247
|
+
**Returns:** `number`
|
|
248
|
+
**Throws:** `Error` for characters outside the alphabet; `RangeError` for a bad
|
|
249
|
+
alphabet or if the decoded value exceeds the safe-integer range.
|
|
250
|
+
|
|
251
|
+
**Example:**
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const code = encodeInt(12345, { salt: "my-secret" }); // "Yq9"
|
|
255
|
+
decodeInt(code, { salt: "my-secret" }); // 12345
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Core primitives
|
|
261
|
+
|
|
262
|
+
### `randomString(length, alphabet?)`
|
|
263
|
+
|
|
264
|
+
Random string of `length` characters from `alphabet`, using rejection sampling
|
|
265
|
+
(the nanoid algorithm) so output is unbiased even for non-power-of-two alphabets.
|
|
266
|
+
|
|
267
|
+
**Parameters:**
|
|
268
|
+
|
|
269
|
+
- `length` (number) — positive integer.
|
|
270
|
+
- `alphabet` (string, optional) — 1–256 characters. Default: the URL-safe nanoid
|
|
271
|
+
alphabet.
|
|
272
|
+
|
|
273
|
+
**Returns:** `string`
|
|
274
|
+
**Throws:** `TypeError` for non-positive-integer `length` or non-string
|
|
275
|
+
`alphabet`; `RangeError` if `alphabet` length is outside 1–256.
|
|
276
|
+
|
|
277
|
+
### `randomBytes(size)`
|
|
278
|
+
|
|
279
|
+
`size` cryptographically-strong random bytes. Transparently chunks calls larger
|
|
280
|
+
than the 65536-byte `getRandomValues` limit.
|
|
281
|
+
|
|
282
|
+
**Parameters:** `size` (number) — positive integer.
|
|
283
|
+
**Returns:** `Uint8Array`
|
|
284
|
+
|
|
285
|
+
### `assertPositiveInt(n, label)` / `assertNonNegativeInt(n, label)`
|
|
286
|
+
|
|
287
|
+
Throw `TypeError` unless `n` is an integer `>= 1` / `>= 0`. The `label` is used
|
|
288
|
+
in the error message. Exposed for building custom strategies with consistent
|
|
289
|
+
validation.
|
|
290
|
+
|
|
291
|
+
**Returns:** `void` (assertion functions)
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Registry
|
|
296
|
+
|
|
297
|
+
### `registerStrategy(name, fn)`
|
|
298
|
+
|
|
299
|
+
Registers (or overrides) a strategy under `name`, making it callable via
|
|
300
|
+
`uid(name, options)`.
|
|
301
|
+
|
|
302
|
+
**Parameters:**
|
|
303
|
+
|
|
304
|
+
- `name` (string) — non-empty strategy name.
|
|
305
|
+
- `fn` (`StrategyFn`) — `(options?) => string`.
|
|
306
|
+
|
|
307
|
+
**Returns:** `void`
|
|
308
|
+
**Throws:** `TypeError` for an empty `name` or non-function `fn`.
|
|
309
|
+
|
|
310
|
+
### `listStrategies()`
|
|
311
|
+
|
|
312
|
+
**Returns:** `string[]` — names of all currently registered strategies.
|
|
313
|
+
|
|
314
|
+
**Example:**
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { registerStrategy, uid } from "@marianmeres/uid";
|
|
318
|
+
|
|
319
|
+
registerStrategy("order", () => "ORD-" + uid("numeric", { length: 8 }));
|
|
320
|
+
uid("order"); // "ORD-40582193"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Opt-in: `@marianmeres/uid/rhr`
|
|
326
|
+
|
|
327
|
+
### `rhr(options?)`
|
|
328
|
+
|
|
329
|
+
Human-readable id built on `@marianmeres/random-human-readable`. Joined with `-`
|
|
330
|
+
by default. Importing this module also registers the `rhr` strategy so
|
|
331
|
+
`uid("rhr", …)` works.
|
|
332
|
+
|
|
333
|
+
**Parameters:** `options` (`RhrOptions`, optional) — passed through to
|
|
334
|
+
`getRandomHumanReadable` (e.g. `adjCount`, `colorsCount`, `nounsCount`,
|
|
335
|
+
`randomizeCase`, `joinWith`).
|
|
336
|
+
|
|
337
|
+
**Returns:** `string` — e.g. `"happy-blue-otter-canyon"`
|
|
338
|
+
|
|
339
|
+
**Example:**
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import "@marianmeres/uid/rhr"; // enables uid("rhr")
|
|
343
|
+
import { rhr } from "@marianmeres/uid/rhr";
|
|
344
|
+
|
|
345
|
+
rhr(); // "happy-blue-otter-canyon"
|
|
346
|
+
rhr({ nounsCount: 1 }); // "otter"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
On npm, install the peer dependency:
|
|
350
|
+
`npm install @marianmeres/random-human-readable`.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Types
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
interface Uuidv7Options {
|
|
358
|
+
timestamp?: number;
|
|
359
|
+
}
|
|
360
|
+
interface UlidOptions {
|
|
361
|
+
timestamp?: number;
|
|
362
|
+
}
|
|
363
|
+
interface Base56Options {
|
|
364
|
+
length?: number;
|
|
365
|
+
uuid?: string;
|
|
366
|
+
}
|
|
367
|
+
interface NanoidOptions {
|
|
368
|
+
length?: number;
|
|
369
|
+
alphabet?: string;
|
|
370
|
+
}
|
|
371
|
+
interface AlphabetOptions {
|
|
372
|
+
length?: number;
|
|
373
|
+
}
|
|
374
|
+
interface CustomOptions {
|
|
375
|
+
alphabet: string;
|
|
376
|
+
length?: number;
|
|
377
|
+
}
|
|
378
|
+
interface CounterOptions {
|
|
379
|
+
prefix?: string;
|
|
380
|
+
start?: number;
|
|
381
|
+
step?: number;
|
|
382
|
+
pad?: number;
|
|
383
|
+
}
|
|
384
|
+
interface EncodeIntOptions {
|
|
385
|
+
alphabet?: string;
|
|
386
|
+
salt?: string;
|
|
387
|
+
minLength?: number;
|
|
388
|
+
}
|
|
389
|
+
interface DecodeIntOptions {
|
|
390
|
+
alphabet?: string;
|
|
391
|
+
salt?: string;
|
|
392
|
+
}
|
|
393
|
+
interface ReversibleOptions {
|
|
394
|
+
value: number;
|
|
395
|
+
alphabet?: string;
|
|
396
|
+
salt?: string;
|
|
397
|
+
minLength?: number;
|
|
398
|
+
}
|
|
399
|
+
interface RhrOptions {/* getRandomHumanReadable options that return a string */}
|
|
400
|
+
type StrategyFn = (options?: Record<string, unknown>) => string;
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Constants
|
|
406
|
+
|
|
407
|
+
### `DEFAULT_LENGTH`
|
|
408
|
+
|
|
409
|
+
`21` — default length for random, fixed-length strategies.
|
|
410
|
+
|
|
411
|
+
### `ALPHABETS`
|
|
412
|
+
|
|
413
|
+
Named alphabets used by the built-in strategies:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
ALPHABETS.hex; // "0123456789abcdef"
|
|
417
|
+
ALPHABETS.base32; // Crockford (no I L O U)
|
|
418
|
+
ALPHABETS.base36; // "0-9a-z"
|
|
419
|
+
ALPHABETS.base56; // no 0 O o 1 l I
|
|
420
|
+
ALPHABETS.base58; // Bitcoin (no 0 O I l)
|
|
421
|
+
ALPHABETS.base62; // "0-9A-Za-z"
|
|
422
|
+
ALPHABETS.numeric; // "0123456789"
|
|
423
|
+
ALPHABETS.alphanumeric; // alias of base62
|
|
424
|
+
ALPHABETS.nanoid; // URL-safe 64-char set
|
|
425
|
+
```
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marian Meres
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|