@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 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 reaches your code as a `string`, and every layer below re-checks it because the type
6
- says nothing. This package checks once and hands back a `UUID`: the same string, carrying
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
- ```bash
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. [`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) installs alongside it when you
18
- want prefixed identifiers built on these values.
15
+ Cloudflare Workers.
19
16
 
20
17
  ## Usage
21
18
 
22
- ### Generate An Identifier
19
+ ### Generate an identifier
23
20
 
24
- Each UUID version is its own entry point, and each exports the same `generateUUID()`. The
25
- import path picks the version, so switching an identifier to the other one changes only that
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" as UUID
28
+ // "019a6f3e-5c21-7a4b-9d3e-8f1a2b3c4d5e", typed UUID
33
29
  ```
34
30
 
35
- A v7 value starts with the millisecond it was created, so identifiers sort in creation order
36
- and new database rows append to the end of their primary-key index. Import from
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" as UUID
38
+ // "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", typed UUID
44
39
  ```
45
40
 
46
- Either way the result is already branded, so it flows into anything typed as `UUID` without a
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 value passes when it is a string of exactly 36 characters shaped `8-4-4-4-12` in lowercase
62
- hex. Uppercase and the brace-wrapped form both fail, so the identifiers you store and compare
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
- ```typescript
69
- import { assertUUID } from "@sdxc/uuid";
56
+ ### Assert and tell the failures apart
70
57
 
71
- function readAccountId(value: string) {
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
- // something other than a string arrived
95
- }
96
-
97
- if (error instanceof InvalidUUIDLengthError) {
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
- Three classes rather than one message, so a truncated identifier and a mistyped one are
108
- distinguishable when you decide what to answer.
82
+ ## Command line
109
83
 
110
- ## API
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
- ### `UUID`
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
- The UUID string type, branded. A plain `string` is not assignable to it, and a `UUID` is
115
- assignable anywhere a `string` is, so the brand costs nothing at the call sites that only
116
- read the value.
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
- Reports whether a string is a UUID, narrowing it to `UUID` inside the branch that takes it.
121
- Use it where an invalid value is an outcome you handle.
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 a string to `UUID` for the rest of the enclosing scope, throwing when it is not one:
126
- `InvalidUUIDTypeError` for a non-string, `InvalidUUIDLengthError` for a string that is not 36
127
- characters, and `InvalidUUIDFormatError` for 36 characters in the wrong shape.
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 new version 7 UUID as defined by [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562):
132
- a 48-bit millisecond timestamp, a 12-bit counter and 62 random bits. Values sort in the order
133
- they were generated, including several created in the same millisecond, which matters on
134
- runtimes such as Cloudflare Workers where `Date.now()` holds still until the next I/O. The
135
- guarantee holds within one process or isolate; values from two processes in the same
136
- millisecond are unique and share no defined order.
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
- The timestamp is readable by anyone holding the value, including through a
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 new version 4 UUID from `crypto.randomUUID()`: 122 random bits, so two values carry
145
- no ordering and none reveals when it was created.
129
+ Returns a version 4 UUID from `crypto.randomUUID()`: 122 random bits, with no ordering and no
130
+ creation time.
146
131
 
147
- ### Errors
132
+ ### `InvalidUUIDTypeError`
148
133
 
149
- #### `InvalidUUIDTypeError`
134
+ Thrown for a non-string; the message names the type received.
150
135
 
151
- Validation received something that is not a string. The message names the runtime type that
152
- arrived, as in `Expected a string, got object`.
136
+ ### `InvalidUUIDLengthError`
153
137
 
154
- #### `InvalidUUIDLengthError`
138
+ Thrown for a string that is not 36 characters; the message names the length.
155
139
 
156
- A string of the wrong length, reported as `Invalid UUID length: 10`. The check runs before
157
- the shape check, so a truncated identifier says so instead of reading as malformed.
140
+ ### `InvalidUUIDFormatError`
158
141
 
159
- #### `InvalidUUIDFormatError`
142
+ Thrown for 36 characters that are not lowercase `8-4-4-4-12` hex; the message repeats the value.
160
143
 
161
- A 36-character string that is not lowercase `8-4-4-4-12` hex. The message repeats the value
162
- that failed, as in `Invalid UUID format: 550e8400_e29b_41d4_a716_446655440000`.
144
+ ### Types
163
145
 
164
- ## Pattern: Narrowing Once At The Edge
146
+ #### `UUID`
165
147
 
166
- An identifier arrives as text — a path segment, a query parameter, a form field — and every
167
- function under the handler wants it typed. Assert it where it enters, and the signatures
168
- below can ask for a `UUID` and be sure they have one:
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 GET(request: Request) {
161
+ export async function handler(request: Request) {
176
162
  let accountId = new URL(request.url).pathname.split("/").at(-1) ?? "";
177
- assertUUID(accountId);
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
- // no re-validation: the type already carries the check
177
+ return { id: accountId };
184
178
  }
185
179
  ```
186
180
 
187
- The throw is the point: an identifier that never validated never reaches the query.
188
-
189
- ## Pattern: Branching Instead Of Throwing
181
+ ## Pattern: Validate A Form Field Without Throwing
190
182
 
191
- A form field is a value a person can get wrong, which makes an exception the wrong shape for
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(input: FormData): { ok: true; accountId: UUID } | { ok: false } {
200
- let value = input.get("accountId");
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
- `isUUID` is `assertUUID` with the throw turned into a `false`, so the two agree on exactly
207
- which strings are identifiers.
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
- A UUID says nothing about what it identifies, which is why an identifier pasted into a bug
212
- report is so hard to place. [`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid)
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
+ });
@@ -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
+ });
@@ -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.10.12",
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
- "gitHead": "857ef87ddb0b2484e28b46431d0740fcc80e5f33",
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.sergiodxa.com/api/uuid",
29
+ "homepage": "https://sdxc.dev/api/uuid",
22
30
  "author": {
23
31
  "name": "Sergio Xalambrí",
24
32
  "url": "https://sergiodxa.com"