@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/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# @marianmeres/uid
|
|
2
|
+
|
|
3
|
+
[](https://jsr.io/@marianmeres/uid)
|
|
4
|
+
[](https://www.npmjs.com/package/@marianmeres/uid)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
One `uid()` function, many id strategies — UUIDs, sortable ids (UUID v7 / ULID),
|
|
8
|
+
URL-safe random ids (nanoid-style), human-friendly alphabets (base56/58/62/…),
|
|
9
|
+
in-memory counters, reversible integer codecs, and human-readable ids.
|
|
10
|
+
|
|
11
|
+
- **Works everywhere** — browsers, Deno, Node 19+, Bun.
|
|
12
|
+
- **Crypto-backed** — all randomness comes from the Web Crypto API, with
|
|
13
|
+
rejection sampling so non-power-of-two alphabets stay unbiased.
|
|
14
|
+
- **Zero dependencies** in the core (the optional `rhr` strategy is the only
|
|
15
|
+
add-on, and it's opt-in).
|
|
16
|
+
- **Type-safe** — `options` are checked per strategy, and every strategy is also
|
|
17
|
+
a standalone, tree-shakeable named export.
|
|
18
|
+
|
|
19
|
+
> Good-quality randomness, but **not** intended for cryptographic secrets
|
|
20
|
+
> (tokens, keys, password resets). Use a dedicated crypto routine for those.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# deno
|
|
26
|
+
deno add jsr:@marianmeres/uid
|
|
27
|
+
|
|
28
|
+
# npm (or pnpm / yarn / bun)
|
|
29
|
+
npx jsr add @marianmeres/uid
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { uid } from "@marianmeres/uid";
|
|
36
|
+
|
|
37
|
+
uid(); // uuid v4 (default)
|
|
38
|
+
uid("uuidv7"); // sortable, UUID-compatible
|
|
39
|
+
uid("ulid"); // sortable, 26-char Crockford base32
|
|
40
|
+
uid("nanoid", { length: 12 }); // "V1StGXR8_Z5j"
|
|
41
|
+
uid("base58", { length: 10 }); // "3kP9xQ2mWz"
|
|
42
|
+
uid("numeric", { length: 6 }); // "048217" (OTP / coupon)
|
|
43
|
+
uid("counter", { prefix: "n" }); // "n0", "n1", "n2", …
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Every strategy is also a direct, tree-shakeable named export — handy when you
|
|
47
|
+
want one specific generator without the dispatcher:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { base62, nanoid, ulid, uuidv7 } from "@marianmeres/uid";
|
|
51
|
+
|
|
52
|
+
uuidv7(); // "0192f6c4-1d2e-7a3b-8c4d-5e6f7a8b9c0d"
|
|
53
|
+
nanoid(); // 21-char URL-safe id
|
|
54
|
+
base62(8); // "aZ09Kp3X"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Strategies
|
|
58
|
+
|
|
59
|
+
| Strategy | `uid(...)` | Output |
|
|
60
|
+
| --------------------------------------------------- | -------------------------------------------------- | --------------------------------------- |
|
|
61
|
+
| `uuid` / `uuidv4` | `uid()` / `uid("uuid")` | RFC 9562 UUID v4 |
|
|
62
|
+
| `uuidv7` | `uid("uuidv7", { timestamp? })` | Time-sortable UUID |
|
|
63
|
+
| `ulid` | `uid("ulid", { timestamp? })` | 26-char Crockford base32, sortable |
|
|
64
|
+
| `base56` | `uid("base56", { length?, uuid? })` | No-ambiguous random / reversible uuid |
|
|
65
|
+
| `nanoid` | `uid("nanoid", { length?, alphabet? })` | URL-safe random id |
|
|
66
|
+
| `hex` `base32` `base36` `base58` `base62` `numeric` | `uid("base58", { length? })` | Random string over a named alphabet |
|
|
67
|
+
| `custom` | `uid("custom", { alphabet, length? })` | Random string over your alphabet |
|
|
68
|
+
| `counter` | `uid("counter", { prefix?, start?, step?, pad? })` | In-memory incrementing id |
|
|
69
|
+
| `reversible` | `uid("reversible", { value, salt?, … })` | Short reversible encoding of an integer |
|
|
70
|
+
| `rhr` | `uid("rhr", { … })` _(opt-in, see below)_ | Human-readable id |
|
|
71
|
+
|
|
72
|
+
Default length for random strategies is **21**. A UUID encoded as base56 is
|
|
73
|
+
always **23** characters (128 bits need 23 base56 digits).
|
|
74
|
+
|
|
75
|
+
### Sortable ids — `uuidv7` vs `ulid`
|
|
76
|
+
|
|
77
|
+
Both embed a 48-bit millisecond timestamp prefix and sort by creation time.
|
|
78
|
+
`uuidv7` is a valid UUID (drops into any `uuid` DB column); `ulid` is shorter,
|
|
79
|
+
dash-free, and case-insensitive but not a valid UUID string.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
uid("uuidv7"); // "0192f6c4-1d2e-7a3b-..."
|
|
83
|
+
uid("ulid"); // "01J9Z7Q8K3M4N5P6R7S8T9V0W1"
|
|
84
|
+
uid("uuidv7", { timestamp: 0 }); // back-dated / deterministic time for tests
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### base56 — random or reversible UUID
|
|
88
|
+
|
|
89
|
+
`base56` drops visually ambiguous characters (`0 O o 1 l I`), so it's safe to
|
|
90
|
+
read aloud or retype.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { base56, base56ToUuid, base56Uuid, uuidToBase56 } from "@marianmeres/uid";
|
|
94
|
+
|
|
95
|
+
base56(10); // random, e.g. "7vNpRmXj4Q"
|
|
96
|
+
base56Uuid(); // fresh uuid, 23-char base56 (reversible)
|
|
97
|
+
|
|
98
|
+
const enc = uuidToBase56("550e8400-e29b-41d4-a716-446655440000");
|
|
99
|
+
base56ToUuid(enc); // back to the original uuid
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### counter — in-memory sequence
|
|
103
|
+
|
|
104
|
+
State lives for the lifetime of the process/page (it resets on reload) and is
|
|
105
|
+
**not** for cross-process uniqueness. Each prefix has its own sequence.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { createCounter } from "@marianmeres/uid";
|
|
109
|
+
|
|
110
|
+
uid("counter", { prefix: "node_" }); // "node_0", "node_1", …
|
|
111
|
+
uid("counter", { prefix: "x", pad: 4 }); // "x0000", "x0001", …
|
|
112
|
+
|
|
113
|
+
// fully isolated instance (best for tests / multiple independent sequences):
|
|
114
|
+
const next = createCounter({ prefix: "row-", start: 1, pad: 4 });
|
|
115
|
+
next(); // "row-0001"
|
|
116
|
+
next(); // "row-0002"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### reversible — encode integers ↔ short strings
|
|
120
|
+
|
|
121
|
+
A small "sqids/hashids-lite". With a `salt` the alphabet is deterministically
|
|
122
|
+
shuffled so sequential ids don't look sequential — useful for exposing database
|
|
123
|
+
row ids in URLs without leaking the row count.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { decodeInt, encodeInt } from "@marianmeres/uid";
|
|
127
|
+
|
|
128
|
+
const code = encodeInt(12345, { salt: "my-secret" }); // e.g. "Yq9"
|
|
129
|
+
decodeInt(code, { salt: "my-secret" }); // 12345
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> Like hashids, this is **obfuscation, not encryption** — anyone who knows the
|
|
133
|
+
> salt and alphabet can reverse it. Don't use it to protect data.
|
|
134
|
+
|
|
135
|
+
### rhr — human-readable ids (opt-in)
|
|
136
|
+
|
|
137
|
+
Human-readable ids are built on
|
|
138
|
+
[`@marianmeres/random-human-readable`](https://jsr.io/@marianmeres/random-human-readable).
|
|
139
|
+
To keep its word lists out of the core bundle, this strategy lives behind a
|
|
140
|
+
subpath you import explicitly:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// register the strategy once (e.g. in your entry file):
|
|
144
|
+
import "@marianmeres/uid/rhr";
|
|
145
|
+
import { uid } from "@marianmeres/uid";
|
|
146
|
+
uid("rhr"); // "happy-blue-otter-canyon"
|
|
147
|
+
|
|
148
|
+
// …or call it directly (tree-shakeable, no registration needed):
|
|
149
|
+
import { rhr } from "@marianmeres/uid/rhr";
|
|
150
|
+
rhr({ nounsCount: 1 });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
On npm, install the peer dependency yourself:
|
|
154
|
+
`npm install @marianmeres/random-human-readable`.
|
|
155
|
+
|
|
156
|
+
## Custom alphabets & strategies
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { randomString, registerStrategy, uid } from "@marianmeres/uid";
|
|
160
|
+
|
|
161
|
+
// arbitrary alphabet, one-off:
|
|
162
|
+
uid("custom", { alphabet: "ABCDEF01", length: 8 });
|
|
163
|
+
randomString(8, "ABCDEF01");
|
|
164
|
+
|
|
165
|
+
// register your own reusable strategy:
|
|
166
|
+
registerStrategy("order", () => "ORD-" + uid("numeric", { length: 8 }));
|
|
167
|
+
uid("order"); // "ORD-40582193"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Validation
|
|
171
|
+
|
|
172
|
+
Options are validated rather than coerced: non-positive/fractional lengths,
|
|
173
|
+
out-of-range UUID v7 / ULID timestamps, missing required options (`custom`'s
|
|
174
|
+
`alphabet`, `reversible`'s `value`), duplicate reversible alphabets, and invalid
|
|
175
|
+
counter options all throw `TypeError` / `RangeError`.
|
|
176
|
+
|
|
177
|
+
## API
|
|
178
|
+
|
|
179
|
+
See [API.md](API.md) for the complete API reference.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-length random strings over the built-in named alphabets.
|
|
3
|
+
*
|
|
4
|
+
* These are thin, well-named wrappers around {@link randomString} so the most
|
|
5
|
+
* common cases read cleanly: `hex(32)`, `numeric(6)`, `base62()`, …
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* nanoid-style id: URL-safe (`A–Z a–z 0–9 _ -`), crypto-backed, unbiased.
|
|
9
|
+
*
|
|
10
|
+
* @param length Number of characters (default: {@link DEFAULT_LENGTH}).
|
|
11
|
+
* @param alphabet Override the alphabet (default: URL-safe 64-char set).
|
|
12
|
+
*/
|
|
13
|
+
export declare function nanoid(length?: number, alphabet?: string): string;
|
|
14
|
+
/** Random lowercase hex string. */
|
|
15
|
+
export declare function hex(length?: number): string;
|
|
16
|
+
/** Random Crockford base32 string (no I, L, O, U). */
|
|
17
|
+
export declare function base32(length?: number): string;
|
|
18
|
+
/** Random lowercase base36 string. */
|
|
19
|
+
export declare function base36(length?: number): string;
|
|
20
|
+
/** Random Bitcoin base58 string (no 0, O, I, l). */
|
|
21
|
+
export declare function base58(length?: number): string;
|
|
22
|
+
/** Random full base62 string. */
|
|
23
|
+
export declare function base62(length?: number): string;
|
|
24
|
+
/** Random digits-only string — handy for OTPs / numeric coupon codes. */
|
|
25
|
+
export declare function numeric(length?: number): string;
|
package/dist/alphabet.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-length random strings over the built-in named alphabets.
|
|
3
|
+
*
|
|
4
|
+
* These are thin, well-named wrappers around {@link randomString} so the most
|
|
5
|
+
* common cases read cleanly: `hex(32)`, `numeric(6)`, `base62()`, …
|
|
6
|
+
*/
|
|
7
|
+
import { ALPHABETS, DEFAULT_LENGTH, randomString } from "./random.js";
|
|
8
|
+
/**
|
|
9
|
+
* nanoid-style id: URL-safe (`A–Z a–z 0–9 _ -`), crypto-backed, unbiased.
|
|
10
|
+
*
|
|
11
|
+
* @param length Number of characters (default: {@link DEFAULT_LENGTH}).
|
|
12
|
+
* @param alphabet Override the alphabet (default: URL-safe 64-char set).
|
|
13
|
+
*/
|
|
14
|
+
export function nanoid(length = DEFAULT_LENGTH, alphabet = ALPHABETS.nanoid) {
|
|
15
|
+
return randomString(length, alphabet);
|
|
16
|
+
}
|
|
17
|
+
/** Random lowercase hex string. */
|
|
18
|
+
export function hex(length = DEFAULT_LENGTH) {
|
|
19
|
+
return randomString(length, ALPHABETS.hex);
|
|
20
|
+
}
|
|
21
|
+
/** Random Crockford base32 string (no I, L, O, U). */
|
|
22
|
+
export function base32(length = DEFAULT_LENGTH) {
|
|
23
|
+
return randomString(length, ALPHABETS.base32);
|
|
24
|
+
}
|
|
25
|
+
/** Random lowercase base36 string. */
|
|
26
|
+
export function base36(length = DEFAULT_LENGTH) {
|
|
27
|
+
return randomString(length, ALPHABETS.base36);
|
|
28
|
+
}
|
|
29
|
+
/** Random Bitcoin base58 string (no 0, O, I, l). */
|
|
30
|
+
export function base58(length = DEFAULT_LENGTH) {
|
|
31
|
+
return randomString(length, ALPHABETS.base58);
|
|
32
|
+
}
|
|
33
|
+
/** Random full base62 string. */
|
|
34
|
+
export function base62(length = DEFAULT_LENGTH) {
|
|
35
|
+
return randomString(length, ALPHABETS.base62);
|
|
36
|
+
}
|
|
37
|
+
/** Random digits-only string — handy for OTPs / numeric coupon codes. */
|
|
38
|
+
export function numeric(length = DEFAULT_LENGTH) {
|
|
39
|
+
return randomString(length, ALPHABETS.numeric);
|
|
40
|
+
}
|
package/dist/base56.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base56 strategy — human-friendly, no ambiguous characters (no 0 O o 1 l I).
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - {@link base56} random fixed-length string (coupon-style)
|
|
6
|
+
* - {@link uuidToBase56} / {@link base56ToUuid} fully reversible 23-char
|
|
7
|
+
* encoding of a UUID
|
|
8
|
+
*
|
|
9
|
+
* Note: 128 bits need ceil(128 / log2(56)) = 23 base56 characters in the worst
|
|
10
|
+
* case (since 56^22 ≈ 2^127.76 < 2^128), so the encoded width is fixed at 23.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Generates a random base56 string.
|
|
14
|
+
*
|
|
15
|
+
* @param length Number of characters (default: {@link DEFAULT_LENGTH}).
|
|
16
|
+
*/
|
|
17
|
+
export declare function base56(length?: number): string;
|
|
18
|
+
/**
|
|
19
|
+
* Encodes a UUID into a 23-character base56 string. Fully reversible via
|
|
20
|
+
* {@link base56ToUuid}.
|
|
21
|
+
*
|
|
22
|
+
* @param uuid A UUID string, with or without dashes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function uuidToBase56(uuid: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Decodes a base56 string produced by {@link uuidToBase56} back into a
|
|
27
|
+
* canonical UUID string.
|
|
28
|
+
*/
|
|
29
|
+
export declare function base56ToUuid(encoded: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Generates a fresh UUID and returns its 23-character base56 encoding.
|
|
32
|
+
* Equivalent to `uuidToBase56(uuid())`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function base56Uuid(): string;
|
package/dist/base56.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base56 strategy — human-friendly, no ambiguous characters (no 0 O o 1 l I).
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - {@link base56} random fixed-length string (coupon-style)
|
|
6
|
+
* - {@link uuidToBase56} / {@link base56ToUuid} fully reversible 23-char
|
|
7
|
+
* encoding of a UUID
|
|
8
|
+
*
|
|
9
|
+
* Note: 128 bits need ceil(128 / log2(56)) = 23 base56 characters in the worst
|
|
10
|
+
* case (since 56^22 ≈ 2^127.76 < 2^128), so the encoded width is fixed at 23.
|
|
11
|
+
*/
|
|
12
|
+
import { ALPHABETS, DEFAULT_LENGTH, randomString } from "./random.js";
|
|
13
|
+
const BASE56 = ALPHABETS.base56;
|
|
14
|
+
/**
|
|
15
|
+
* Generates a random base56 string.
|
|
16
|
+
*
|
|
17
|
+
* @param length Number of characters (default: {@link DEFAULT_LENGTH}).
|
|
18
|
+
*/
|
|
19
|
+
export function base56(length = DEFAULT_LENGTH) {
|
|
20
|
+
return randomString(length, BASE56);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encodes a UUID into a 23-character base56 string. Fully reversible via
|
|
24
|
+
* {@link base56ToUuid}.
|
|
25
|
+
*
|
|
26
|
+
* @param uuid A UUID string, with or without dashes.
|
|
27
|
+
*/
|
|
28
|
+
export function uuidToBase56(uuid) {
|
|
29
|
+
const hex = uuid.replace(/-/g, "").toLowerCase();
|
|
30
|
+
if (!/^[0-9a-f]{32}$/.test(hex)) {
|
|
31
|
+
throw new TypeError(`uuidToBase56: invalid uuid "${uuid}"`);
|
|
32
|
+
}
|
|
33
|
+
let num = BigInt("0x" + hex);
|
|
34
|
+
let result = "";
|
|
35
|
+
while (num > 0n) {
|
|
36
|
+
result = BASE56[Number(num % 56n)] + result;
|
|
37
|
+
num = num / 56n;
|
|
38
|
+
}
|
|
39
|
+
return result.padStart(23, BASE56[0]);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Decodes a base56 string produced by {@link uuidToBase56} back into a
|
|
43
|
+
* canonical UUID string.
|
|
44
|
+
*/
|
|
45
|
+
export function base56ToUuid(encoded) {
|
|
46
|
+
let num = 0n;
|
|
47
|
+
for (const ch of encoded) {
|
|
48
|
+
const idx = BASE56.indexOf(ch);
|
|
49
|
+
if (idx < 0) {
|
|
50
|
+
throw new Error(`base56ToUuid: invalid character "${ch}"`);
|
|
51
|
+
}
|
|
52
|
+
num = num * 56n + BigInt(idx);
|
|
53
|
+
}
|
|
54
|
+
const hex = num.toString(16).padStart(32, "0");
|
|
55
|
+
return [
|
|
56
|
+
hex.slice(0, 8),
|
|
57
|
+
hex.slice(8, 12),
|
|
58
|
+
hex.slice(12, 16),
|
|
59
|
+
hex.slice(16, 20),
|
|
60
|
+
hex.slice(20),
|
|
61
|
+
].join("-");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generates a fresh UUID and returns its 23-character base56 encoding.
|
|
65
|
+
* Equivalent to `uuidToBase56(uuid())`.
|
|
66
|
+
*/
|
|
67
|
+
export function base56Uuid() {
|
|
68
|
+
return uuidToBase56(crypto.randomUUID());
|
|
69
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counter strategy — a simple, monotonically increasing, in-memory counter
|
|
3
|
+
* with an optional prefix. State lives for the lifetime of the running
|
|
4
|
+
* process/page; it is NOT persisted and resets on reload. Suitable for
|
|
5
|
+
* within-process unique ids (tree nodes, DOM ids, test fixtures), not for
|
|
6
|
+
* cross-process uniqueness.
|
|
7
|
+
*/
|
|
8
|
+
/** Options shared by {@link counter} and {@link createCounter}. */
|
|
9
|
+
export interface CounterOptions {
|
|
10
|
+
/** String prepended to the number (also the key for the shared counter). */
|
|
11
|
+
prefix?: string;
|
|
12
|
+
/** First value emitted (default: `0`). */
|
|
13
|
+
start?: number;
|
|
14
|
+
/** Increment between successive values (default: `1`). */
|
|
15
|
+
step?: number;
|
|
16
|
+
/** Zero-pad the numeric part to this width (default: `0`, no padding). */
|
|
17
|
+
pad?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns the next value of the shared, per-prefix counter.
|
|
21
|
+
*
|
|
22
|
+
* Each distinct `prefix` has its own independent sequence. The **first** call
|
|
23
|
+
* for a given prefix configures it (`start`, `step`, `pad`) and returns `start`;
|
|
24
|
+
* subsequent calls advance it by the captured `step` and reuse the captured
|
|
25
|
+
* `pad`, so option overrides passed to later calls for the same prefix are
|
|
26
|
+
* ignored (and the id width stays stable). Use {@link createCounter} when you
|
|
27
|
+
* want explicit per-instance configuration.
|
|
28
|
+
*
|
|
29
|
+
* Options are still validated on every call, so malformed input throws even when
|
|
30
|
+
* the values would be ignored.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* counter({ prefix: "node_" }); // "node_0"
|
|
34
|
+
* counter({ prefix: "node_" }); // "node_1"
|
|
35
|
+
* counter({ prefix: "x", pad: 3 }); // "x000"
|
|
36
|
+
*/
|
|
37
|
+
export declare function counter(options?: CounterOptions): string;
|
|
38
|
+
/**
|
|
39
|
+
* Creates an isolated counter function with its own private state — no sharing
|
|
40
|
+
* with {@link counter} or with other instances. Prefer this when you need
|
|
41
|
+
* several independent sequences or deterministic behavior in tests.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const next = createCounter({ prefix: "row-", start: 1, pad: 4 });
|
|
45
|
+
* next(); // "row-0001"
|
|
46
|
+
* next(); // "row-0002"
|
|
47
|
+
*/
|
|
48
|
+
export declare function createCounter(options?: CounterOptions): () => string;
|
|
49
|
+
/**
|
|
50
|
+
* Clears the shared per-prefix counter state used by {@link counter}.
|
|
51
|
+
* Mainly useful in tests. Does not affect {@link createCounter} instances.
|
|
52
|
+
*
|
|
53
|
+
* @param prefix If given, resets only that prefix; otherwise resets all.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resetCounters(prefix?: string): void;
|
package/dist/counter.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counter strategy — a simple, monotonically increasing, in-memory counter
|
|
3
|
+
* with an optional prefix. State lives for the lifetime of the running
|
|
4
|
+
* process/page; it is NOT persisted and resets on reload. Suitable for
|
|
5
|
+
* within-process unique ids (tree nodes, DOM ids, test fixtures), not for
|
|
6
|
+
* cross-process uniqueness.
|
|
7
|
+
*/
|
|
8
|
+
import { assertNonNegativeInt } from "./random.js";
|
|
9
|
+
function validate(o) {
|
|
10
|
+
const { prefix = "", start = 0, step = 1, pad = 0 } = o;
|
|
11
|
+
if (typeof prefix !== "string") {
|
|
12
|
+
throw new TypeError(`counter: "prefix" must be a string`);
|
|
13
|
+
}
|
|
14
|
+
if (!Number.isInteger(start)) {
|
|
15
|
+
throw new TypeError(`counter: "start" must be an integer`);
|
|
16
|
+
}
|
|
17
|
+
if (!Number.isInteger(step) || step < 1) {
|
|
18
|
+
throw new TypeError(`counter: "step" must be a positive integer`);
|
|
19
|
+
}
|
|
20
|
+
assertNonNegativeInt(pad, "pad");
|
|
21
|
+
return { prefix, start, step, pad };
|
|
22
|
+
}
|
|
23
|
+
function format(prefix, n, pad) {
|
|
24
|
+
const s = pad > 0 ? String(n).padStart(pad, "0") : String(n);
|
|
25
|
+
return prefix + s;
|
|
26
|
+
}
|
|
27
|
+
// One independent counter per prefix, shared across the module. The full config
|
|
28
|
+
// (current value + step + pad) is captured on first use so a prefix's sequence
|
|
29
|
+
// stays consistent regardless of options passed to later calls.
|
|
30
|
+
const _counters = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Returns the next value of the shared, per-prefix counter.
|
|
33
|
+
*
|
|
34
|
+
* Each distinct `prefix` has its own independent sequence. The **first** call
|
|
35
|
+
* for a given prefix configures it (`start`, `step`, `pad`) and returns `start`;
|
|
36
|
+
* subsequent calls advance it by the captured `step` and reuse the captured
|
|
37
|
+
* `pad`, so option overrides passed to later calls for the same prefix are
|
|
38
|
+
* ignored (and the id width stays stable). Use {@link createCounter} when you
|
|
39
|
+
* want explicit per-instance configuration.
|
|
40
|
+
*
|
|
41
|
+
* Options are still validated on every call, so malformed input throws even when
|
|
42
|
+
* the values would be ignored.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* counter({ prefix: "node_" }); // "node_0"
|
|
46
|
+
* counter({ prefix: "node_" }); // "node_1"
|
|
47
|
+
* counter({ prefix: "x", pad: 3 }); // "x000"
|
|
48
|
+
*/
|
|
49
|
+
export function counter(options = {}) {
|
|
50
|
+
const { prefix, start, step, pad } = validate(options);
|
|
51
|
+
const existing = _counters.get(prefix);
|
|
52
|
+
const entry = existing
|
|
53
|
+
? { ...existing, value: existing.value + existing.step }
|
|
54
|
+
: { value: start, step, pad };
|
|
55
|
+
_counters.set(prefix, entry);
|
|
56
|
+
return format(prefix, entry.value, entry.pad);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Creates an isolated counter function with its own private state — no sharing
|
|
60
|
+
* with {@link counter} or with other instances. Prefer this when you need
|
|
61
|
+
* several independent sequences or deterministic behavior in tests.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const next = createCounter({ prefix: "row-", start: 1, pad: 4 });
|
|
65
|
+
* next(); // "row-0001"
|
|
66
|
+
* next(); // "row-0002"
|
|
67
|
+
*/
|
|
68
|
+
export function createCounter(options = {}) {
|
|
69
|
+
const { prefix, start, step, pad } = validate(options);
|
|
70
|
+
let current = start - step;
|
|
71
|
+
return () => {
|
|
72
|
+
current += step;
|
|
73
|
+
return format(prefix, current, pad);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Clears the shared per-prefix counter state used by {@link counter}.
|
|
78
|
+
* Mainly useful in tests. Does not affect {@link createCounter} instances.
|
|
79
|
+
*
|
|
80
|
+
* @param prefix If given, resets only that prefix; otherwise resets all.
|
|
81
|
+
*/
|
|
82
|
+
export function resetCounters(prefix) {
|
|
83
|
+
if (prefix === undefined)
|
|
84
|
+
_counters.clear();
|
|
85
|
+
else
|
|
86
|
+
_counters.delete(prefix);
|
|
87
|
+
}
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @marianmeres/uid — one `uid()` function, many id strategies.
|
|
3
|
+
*
|
|
4
|
+
* The core entry point is zero-dependency and works in every modern runtime.
|
|
5
|
+
* The `rhr` strategy (human-readable ids) is opt-in via the `./rhr` subpath to
|
|
6
|
+
* keep its word lists out of the core bundle.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { uid, nanoid, uuidv7 } from "@marianmeres/uid";
|
|
10
|
+
* uid(); // uuid v4
|
|
11
|
+
* nanoid(12); // tree-shakeable named export
|
|
12
|
+
* uid("uuidv7"); // sortable database key
|
|
13
|
+
*/
|
|
14
|
+
export * from "./random.js";
|
|
15
|
+
export * from "./uuid.js";
|
|
16
|
+
export * from "./base56.js";
|
|
17
|
+
export * from "./alphabet.js";
|
|
18
|
+
export * from "./counter.js";
|
|
19
|
+
export * from "./reversible.js";
|
|
20
|
+
export * from "./uid.js";
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @marianmeres/uid — one `uid()` function, many id strategies.
|
|
3
|
+
*
|
|
4
|
+
* The core entry point is zero-dependency and works in every modern runtime.
|
|
5
|
+
* The `rhr` strategy (human-readable ids) is opt-in via the `./rhr` subpath to
|
|
6
|
+
* keep its word lists out of the core bundle.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { uid, nanoid, uuidv7 } from "@marianmeres/uid";
|
|
10
|
+
* uid(); // uuid v4
|
|
11
|
+
* nanoid(12); // tree-shakeable named export
|
|
12
|
+
* uid("uuidv7"); // sortable database key
|
|
13
|
+
*/
|
|
14
|
+
export * from "./random.js";
|
|
15
|
+
export * from "./uuid.js";
|
|
16
|
+
export * from "./base56.js";
|
|
17
|
+
export * from "./alphabet.js";
|
|
18
|
+
export * from "./counter.js";
|
|
19
|
+
export * from "./reversible.js";
|
|
20
|
+
export * from "./uid.js";
|
package/dist/random.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto-backed randomness primitives shared by every strategy.
|
|
3
|
+
*
|
|
4
|
+
* All randomness goes through the Web Crypto API (`crypto.getRandomValues`),
|
|
5
|
+
* which is available in every modern runtime (browsers, Deno, Node 19+, Bun)
|
|
6
|
+
* without any external dependency. This is good-quality randomness, but the
|
|
7
|
+
* library is NOT intended for cryptographic secrets (tokens, keys, etc.).
|
|
8
|
+
*/
|
|
9
|
+
/** Default length for random, fixed-length strategies (nanoid, base*, hex, …). */
|
|
10
|
+
export declare const DEFAULT_LENGTH = 21;
|
|
11
|
+
/**
|
|
12
|
+
* Named alphabets used by the built-in strategies.
|
|
13
|
+
*
|
|
14
|
+
* The "no-ambiguous" alphabets (`base56`, `base58`, `base32`) drop visually
|
|
15
|
+
* confusable characters so the output is safe to read aloud / retype.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ALPHABETS: {
|
|
18
|
+
hex: string;
|
|
19
|
+
base32: string;
|
|
20
|
+
base36: string;
|
|
21
|
+
base56: string;
|
|
22
|
+
base58: string;
|
|
23
|
+
base62: string;
|
|
24
|
+
numeric: string;
|
|
25
|
+
alphanumeric: string;
|
|
26
|
+
nanoid: string;
|
|
27
|
+
};
|
|
28
|
+
/** Throws `TypeError` unless `n` is an integer `>= 1`. */
|
|
29
|
+
export declare function assertPositiveInt(n: unknown, label: string): asserts n is number;
|
|
30
|
+
/** Throws `TypeError` unless `n` is an integer `>= 0`. */
|
|
31
|
+
export declare function assertNonNegativeInt(n: unknown, label: string): asserts n is number;
|
|
32
|
+
/**
|
|
33
|
+
* Returns `size` cryptographically-strong random bytes.
|
|
34
|
+
*
|
|
35
|
+
* Transparently chunks calls larger than the 65536-byte `getRandomValues`
|
|
36
|
+
* limit so any `size` is supported.
|
|
37
|
+
*/
|
|
38
|
+
export declare function randomBytes(size: number): Uint8Array;
|
|
39
|
+
/**
|
|
40
|
+
* Generates a random string of `length` characters from `alphabet`.
|
|
41
|
+
*
|
|
42
|
+
* Uses rejection sampling (the nanoid algorithm) so the output is unbiased
|
|
43
|
+
* even when `alphabet.length` is not a power of two — no modulo skew.
|
|
44
|
+
*
|
|
45
|
+
* @param length Number of characters to produce (positive integer).
|
|
46
|
+
* @param alphabet Characters to choose from (1–256 chars). Defaults to the
|
|
47
|
+
* URL-safe nanoid alphabet.
|
|
48
|
+
*/
|
|
49
|
+
export declare function randomString(length: number, alphabet?: string): string;
|