@sdxc/uuid 2026.10.12 → 2026.11.3
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 +117 -103
- package/dist/cli-contract.d.ts +23 -0
- package/dist/cli-contract.js +59 -0
- package/dist/cli-v4.d.ts +12 -0
- package/dist/cli-v4.js +16 -0
- package/dist/cli-v7.d.ts +12 -0
- package/dist/cli-v7.js +16 -0
- package/dist/cli-validate.d.ts +19 -0
- package/dist/cli-validate.js +48 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +19 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/v7.d.ts +11 -0
- package/dist/v7.js +13 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -2,51 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
Validate and assert UUIDs behind a branded UUID type, and generate v4 or v7 values.
|
|
4
4
|
|
|
5
|
-
A UUID
|
|
6
|
-
|
|
7
|
-
proof of the check in its type, so a function that asks for a `UUID` cannot be handed the
|
|
8
|
-
raw path segment it came from.
|
|
5
|
+
A `UUID` is the same string, carrying proof of the check in its type: validate once where the
|
|
6
|
+
value enters, and every function below can ask for a `UUID` instead of re-checking a `string`.
|
|
9
7
|
|
|
10
8
|
## Installation
|
|
11
9
|
|
|
12
|
-
```
|
|
10
|
+
```sh
|
|
13
11
|
npm add @sdxc/uuid
|
|
14
12
|
```
|
|
15
13
|
|
|
16
14
|
The generators use the platform's Web Crypto API, available in browsers, Node, Bun, Deno and
|
|
17
|
-
Cloudflare Workers.
|
|
18
|
-
want prefixed identifiers built on these values.
|
|
15
|
+
Cloudflare Workers.
|
|
19
16
|
|
|
20
17
|
## Usage
|
|
21
18
|
|
|
22
|
-
### Generate
|
|
19
|
+
### Generate an identifier
|
|
23
20
|
|
|
24
|
-
Each UUID version is its own entry point
|
|
25
|
-
|
|
26
|
-
line.
|
|
21
|
+
Each UUID version is its own entry point exporting the same `generateUUID()`, so the import
|
|
22
|
+
path picks the version.
|
|
27
23
|
|
|
28
24
|
```typescript
|
|
29
25
|
import { generateUUID } from "@sdxc/uuid/v7";
|
|
30
26
|
|
|
31
27
|
let id = generateUUID();
|
|
32
|
-
// "019a6f3e-5c21-7a4b-9d3e-8f1a2b3c4d5e"
|
|
28
|
+
// "019a6f3e-5c21-7a4b-9d3e-8f1a2b3c4d5e", typed UUID
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
A v7 value starts with
|
|
36
|
-
|
|
37
|
-
`@sdxc/uuid/v4` instead when the identifier must not reveal when it was created:
|
|
31
|
+
A v7 value starts with its creation time, so values sort in the order they were generated.
|
|
32
|
+
Import from `@sdxc/uuid/v4` for a fully random value that reveals nothing:
|
|
38
33
|
|
|
39
34
|
```typescript
|
|
40
35
|
import { generateUUID } from "@sdxc/uuid/v4";
|
|
41
36
|
|
|
42
37
|
let id = generateUUID();
|
|
43
|
-
// "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
|
|
38
|
+
// "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", typed UUID
|
|
44
39
|
```
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
cast.
|
|
48
|
-
|
|
49
|
-
### Narrow A String You Received
|
|
41
|
+
### Check a string you received
|
|
50
42
|
|
|
51
43
|
```typescript
|
|
52
44
|
import { isUUID } from "@sdxc/uuid";
|
|
@@ -58,26 +50,12 @@ if (isUUID(value)) {
|
|
|
58
50
|
}
|
|
59
51
|
```
|
|
60
52
|
|
|
61
|
-
A
|
|
62
|
-
|
|
63
|
-
have one spelling. The version and variant digits are read as hex like any other, so the
|
|
64
|
-
check is about the format rather than which UUID version produced it.
|
|
65
|
-
|
|
66
|
-
### Assert At A Boundary
|
|
53
|
+
A UUID is exactly 36 characters of lowercase hex shaped `8-4-4-4-12`, so stored identifiers
|
|
54
|
+
have one spelling. Any version passes.
|
|
67
55
|
|
|
68
|
-
|
|
69
|
-
import { assertUUID } from "@sdxc/uuid";
|
|
56
|
+
### Assert and tell the failures apart
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
assertUUID(value);
|
|
73
|
-
return value; // typed UUID from here on
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
`assertUUID` throws rather than returning, which suits the edge of a system: the value is an
|
|
78
|
-
identifier or the request stops here.
|
|
79
|
-
|
|
80
|
-
### Tell The Failures Apart
|
|
58
|
+
`assertUUID` narrows the value for the rest of the scope, or throws one of three error classes.
|
|
81
59
|
|
|
82
60
|
```typescript
|
|
83
61
|
import {
|
|
@@ -89,128 +67,168 @@ import {
|
|
|
89
67
|
|
|
90
68
|
try {
|
|
91
69
|
assertUUID(value);
|
|
70
|
+
// value is UUID from here on
|
|
92
71
|
} catch (error) {
|
|
93
72
|
if (error instanceof InvalidUUIDTypeError) {
|
|
94
|
-
//
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (error instanceof
|
|
98
|
-
// a string of the wrong length: "Invalid UUID length: 10"
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (error instanceof InvalidUUIDFormatError) {
|
|
73
|
+
// not a string: "Expected a string, got object"
|
|
74
|
+
} else if (error instanceof InvalidUUIDLengthError) {
|
|
75
|
+
// wrong length: "Invalid UUID length: 10"
|
|
76
|
+
} else if (error instanceof InvalidUUIDFormatError) {
|
|
102
77
|
// 36 characters in the wrong shape
|
|
103
78
|
}
|
|
104
79
|
}
|
|
105
80
|
```
|
|
106
81
|
|
|
107
|
-
|
|
108
|
-
distinguishable when you decide what to answer.
|
|
82
|
+
## Command line
|
|
109
83
|
|
|
110
|
-
|
|
84
|
+
`sdxc-uuid` generates and checks UUIDs from a terminal or a script, with no install step:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
bunx @sdxc/uuid --help
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With [`@sdxc/kit`](https://www.npmjs.com/package/@sdxc/kit) installed, `sdxc pkg uuid …` runs the same command line, using the version your project installs.
|
|
111
91
|
|
|
112
|
-
|
|
92
|
+
`validate` reads the UUID from its argument, or stdin when it is `-` or absent, prints its version and, for v7, its creation time, and exits 1 when the value is invalid. `--json` prints the result as data.
|
|
113
93
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
94
|
+
```sh
|
|
95
|
+
bunx @sdxc/uuid v4 -n 5
|
|
96
|
+
bunx @sdxc/uuid v7 -n 1000 > ids.txt
|
|
97
|
+
bunx @sdxc/uuid validate 019a6f3e-5c21-7a4b-9d3e-8f1a2b3c4d5e --json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API
|
|
117
101
|
|
|
118
102
|
### `isUUID(value: string): value is UUID`
|
|
119
103
|
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
Returns `true` when the value is a lowercase `8-4-4-4-12` hex string, narrowing it to `UUID`.
|
|
105
|
+
Accepts exactly the values `assertUUID` accepts.
|
|
122
106
|
|
|
123
107
|
### `assertUUID(value: string): asserts value is UUID`
|
|
124
108
|
|
|
125
|
-
Narrows
|
|
126
|
-
`
|
|
127
|
-
|
|
109
|
+
Narrows the value to `UUID`, or throws `InvalidUUIDTypeError` for a non-string,
|
|
110
|
+
`InvalidUUIDLengthError` for a length other than 36, and `InvalidUUIDFormatError` otherwise.
|
|
111
|
+
|
|
112
|
+
### `getVersion(id: UUID): number`
|
|
113
|
+
|
|
114
|
+
Returns the version the UUID declares, such as `4` or `7`.
|
|
128
115
|
|
|
129
116
|
### `generateUUID(): UUID` from `@sdxc/uuid/v7`
|
|
130
117
|
|
|
131
|
-
Returns a
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
118
|
+
Returns a version 7 UUID ([RFC 9562](https://www.rfc-editor.org/rfc/rfc9562)): a 48-bit
|
|
119
|
+
millisecond timestamp, a 12-bit counter and 62 random bits. Each value sorts after the previous
|
|
120
|
+
one from the same process, including within one millisecond. Anyone holding the value can read
|
|
121
|
+
its creation time.
|
|
122
|
+
|
|
123
|
+
### `getTimestamp(id: UUID): number` from `@sdxc/uuid/v7`
|
|
137
124
|
|
|
138
|
-
|
|
139
|
-
[`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) prefix. Use it for identifiers
|
|
140
|
-
whose creation time is fine to expose, and keep secrets on random values.
|
|
125
|
+
Returns the Unix millisecond timestamp a v7 UUID was generated at.
|
|
141
126
|
|
|
142
127
|
### `generateUUID(): UUID` from `@sdxc/uuid/v4`
|
|
143
128
|
|
|
144
|
-
Returns a
|
|
145
|
-
|
|
129
|
+
Returns a version 4 UUID from `crypto.randomUUID()`: 122 random bits, with no ordering and no
|
|
130
|
+
creation time.
|
|
146
131
|
|
|
147
|
-
###
|
|
132
|
+
### `InvalidUUIDTypeError`
|
|
148
133
|
|
|
149
|
-
|
|
134
|
+
Thrown for a non-string; the message names the type received.
|
|
150
135
|
|
|
151
|
-
|
|
152
|
-
arrived, as in `Expected a string, got object`.
|
|
136
|
+
### `InvalidUUIDLengthError`
|
|
153
137
|
|
|
154
|
-
|
|
138
|
+
Thrown for a string that is not 36 characters; the message names the length.
|
|
155
139
|
|
|
156
|
-
|
|
157
|
-
the shape check, so a truncated identifier says so instead of reading as malformed.
|
|
140
|
+
### `InvalidUUIDFormatError`
|
|
158
141
|
|
|
159
|
-
|
|
142
|
+
Thrown for 36 characters that are not lowercase `8-4-4-4-12` hex; the message repeats the value.
|
|
160
143
|
|
|
161
|
-
|
|
162
|
-
that failed, as in `Invalid UUID format: 550e8400_e29b_41d4_a716_446655440000`.
|
|
144
|
+
### Types
|
|
163
145
|
|
|
164
|
-
|
|
146
|
+
#### `UUID`
|
|
165
147
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
148
|
+
A branded UUID string. A plain `string` must pass `isUUID` or `assertUUID` to become one, and a
|
|
149
|
+
`UUID` is usable anywhere a `string` is.
|
|
150
|
+
|
|
151
|
+
## Pattern: Narrow Once At The Edge
|
|
152
|
+
|
|
153
|
+
Assert an identifier where it enters a request handler, and the functions below take a `UUID`
|
|
154
|
+
without re-validating.
|
|
169
155
|
|
|
170
156
|
```typescript
|
|
171
157
|
import type { UUID } from "@sdxc/uuid";
|
|
172
158
|
|
|
173
|
-
import { assertUUID } from "@sdxc/uuid";
|
|
159
|
+
import { assertUUID, InvalidUUIDLengthError, InvalidUUIDFormatError } from "@sdxc/uuid";
|
|
174
160
|
|
|
175
|
-
export async function
|
|
161
|
+
export async function handler(request: Request) {
|
|
176
162
|
let accountId = new URL(request.url).pathname.split("/").at(-1) ?? "";
|
|
177
|
-
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
assertUUID(accountId);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error instanceof InvalidUUIDLengthError || error instanceof InvalidUUIDFormatError) {
|
|
168
|
+
return new Response("Not Found", { status: 404 });
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
178
172
|
|
|
179
173
|
return Response.json(await loadAccount(accountId));
|
|
180
174
|
}
|
|
181
175
|
|
|
182
176
|
async function loadAccount(accountId: UUID) {
|
|
183
|
-
|
|
177
|
+
return { id: accountId };
|
|
184
178
|
}
|
|
185
179
|
```
|
|
186
180
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
## Pattern: Branching Instead Of Throwing
|
|
181
|
+
## Pattern: Validate A Form Field Without Throwing
|
|
190
182
|
|
|
191
|
-
A
|
|
192
|
-
the answer. `isUUID` gives you the same narrowing in an expression you can branch on:
|
|
183
|
+
A person can mistype a field, so branch on `isUUID` and answer with a result.
|
|
193
184
|
|
|
194
185
|
```typescript
|
|
195
186
|
import type { UUID } from "@sdxc/uuid";
|
|
196
187
|
|
|
197
188
|
import { isUUID } from "@sdxc/uuid";
|
|
198
189
|
|
|
199
|
-
function readAccountId(
|
|
200
|
-
let value =
|
|
190
|
+
function readAccountId(form: FormData): { ok: true; accountId: UUID } | { ok: false } {
|
|
191
|
+
let value = form.get("accountId");
|
|
201
192
|
if (typeof value !== "string" || !isUUID(value)) return { ok: false };
|
|
202
193
|
return { ok: true, accountId: value };
|
|
203
194
|
}
|
|
204
195
|
```
|
|
205
196
|
|
|
206
|
-
|
|
207
|
-
|
|
197
|
+
## Pattern: Time-Ordered Record Ids
|
|
198
|
+
|
|
199
|
+
Use v7 for primary keys: new rows append to the end of the index, and sorting by id sorts by
|
|
200
|
+
creation, so the last id of a page is enough of a cursor for the next one.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import type { UUID } from "@sdxc/uuid";
|
|
204
|
+
|
|
205
|
+
import { generateUUID } from "@sdxc/uuid/v7";
|
|
206
|
+
|
|
207
|
+
interface Note {
|
|
208
|
+
id: UUID;
|
|
209
|
+
body: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let notes: Note[] = [];
|
|
213
|
+
|
|
214
|
+
function createNote(body: string): Note {
|
|
215
|
+
let note = { id: generateUUID(), body };
|
|
216
|
+
notes.push(note);
|
|
217
|
+
return note;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function notesAfter(cursor: UUID | null, limit: number) {
|
|
221
|
+
return notes.filter((note) => cursor === null || note.id > cursor).slice(0, limit);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Reach for `@sdxc/uuid/v4` when an identifier must not reveal when it was created, such as a
|
|
226
|
+
public share id.
|
|
208
227
|
|
|
209
228
|
## Pattern: Prefixed Identifiers
|
|
210
229
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
takes a `UUID` and a prefix and encodes them as one sortable, self-describing string:
|
|
230
|
+
[`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) takes a `UUID` and a prefix and
|
|
231
|
+
encodes them as one self-describing string that keeps the v7 sort order.
|
|
214
232
|
|
|
215
233
|
```typescript
|
|
216
234
|
import { TypeID } from "@sdxc/typeid";
|
|
@@ -222,10 +240,6 @@ accountId.toString();
|
|
|
222
240
|
// "account_01h455vb4pex5vsknk084sn02q"
|
|
223
241
|
```
|
|
224
242
|
|
|
225
|
-
It asks for a `UUID` rather than a `string`, so the value you generate here is accepted and a
|
|
226
|
-
random 36 characters is not. TypeID keeps the UUID's sort order, so a v7 identifier stays
|
|
227
|
-
time-ordered once it is prefixed.
|
|
228
|
-
|
|
229
243
|
## Versioning
|
|
230
244
|
|
|
231
245
|
Releases are dated rather than semantic. A version is the UTC date it was published,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid` command line as a contract: `v4` and `v7` generate identifiers, `validate`
|
|
3
|
+
* checks one and reports its version. Help, usage errors and each handler's typed `ctx.flags`
|
|
4
|
+
* come from this one declaration.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
/** Every command `sdxc-uuid` takes. */
|
|
10
|
+
export declare const UUID_COMMANDS: import("@sdxc/cli").Group<Omit<{
|
|
11
|
+
readonly description: "sdxc-uuid — generate and validate UUIDs";
|
|
12
|
+
readonly v4: import("@sdxc/cli").Command<Readonly<Record<string, import("@sdxc/cli").Argument<unknown>>>, {
|
|
13
|
+
readonly count: import("@sdxc/cli").ValueFlag<number, true, false, false>;
|
|
14
|
+
}>;
|
|
15
|
+
readonly v7: import("@sdxc/cli").Command<Readonly<Record<string, import("@sdxc/cli").Argument<unknown>>>, {
|
|
16
|
+
readonly count: import("@sdxc/cli").ValueFlag<number, true, false, false>;
|
|
17
|
+
}>;
|
|
18
|
+
readonly validate: import("@sdxc/cli").Command<{
|
|
19
|
+
readonly uuid: import("@sdxc/cli").Argument<string | undefined>;
|
|
20
|
+
}, {
|
|
21
|
+
readonly json: import("@sdxc/cli").Flag<boolean>;
|
|
22
|
+
}>;
|
|
23
|
+
}, "description" | "hidden" | "arguments" | "flags" | "tasks">, Record<never, never>, Record<never, never>>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid` command line as a contract: `v4` and `v7` generate identifiers, `validate`
|
|
3
|
+
* checks one and reports its version. Help, usage errors and each handler's typed `ctx.flags`
|
|
4
|
+
* come from this one declaration.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import { argument, command, commands, flag } from "@sdxc/cli";
|
|
10
|
+
import * as s from "remix/data-schema";
|
|
11
|
+
/** The most identifiers one run prints, so a typo in `-n` cannot flood a terminal. */
|
|
12
|
+
const MAX_COUNT = 100_000;
|
|
13
|
+
/**
|
|
14
|
+
* A count from 1 to {@link MAX_COUNT}, written as digits only, so `+8`, `8.0` and `0x8` are
|
|
15
|
+
* usage errors.
|
|
16
|
+
*/
|
|
17
|
+
function count() {
|
|
18
|
+
return s.createSchema(function validate(value, context) {
|
|
19
|
+
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
20
|
+
let parsed = Number(value);
|
|
21
|
+
if (parsed >= 1 && parsed <= MAX_COUNT)
|
|
22
|
+
return { value: parsed };
|
|
23
|
+
}
|
|
24
|
+
return s.fail(`expects an integer from 1 to ${MAX_COUNT}; got ${JSON.stringify(value)}`, context.path);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** The `-n` flag both generators take: how many identifiers to print, one per line. */
|
|
28
|
+
const COUNT = flag.string("count", {
|
|
29
|
+
short: "n",
|
|
30
|
+
valueName: "N",
|
|
31
|
+
default: 1,
|
|
32
|
+
schema: count(),
|
|
33
|
+
description: "How many to print, one per line",
|
|
34
|
+
});
|
|
35
|
+
/** Every command `sdxc-uuid` takes. */
|
|
36
|
+
export const UUID_COMMANDS = commands({
|
|
37
|
+
description: "sdxc-uuid — generate and validate UUIDs",
|
|
38
|
+
v4: command({
|
|
39
|
+
description: "Print random version 4 UUIDs, which reveal nothing about when they were made.",
|
|
40
|
+
flags: { count: COUNT },
|
|
41
|
+
examples: ["v4", "v4 -n 5"],
|
|
42
|
+
}),
|
|
43
|
+
v7: command({
|
|
44
|
+
description: "Print time-ordered version 7 UUIDs; each sorts after the one before, within a millisecond too.",
|
|
45
|
+
flags: { count: COUNT },
|
|
46
|
+
examples: ["v7", "v7 -n 1000 > ids.txt"],
|
|
47
|
+
}),
|
|
48
|
+
validate: command({
|
|
49
|
+
description: "Check a UUID is lowercase 8-4-4-4-12 hex and print its version, plus its creation time for v7. Exits 1 when it is invalid.",
|
|
50
|
+
arguments: {
|
|
51
|
+
uuid: argument(s.string(), {
|
|
52
|
+
optional: true,
|
|
53
|
+
description: "The UUID to check; stdin when it is - or absent",
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
flags: { json: flag.boolean("json", { description: "Print the result as JSON" }) },
|
|
57
|
+
examples: ["validate 019a6f3e-5c21-7a4b-9d3e-8f1a2b3c4d5e", "validate --json < id.txt"],
|
|
58
|
+
}),
|
|
59
|
+
});
|
package/dist/cli-v4.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid v4` handler: prints random version 4 UUIDs from the package's own generator,
|
|
3
|
+
* one per line, for ids that must not reveal when they were created.
|
|
4
|
+
*
|
|
5
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
6
|
+
* @copyright Sergio Xalambrí 2026
|
|
7
|
+
*/
|
|
8
|
+
/** Prints `--count` values, each on its own line. */
|
|
9
|
+
declare const _default: import("@sdxc/cli").CommandAction<import("@sdxc/cli").Command<Readonly<Record<string, import("@sdxc/cli").Argument<unknown>>>, {
|
|
10
|
+
readonly count: import("@sdxc/cli").ValueFlag<number, true, false, false>;
|
|
11
|
+
}>, []>;
|
|
12
|
+
export default _default;
|
package/dist/cli-v4.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid v4` handler: prints random version 4 UUIDs from the package's own generator,
|
|
3
|
+
* one per line, for ids that must not reveal when they were created.
|
|
4
|
+
*
|
|
5
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
6
|
+
* @copyright Sergio Xalambrí 2026
|
|
7
|
+
*/
|
|
8
|
+
import { createCommand, success } from "@sdxc/cli";
|
|
9
|
+
import { UUID_COMMANDS } from "./cli-contract.js";
|
|
10
|
+
import { generateUUID } from "./v4.js";
|
|
11
|
+
/** Prints `--count` values, each on its own line. */
|
|
12
|
+
export default createCommand(UUID_COMMANDS.v4, (ctx) => {
|
|
13
|
+
let ids = Array.from({ length: ctx.flags.count }, () => generateUUID());
|
|
14
|
+
ctx.stdout(`${ids.join("\n")}\n`);
|
|
15
|
+
return success();
|
|
16
|
+
});
|
package/dist/cli-v7.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid v7` handler: prints time-ordered version 7 UUIDs, one per line, in the order
|
|
3
|
+
* they sort, so a batch can seed a table or fixture already in index order.
|
|
4
|
+
*
|
|
5
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
6
|
+
* @copyright Sergio Xalambrí 2026
|
|
7
|
+
*/
|
|
8
|
+
/** Prints `--count` values, each on its own line and each sorting after the line above. */
|
|
9
|
+
declare const _default: import("@sdxc/cli").CommandAction<import("@sdxc/cli").Command<Readonly<Record<string, import("@sdxc/cli").Argument<unknown>>>, {
|
|
10
|
+
readonly count: import("@sdxc/cli").ValueFlag<number, true, false, false>;
|
|
11
|
+
}>, []>;
|
|
12
|
+
export default _default;
|
package/dist/cli-v7.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid v7` handler: prints time-ordered version 7 UUIDs, one per line, in the order
|
|
3
|
+
* they sort, so a batch can seed a table or fixture already in index order.
|
|
4
|
+
*
|
|
5
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
6
|
+
* @copyright Sergio Xalambrí 2026
|
|
7
|
+
*/
|
|
8
|
+
import { createCommand, success } from "@sdxc/cli";
|
|
9
|
+
import { UUID_COMMANDS } from "./cli-contract.js";
|
|
10
|
+
import { generateUUID } from "./v7.js";
|
|
11
|
+
/** Prints `--count` values, each on its own line and each sorting after the line above. */
|
|
12
|
+
export default createCommand(UUID_COMMANDS.v7, (ctx) => {
|
|
13
|
+
let ids = Array.from({ length: ctx.flags.count }, () => generateUUID());
|
|
14
|
+
ctx.stdout(`${ids.join("\n")}\n`);
|
|
15
|
+
return success();
|
|
16
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid validate` handler: checks one UUID from an argument or stdin with the
|
|
3
|
+
* package's `assertUUID`, and reports its version and, for v7, the time it was generated.
|
|
4
|
+
* An invalid value exits 1 with the reason, so a script can branch on the exit code.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Prints `valid`, the version and the v7 creation time, or `invalid` and the reason; `--json`
|
|
11
|
+
* prints `{ valid, uuid, version, timestamp? }` or `{ valid, uuid, error }`. Stdin loses one
|
|
12
|
+
* trailing line ending, so `echo $ID |` checks the id itself.
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: import("@sdxc/cli").CommandAction<import("@sdxc/cli").Command<{
|
|
15
|
+
readonly uuid: import("@sdxc/cli").Argument<string | undefined>;
|
|
16
|
+
}, {
|
|
17
|
+
readonly json: import("@sdxc/cli").Flag<boolean>;
|
|
18
|
+
}>, []>;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sdxc-uuid validate` handler: checks one UUID from an argument or stdin with the
|
|
3
|
+
* package's `assertUUID`, and reports its version and, for v7, the time it was generated.
|
|
4
|
+
* An invalid value exits 1 with the reason, so a script can branch on the exit code.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import { createCommand, exit, success, usage } from "@sdxc/cli";
|
|
10
|
+
import { isFailure, wrap } from "@sdxc/result";
|
|
11
|
+
import { UUID_COMMANDS } from "./cli-contract.js";
|
|
12
|
+
import { getTimestamp } from "./v7.js";
|
|
13
|
+
import { assertUUID, getVersion } from "./index.js";
|
|
14
|
+
/**
|
|
15
|
+
* Prints `valid`, the version and the v7 creation time, or `invalid` and the reason; `--json`
|
|
16
|
+
* prints `{ valid, uuid, version, timestamp? }` or `{ valid, uuid, error }`. Stdin loses one
|
|
17
|
+
* trailing line ending, so `echo $ID |` checks the id itself.
|
|
18
|
+
*/
|
|
19
|
+
export default createCommand(UUID_COMMANDS.validate, async (ctx) => {
|
|
20
|
+
let value = ctx.arguments.uuid ?? "-";
|
|
21
|
+
if (value === "-") {
|
|
22
|
+
if (ctx.stdin.isTTY)
|
|
23
|
+
return usage("Pass a UUID, or pipe one on stdin");
|
|
24
|
+
value = (await ctx.stdin.text()).replace(/\r?\n$/, "");
|
|
25
|
+
}
|
|
26
|
+
let checked = wrap(() => {
|
|
27
|
+
assertUUID(value);
|
|
28
|
+
return value;
|
|
29
|
+
});
|
|
30
|
+
if (isFailure(checked)) {
|
|
31
|
+
ctx.stdout(ctx.flags.json
|
|
32
|
+
? `${JSON.stringify({ valid: false, uuid: value, error: checked.error.message })}\n`
|
|
33
|
+
: `invalid: ${checked.error.message}\n`);
|
|
34
|
+
return exit(1);
|
|
35
|
+
}
|
|
36
|
+
let uuid = checked.data;
|
|
37
|
+
let version = getVersion(uuid);
|
|
38
|
+
let timestamp = version === 7 ? new Date(getTimestamp(uuid)).toISOString() : undefined;
|
|
39
|
+
if (ctx.flags.json) {
|
|
40
|
+
ctx.stdout(`${JSON.stringify({ valid: true, uuid, version, timestamp })}\n`);
|
|
41
|
+
return success();
|
|
42
|
+
}
|
|
43
|
+
let lines = ["valid", `version ${version}`];
|
|
44
|
+
if (timestamp !== undefined)
|
|
45
|
+
lines.push(`timestamp ${timestamp}`);
|
|
46
|
+
ctx.stdout(`${lines.join("\n")}\n`);
|
|
47
|
+
return success();
|
|
48
|
+
});
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `sdxc-uuid` command-line program: the contract in `cli-contract.ts` with each command
|
|
4
|
+
* mapped onto its handler, loaded only when that command runs. Exit codes: 0 done, 1 the UUID
|
|
5
|
+
* is invalid, 2 usage or unreadable input.
|
|
6
|
+
*
|
|
7
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
8
|
+
* @copyright Sergio Xalambrí 2026
|
|
9
|
+
*/
|
|
10
|
+
/** The `sdxc-uuid` program, run by the bin entry below and by tests through `invoke()`. */
|
|
11
|
+
export declare const cli: import("@sdxc/cli").CLI<import("@sdxc/cli").Group<Omit<{
|
|
12
|
+
readonly description: "sdxc-uuid — generate and validate UUIDs";
|
|
13
|
+
readonly v4: import("@sdxc/cli").Command<Readonly<Record<string, import("@sdxc/cli").Argument<unknown>>>, {
|
|
14
|
+
readonly count: import("@sdxc/cli").ValueFlag<number, true, false, false>;
|
|
15
|
+
}>;
|
|
16
|
+
readonly v7: import("@sdxc/cli").Command<Readonly<Record<string, import("@sdxc/cli").Argument<unknown>>>, {
|
|
17
|
+
readonly count: import("@sdxc/cli").ValueFlag<number, true, false, false>;
|
|
18
|
+
}>;
|
|
19
|
+
readonly validate: import("@sdxc/cli").Command<{
|
|
20
|
+
readonly uuid: import("@sdxc/cli").Argument<string | undefined>;
|
|
21
|
+
}, {
|
|
22
|
+
readonly json: import("@sdxc/cli").Flag<boolean>;
|
|
23
|
+
}>;
|
|
24
|
+
}, "description" | "hidden" | "arguments" | "flags" | "tasks">, Record<never, never>, Record<never, never>>, []>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `sdxc-uuid` command-line program: the contract in `cli-contract.ts` with each command
|
|
4
|
+
* mapped onto its handler, loaded only when that command runs. Exit codes: 0 done, 1 the UUID
|
|
5
|
+
* is invalid, 2 usage or unreadable input.
|
|
6
|
+
*
|
|
7
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
8
|
+
* @copyright Sergio Xalambrí 2026
|
|
9
|
+
*/
|
|
10
|
+
import { createCLI, lazy } from "@sdxc/cli";
|
|
11
|
+
import { UUID_COMMANDS } from "./cli-contract.js";
|
|
12
|
+
/** The `sdxc-uuid` program, run by the bin entry below and by tests through `invoke()`. */
|
|
13
|
+
export const cli = createCLI({ name: "sdxc-uuid", commands: UUID_COMMANDS });
|
|
14
|
+
cli.map(UUID_COMMANDS.v4, lazy(() => import("./cli-v4.js")));
|
|
15
|
+
cli.map(UUID_COMMANDS.v7, lazy(() => import("./cli-v7.js")));
|
|
16
|
+
cli.map(UUID_COMMANDS.validate, lazy(() => import("./cli-validate.js")));
|
|
17
|
+
if (import.meta.main) {
|
|
18
|
+
process.exit(await cli.run(process.argv.slice(2)));
|
|
19
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,14 @@ export type UUID = BaseUUID & {
|
|
|
9
9
|
};
|
|
10
10
|
export declare function isUUID(value: string): value is UUID;
|
|
11
11
|
export declare function assertUUID(value: string): asserts value is UUID;
|
|
12
|
+
/**
|
|
13
|
+
* The version a UUID declares in its 13th hex digit, so a caller can tell a time-ordered v7
|
|
14
|
+
* from a random v4 before reading any version-specific field.
|
|
15
|
+
*
|
|
16
|
+
* @param id - A validated UUID
|
|
17
|
+
* @returns The version number, 0 to 15.
|
|
18
|
+
* @example
|
|
19
|
+
* getVersion(generateUUID()); // 7, from "@sdxc/uuid/v7"
|
|
20
|
+
*/
|
|
21
|
+
export declare function getVersion(id: UUID): number;
|
|
12
22
|
export { InvalidUUIDFormatError, InvalidUUIDLengthError, InvalidUUIDTypeError };
|
package/dist/index.js
CHANGED
|
@@ -21,4 +21,16 @@ export function assertUUID(value) {
|
|
|
21
21
|
throw new InvalidUUIDFormatError(value);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The version a UUID declares in its 13th hex digit, so a caller can tell a time-ordered v7
|
|
26
|
+
* from a random v4 before reading any version-specific field.
|
|
27
|
+
*
|
|
28
|
+
* @param id - A validated UUID
|
|
29
|
+
* @returns The version number, 0 to 15.
|
|
30
|
+
* @example
|
|
31
|
+
* getVersion(generateUUID()); // 7, from "@sdxc/uuid/v7"
|
|
32
|
+
*/
|
|
33
|
+
export function getVersion(id) {
|
|
34
|
+
return Number.parseInt(id[14], 16);
|
|
35
|
+
}
|
|
24
36
|
export { InvalidUUIDFormatError, InvalidUUIDLengthError, InvalidUUIDTypeError };
|
package/dist/v7.d.ts
CHANGED
|
@@ -19,3 +19,14 @@ import type { UUID } from "./index.js";
|
|
|
19
19
|
* first < second; // true
|
|
20
20
|
*/
|
|
21
21
|
export declare function generateUUID(): UUID;
|
|
22
|
+
/**
|
|
23
|
+
* The Unix millisecond timestamp a version 7 UUID starts with: the moment it was generated.
|
|
24
|
+
* The first 48 bits carry it for v7 only, so check `getVersion` first for a UUID of
|
|
25
|
+
* unknown origin.
|
|
26
|
+
*
|
|
27
|
+
* @param id - A version 7 UUID
|
|
28
|
+
* @returns Milliseconds since the Unix epoch.
|
|
29
|
+
* @example
|
|
30
|
+
* new Date(getTimestamp(generateUUID())); // now
|
|
31
|
+
*/
|
|
32
|
+
export declare function getTimestamp(id: UUID): number;
|
package/dist/v7.js
CHANGED
|
@@ -42,6 +42,19 @@ export function generateUUID() {
|
|
|
42
42
|
assertUUID(id);
|
|
43
43
|
return id;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The Unix millisecond timestamp a version 7 UUID starts with: the moment it was generated.
|
|
47
|
+
* The first 48 bits carry it for v7 only, so check `getVersion` first for a UUID of
|
|
48
|
+
* unknown origin.
|
|
49
|
+
*
|
|
50
|
+
* @param id - A version 7 UUID
|
|
51
|
+
* @returns Milliseconds since the Unix epoch.
|
|
52
|
+
* @example
|
|
53
|
+
* new Date(getTimestamp(generateUUID())); // now
|
|
54
|
+
*/
|
|
55
|
+
export function getTimestamp(id) {
|
|
56
|
+
return Number.parseInt(id.slice(0, 8) + id.slice(9, 13), 16);
|
|
57
|
+
}
|
|
45
58
|
/**
|
|
46
59
|
* Moves the clock to `now`, or past the last timestamp when `now` is not later than it.
|
|
47
60
|
* A new millisecond seeds the counter randomly below its midpoint, leaving at least 2048
|
package/package.json
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdxc/uuid",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.11.3",
|
|
4
4
|
"description": "Validate and assert UUIDs behind a branded UUID type, and generate v4 or v7 values",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sdxc-uuid": "dist/cli.js"
|
|
8
|
+
},
|
|
6
9
|
"type": "module",
|
|
7
10
|
"exports": {
|
|
8
11
|
".": "./dist/index.js",
|
|
9
12
|
"./v4": "./dist/v4.js",
|
|
10
13
|
"./v7": "./dist/v7.js"
|
|
11
14
|
},
|
|
12
|
-
"
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@sdxc/cli": "2026.11.3",
|
|
17
|
+
"@sdxc/result": "2026.10.13",
|
|
18
|
+
"remix": "3.1.0"
|
|
19
|
+
},
|
|
20
|
+
"gitHead": "d1dd4030f8df68b8a170940a3f790ea8654d3676",
|
|
13
21
|
"publishConfig": {
|
|
14
22
|
"access": "public"
|
|
15
23
|
},
|
|
@@ -18,7 +26,7 @@
|
|
|
18
26
|
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
19
27
|
"directory": "packages/uuid"
|
|
20
28
|
},
|
|
21
|
-
"homepage": "https://sdxc.
|
|
29
|
+
"homepage": "https://sdxc.dev/api/uuid",
|
|
22
30
|
"author": {
|
|
23
31
|
"name": "Sergio Xalambrí",
|
|
24
32
|
"url": "https://sergiodxa.com"
|