@smonn/ids 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +60 -930
  2. package/dist/cli.mjs +398 -513
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{codec-shell-C7_B4oum.mjs → codec-shell-CW2sD6BU.mjs} +3 -3
  5. package/dist/{codec-shell-C7_B4oum.mjs.map → codec-shell-CW2sD6BU.mjs.map} +1 -1
  6. package/dist/drizzle.d.mts +32 -2
  7. package/dist/drizzle.d.mts.map +1 -0
  8. package/dist/index.mjs +1 -1
  9. package/dist/{key-material-DUHhmMq-.mjs → key-material-gOnqTNoV.mjs} +3 -3
  10. package/dist/{key-material-DUHhmMq-.mjs.map → key-material-gOnqTNoV.mjs.map} +1 -1
  11. package/dist/kysely.d.mts.map +1 -1
  12. package/dist/kysely.mjs +2 -3
  13. package/dist/kysely.mjs.map +1 -1
  14. package/dist/{opaque-BQOlZ2oD.mjs → opaque-BpqxV8oB.mjs} +8 -8
  15. package/dist/{opaque-BQOlZ2oD.mjs.map → opaque-BpqxV8oB.mjs.map} +1 -1
  16. package/dist/opaque.mjs +1 -1
  17. package/dist/prisma.d.mts +1 -2
  18. package/dist/prisma.d.mts.map +1 -1
  19. package/dist/prisma.mjs +1 -2
  20. package/dist/prisma.mjs.map +1 -1
  21. package/dist/{reverse-C12D1btB.mjs → reverse-d5uEoIET.mjs} +4 -4
  22. package/dist/{reverse-C12D1btB.mjs.map → reverse-d5uEoIET.mjs.map} +1 -1
  23. package/dist/reverse.mjs +1 -1
  24. package/dist/{signed-CwqKTFaQ.mjs → signed-BnRSC03a.mjs} +13 -13
  25. package/dist/signed-BnRSC03a.mjs.map +1 -0
  26. package/dist/signed.d.mts.map +1 -1
  27. package/dist/signed.mjs +1 -1
  28. package/dist/{timestamp-BjIMQkJf.mjs → timestamp-BbZL8hwg.mjs} +5 -5
  29. package/dist/{timestamp-BjIMQkJf.mjs.map → timestamp-BbZL8hwg.mjs.map} +1 -1
  30. package/dist/{timestamp-bytes-Bbg6Y66Z.mjs → timestamp-bytes-DoFjLjDp.mjs} +3 -2
  31. package/dist/timestamp-bytes-DoFjLjDp.mjs.map +1 -0
  32. package/dist/{wrapped-DKOsN_dq.mjs → wrapped-BI9UXnAm.mjs} +21 -16
  33. package/dist/wrapped-BI9UXnAm.mjs.map +1 -0
  34. package/dist/wrapped.d.mts.map +1 -1
  35. package/dist/wrapped.mjs +1 -1
  36. package/package.json +5 -5
  37. package/dist/drizzle-CHtyDXpv.d.mts +0 -33
  38. package/dist/drizzle-CHtyDXpv.d.mts.map +0 -1
  39. package/dist/signed-CwqKTFaQ.mjs.map +0 -1
  40. package/dist/timestamp-bytes-Bbg6Y66Z.mjs.map +0 -1
  41. package/dist/wrapped-DKOsN_dq.mjs.map +0 -1
package/dist/cli.mjs CHANGED
@@ -1,10 +1,44 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as isIdsError } from "./error-Cp5qYZcv.mjs";
3
- import { t as createTimestampId } from "./timestamp-BjIMQkJf.mjs";
4
- import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BQOlZ2oD.mjs";
5
- import { t as createReverseTimestampId } from "./reverse-C12D1btB.mjs";
6
- import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-CwqKTFaQ.mjs";
7
- import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-DKOsN_dq.mjs";
3
+ import { t as createTimestampId } from "./timestamp-BbZL8hwg.mjs";
4
+ import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BpqxV8oB.mjs";
5
+ import { t as createReverseTimestampId } from "./reverse-d5uEoIET.mjs";
6
+ import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-BnRSC03a.mjs";
7
+ import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-BI9UXnAm.mjs";
8
+ //#region src/cli/key-io.ts
9
+ function isKeyFormatError(result) {
10
+ return result !== "hex" && result !== "base64url";
11
+ }
12
+ function parseKeyFormatFlag(values) {
13
+ const fromFlag = values.get("--key-format");
14
+ if (fromFlag === void 0) return void 0;
15
+ if (fromFlag === "") return "--key-format requires a value";
16
+ if (fromFlag === "hex" || fromFlag === "base64url") return fromFlag;
17
+ return `--key-format must be hex or base64url, got '${fromFlag}'`;
18
+ }
19
+ function parseKeyFormatFromFlag(values) {
20
+ const fromFlag = parseKeyFormatFlag(values);
21
+ if (fromFlag === void 0) return "hex";
22
+ return fromFlag;
23
+ }
24
+ function parseKeyFormat(values, opts, facet) {
25
+ const fromFlag = parseKeyFormatFlag(values);
26
+ if (fromFlag !== void 0) return fromFlag;
27
+ const fromEnv = (opts.env ?? process.env)[facet.formatEnvVar];
28
+ if (fromEnv === void 0 || fromEnv === "") return "hex";
29
+ if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
30
+ return `${facet.formatEnvVar} must be hex or base64url, got '${fromEnv}'`;
31
+ }
32
+ async function loadKey(opts, format, facet) {
33
+ const raw = (opts.env ?? process.env)[facet.envVar];
34
+ if (raw === void 0 || raw === "") return `missing ${facet.envVar} environment variable`;
35
+ try {
36
+ return await facet.import(facet.decode(raw, format));
37
+ } catch (err) {
38
+ return err.message;
39
+ }
40
+ }
41
+ //#endregion
8
42
  //#region src/cli/codec-options.ts
9
43
  function codecOpts(opts) {
10
44
  const o = { allowDuplicateBrand: true };
@@ -13,73 +47,6 @@ function codecOpts(opts) {
13
47
  return o;
14
48
  }
15
49
  //#endregion
16
- //#region src/cli/format.ts
17
- function formatCliError(err) {
18
- return isIdsError(err) ? `${err.code}: ${err.message}` : err instanceof Error ? err.message : String(err);
19
- }
20
- function formatWrappedInspectOutput(result) {
21
- const inputLine = describeInputForm(result.input, result.canonical);
22
- return [
23
- `brand: ${result.brand}`,
24
- `lookup-key: ${result.lookupKey.toString()}`,
25
- `canonical: ${result.canonical}`,
26
- `input: ${inputLine}`,
27
- ""
28
- ].join("\n");
29
- }
30
- function formatSignedInspectOutput(result) {
31
- const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
32
- const inputLine = describeInputForm(result.input, result.canonical);
33
- const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
34
- if (result.verification !== void 0) lines.push(`verification: ${result.verification}`);
35
- lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
36
- return lines.join("\n");
37
- }
38
- function formatInspectOutput(result) {
39
- const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
40
- const inputLine = describeInputForm(result.input, result.canonical);
41
- return [
42
- `brand: ${result.brand}`,
43
- `timestamp: ${result.timestamp.toISOString()} (${relative})`,
44
- `canonical: ${result.canonical}`,
45
- `input: ${inputLine}`,
46
- ""
47
- ].join("\n");
48
- }
49
- function describeInputForm(input, canonical) {
50
- if (input === canonical) return "canonical";
51
- const notes = [];
52
- if (input !== input.toLowerCase()) notes.push("was uppercase");
53
- if (/[ilo]/i.test(input.slice(4))) notes.push("used Crockford aliases");
54
- return `not canonical (${notes.join(" + ")})`;
55
- }
56
- const msPerMinute = 60 * 1e3;
57
- const msPerHour = 60 * msPerMinute;
58
- const msPerDay = 24 * msPerHour;
59
- const daysPerMonth = 30.44;
60
- const monthsPerYear = 12;
61
- function formatRelative(thenMs, nowMs) {
62
- const diff = nowMs - thenMs;
63
- const abs = Math.abs(diff);
64
- const suffix = diff < 0 ? "from now" : "ago";
65
- const head = headUnits(abs);
66
- return head === "" ? "just now" : `${head} ${suffix}`;
67
- }
68
- function headUnits(abs) {
69
- if (abs < msPerMinute) return "";
70
- if (abs < msPerHour) return unit(Math.round(abs / msPerMinute), "minute");
71
- if (abs < msPerDay) return unit(Math.round(abs / msPerHour), "hour");
72
- if (abs < msPerDay * daysPerMonth) return unit(Math.round(abs / msPerDay), "day");
73
- const totalMonths = Math.round(abs / (msPerDay * daysPerMonth));
74
- if (totalMonths < monthsPerYear) return unit(totalMonths, "month");
75
- const years = Math.floor(totalMonths / monthsPerYear);
76
- const months = totalMonths % monthsPerYear;
77
- return months === 0 ? unit(years, "year") : `${unit(years, "year")} ${unit(months, "month")}`;
78
- }
79
- function unit(n, name) {
80
- return `${n} ${n === 1 ? name : `${name}s`}`;
81
- }
82
- //#endregion
83
50
  //#region src/cli/constants.ts
84
51
  const maxGenerateCount = 1e4;
85
52
  //#endregion
@@ -95,19 +62,12 @@ function splitFlagToken(arg) {
95
62
  inlineValue: arg.slice(eq + 1)
96
63
  };
97
64
  }
98
- function splitFlags(args) {
65
+ function splitFlags(args, valueFlags) {
99
66
  const flags = /* @__PURE__ */ new Set();
100
67
  const values = /* @__PURE__ */ new Map();
101
68
  const positionals = [];
102
69
  const errors = [];
103
70
  const seenFlags = /* @__PURE__ */ new Set();
104
- const valueFlags = new Set([
105
- "--count",
106
- "-c",
107
- "--bits",
108
- "--key-format",
109
- "--kind"
110
- ]);
111
71
  const addFlag = (flag) => {
112
72
  const canonical = canonicalFlag(flag);
113
73
  if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
@@ -117,11 +77,6 @@ function splitFlags(args) {
117
77
  for (let i = 0; i < args.length; i++) {
118
78
  const raw = args[i];
119
79
  const { flag, inlineValue } = splitFlagToken(raw);
120
- if (flag === "--opaque" || flag === "--wrapped" || flag === "--reverse" || flag === "--signed") {
121
- addFlag(flag);
122
- if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
123
- continue;
124
- }
125
80
  if (valueFlags.has(flag)) {
126
81
  if (inlineValue !== void 0) {
127
82
  addFlag(flag);
@@ -141,6 +96,7 @@ function splitFlags(args) {
141
96
  }
142
97
  if (flag.startsWith("-")) {
143
98
  addFlag(flag);
99
+ if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
144
100
  continue;
145
101
  }
146
102
  positionals.push(raw);
@@ -156,7 +112,7 @@ function canonicalFlag(flag) {
156
112
  if (flag === "-c") return "--count";
157
113
  return flag;
158
114
  }
159
- const knownFlags = new Set([
115
+ const knownFlags = /* @__PURE__ */ new Set([
160
116
  "--opaque",
161
117
  "--wrapped",
162
118
  "--reverse",
@@ -199,211 +155,267 @@ function isKindError(result) {
199
155
  return result !== "u32" && result !== "i32" && result !== "u64" && result !== "i64";
200
156
  }
201
157
  //#endregion
202
- //#region src/cli/key-io.ts
203
- function isKeyFormatError(result) {
204
- return result !== "hex" && result !== "base64url";
158
+ //#region src/cli/format.ts
159
+ function formatCliError(err) {
160
+ return isIdsError(err) ? `${err.code}: ${err.message}` : err instanceof Error ? err.message : String(err);
205
161
  }
206
- function parseKeyFormatFlag(values) {
207
- const fromFlag = values.get("--key-format");
208
- if (fromFlag === void 0) return void 0;
209
- if (fromFlag === "") return "--key-format requires a value";
210
- if (fromFlag === "hex" || fromFlag === "base64url") return fromFlag;
211
- return `--key-format must be hex or base64url, got '${fromFlag}'`;
162
+ function formatWrappedInspectOutput(result) {
163
+ const inputLine = describeInputForm(result.input, result.canonical);
164
+ return [
165
+ `brand: ${result.brand}`,
166
+ `lookup-key: ${result.lookupKey.toString()}`,
167
+ `canonical: ${result.canonical}`,
168
+ `input: ${inputLine}`,
169
+ ""
170
+ ].join("\n");
212
171
  }
213
- function parseKeyFormatFromFlag(values) {
214
- const fromFlag = parseKeyFormatFlag(values);
215
- if (fromFlag === void 0) return "hex";
216
- return fromFlag;
172
+ function formatSignedInspectOutput(result) {
173
+ const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
174
+ const inputLine = describeInputForm(result.input, result.canonical);
175
+ const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
176
+ lines.push(`verification: ${result.verification}`);
177
+ lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
178
+ return lines.join("\n");
217
179
  }
218
- function parseKeyFormat(values, opts, facet) {
219
- const fromFlag = parseKeyFormatFlag(values);
220
- if (fromFlag !== void 0) return fromFlag;
221
- const fromEnv = (opts.env ?? process.env)[facet.formatEnvVar];
222
- if (fromEnv === void 0 || fromEnv === "") return "hex";
223
- if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
224
- return `${facet.formatEnvVar} must be hex or base64url, got '${fromEnv}'`;
180
+ function formatInspectOutput(result) {
181
+ const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
182
+ const inputLine = describeInputForm(result.input, result.canonical);
183
+ return [
184
+ `brand: ${result.brand}`,
185
+ `timestamp: ${result.timestamp.toISOString()} (${relative})`,
186
+ `canonical: ${result.canonical}`,
187
+ `input: ${inputLine}`,
188
+ ""
189
+ ].join("\n");
225
190
  }
226
- async function loadKey(opts, format, facet) {
227
- const raw = (opts.env ?? process.env)[facet.envVar];
228
- if (raw === void 0 || raw === "") return `missing ${facet.envVar} environment variable`;
229
- try {
230
- return await facet.import(facet.decode(raw, format));
231
- } catch (err) {
232
- return err.message;
233
- }
191
+ function describeInputForm(input, canonical) {
192
+ if (input === canonical) return "canonical";
193
+ const notes = [];
194
+ if (input !== input.toLowerCase()) notes.push("was uppercase");
195
+ if (/[ilo]/i.test(input.slice(4))) notes.push("used Crockford aliases");
196
+ return `not canonical (${notes.join(" + ")})`;
234
197
  }
235
- //#endregion
236
- //#region src/cli/opaque-key.ts
237
- const opaqueFacet = {
238
- envVar: "IDS_KEY",
239
- formatEnvVar: "IDS_KEY_FORMAT",
240
- decode: decodeOpaqueKey,
241
- import: importOpaqueKey
242
- };
243
- function parseKeygenFormat(values) {
244
- return parseKeyFormatFromFlag(values);
198
+ const msPerMinute = 60 * 1e3;
199
+ const msPerHour = 60 * msPerMinute;
200
+ const msPerDay = 24 * msPerHour;
201
+ const daysPerMonth = 30.44;
202
+ const monthsPerYear = 12;
203
+ function formatRelative(thenMs, nowMs) {
204
+ const diff = nowMs - thenMs;
205
+ const abs = Math.abs(diff);
206
+ const suffix = diff < 0 ? "from now" : "ago";
207
+ const head = headUnits(abs);
208
+ return head === "" ? "just now" : `${head} ${suffix}`;
245
209
  }
246
- function parseOpaqueKeyFormat(values, opts) {
247
- return parseKeyFormat(values, opts, opaqueFacet);
210
+ function headUnits(abs) {
211
+ if (abs < msPerMinute) return "";
212
+ if (abs < msPerHour) return unit(Math.round(abs / msPerMinute), "minute");
213
+ if (abs < msPerDay) return unit(Math.round(abs / msPerHour), "hour");
214
+ if (abs < msPerDay * daysPerMonth) return unit(Math.round(abs / msPerDay), "day");
215
+ const totalMonths = Math.round(abs / (msPerDay * daysPerMonth));
216
+ if (totalMonths < monthsPerYear) return unit(totalMonths, "month");
217
+ const years = Math.floor(totalMonths / monthsPerYear);
218
+ const months = totalMonths % monthsPerYear;
219
+ return months === 0 ? unit(years, "year") : `${unit(years, "year")} ${unit(months, "month")}`;
248
220
  }
249
- async function loadOpaqueKey(opts, format) {
250
- return loadKey(opts, format, opaqueFacet);
221
+ function unit(n, name) {
222
+ return `${n} ${n === 1 ? name : `${name}s`}`;
251
223
  }
252
224
  //#endregion
253
- //#region src/cli/signing-key.ts
254
- const signingFacet = {
255
- envVar: "IDS_SIGNING_KEY",
256
- formatEnvVar: "IDS_SIGNING_KEY_FORMAT",
257
- decode: decodeSigningKey,
258
- import: importSigningKey
225
+ //#region src/cli/variants.ts
226
+ const timestampVariant = {
227
+ inspectMode: "readable",
228
+ construct(brand, opts) {
229
+ try {
230
+ return createTimestampId(brand, codecOpts(opts));
231
+ } catch (err) {
232
+ return formatCliError(err);
233
+ }
234
+ }
259
235
  };
260
- function parseSigningKeyFormat(values, opts) {
261
- return parseKeyFormat(values, opts, signingFacet);
262
- }
263
- async function loadSigningKey(opts, format) {
264
- return loadKey(opts, format, signingFacet);
236
+ const opaqueVariant = {
237
+ flag: "--opaque",
238
+ key: {
239
+ envVar: "IDS_KEY",
240
+ formatEnvVar: "IDS_KEY_FORMAT",
241
+ encode: encodeOpaqueKey,
242
+ decode: decodeOpaqueKey,
243
+ import: importOpaqueKey
244
+ },
245
+ inspectMode: "keyed-readable",
246
+ construct(brand, opts, key) {
247
+ try {
248
+ return createOpaqueTimestampId(brand, {
249
+ key,
250
+ ...codecOpts(opts)
251
+ });
252
+ } catch (err) {
253
+ return formatCliError(err);
254
+ }
255
+ }
256
+ };
257
+ const reverseVariant = {
258
+ flag: "--reverse",
259
+ inspectMode: "readable",
260
+ construct(brand, opts) {
261
+ try {
262
+ return createReverseTimestampId(brand, codecOpts(opts));
263
+ } catch (err) {
264
+ return formatCliError(err);
265
+ }
266
+ }
267
+ };
268
+ const wrappedVariant = {
269
+ flag: "--wrapped",
270
+ key: {
271
+ envVar: "IDS_WRAPPING_KEY",
272
+ formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
273
+ encode: encodeWrappingKey,
274
+ decode: decodeWrappingKey,
275
+ import: importWrappingKey
276
+ },
277
+ inspectMode: "unwrap",
278
+ extraFlags: ["--kind"],
279
+ construct(brand, _opts, key, values) {
280
+ const kind = parseKind(values ?? /* @__PURE__ */ new Map());
281
+ if (kind === void 0) return "--kind is required with --wrapped";
282
+ if (isKindError(kind)) return kind;
283
+ try {
284
+ return createWrappedKeyId(brand, {
285
+ kind,
286
+ keys: [key],
287
+ allowDuplicateBrand: true
288
+ });
289
+ } catch (err) {
290
+ return formatCliError(err);
291
+ }
292
+ }
293
+ };
294
+ const signedVariant = {
295
+ flag: "--signed",
296
+ key: {
297
+ envVar: "IDS_SIGNING_KEY",
298
+ formatEnvVar: "IDS_SIGNING_KEY_FORMAT",
299
+ encode: encodeSigningKey,
300
+ decode: decodeSigningKey,
301
+ import: importSigningKey
302
+ },
303
+ inspectMode: "verify",
304
+ construct(brand, opts, key) {
305
+ try {
306
+ return createSignedTimestampId(brand, {
307
+ keys: [key],
308
+ ...codecOpts(opts)
309
+ });
310
+ } catch (err) {
311
+ return formatCliError(err);
312
+ }
313
+ }
314
+ };
315
+ const conflictPriorityOrder = [
316
+ signedVariant,
317
+ reverseVariant,
318
+ wrappedVariant,
319
+ opaqueVariant
320
+ ];
321
+ const generatePolicy = {
322
+ default: timestampVariant,
323
+ selectable: [
324
+ opaqueVariant,
325
+ reverseVariant,
326
+ signedVariant
327
+ ],
328
+ intrinsicFlags: ["--count", "-c"]
329
+ };
330
+ const inspectPolicy = {
331
+ default: timestampVariant,
332
+ selectable: [
333
+ reverseVariant,
334
+ wrappedVariant,
335
+ opaqueVariant,
336
+ signedVariant
337
+ ],
338
+ intrinsicFlags: []
339
+ };
340
+ const keygenPolicy = {
341
+ default: opaqueVariant,
342
+ selectable: [wrappedVariant, signedVariant],
343
+ intrinsicFlags: ["--bits"]
344
+ };
345
+ //#endregion
346
+ //#region src/cli/dispatch.ts
347
+ function deriveAllowedFlags(policy) {
348
+ const flags = new Set(policy.intrinsicFlags);
349
+ let hasKeyed = policy.default.key !== void 0;
350
+ for (const v of policy.selectable) {
351
+ if (v.flag !== void 0) flags.add(v.flag);
352
+ if (v.key !== void 0) hasKeyed = true;
353
+ if (v.extraFlags !== void 0) for (const f of v.extraFlags) flags.add(f);
354
+ }
355
+ if (hasKeyed) flags.add("--key-format");
356
+ return flags;
357
+ }
358
+ function resolveVariant(policy, flags) {
359
+ const selected = conflictPriorityOrder.filter((v) => policy.selectable.some((d) => d === v) && v.flag !== void 0 && flags.has(v.flag));
360
+ if (selected.length === 0) return policy.default;
361
+ if (selected.length === 1) return selected[0];
362
+ return `cannot use ${selected[0].flag} and ${selected[1].flag} together`;
363
+ }
364
+ async function buildCodec(variant, brand, values, opts) {
365
+ let key;
366
+ if (variant.key !== void 0) {
367
+ const format = parseKeyFormat(values, opts, variant.key);
368
+ if (isKeyFormatError(format)) return format;
369
+ const keyResult = await loadKey(opts, format, variant.key);
370
+ if (typeof keyResult === "string") return keyResult;
371
+ key = keyResult;
372
+ }
373
+ return variant.construct(brand, opts, key, values);
265
374
  }
266
375
  //#endregion
267
376
  //#region src/cli/commands/generate.ts
268
- function runGenerate(args, opts) {
269
- const { flags, values, positionals, errors } = splitFlags(args);
270
- const unsupported = unsupportedFlagForCommand("generate", flags, new Set([
271
- "--count",
272
- "-c",
273
- "--opaque",
274
- "--reverse",
275
- "--signed",
276
- "--key-format"
277
- ]));
377
+ async function runGenerate(args, opts) {
378
+ const allowedFlags = deriveAllowedFlags(generatePolicy);
379
+ const selectorFlags = new Set(generatePolicy.selectable.map((v) => v.flag).filter((f) => f !== void 0));
380
+ const { flags, values, positionals, errors } = splitFlags(args, new Set([...allowedFlags].filter((f) => !selectorFlags.has(f))));
381
+ const unsupported = unsupportedFlagForCommand("generate", flags, allowedFlags);
278
382
  if (unsupported !== void 0) {
279
383
  opts.stderr(unsupported + "\n");
280
- return Promise.resolve(1);
384
+ return 1;
281
385
  }
282
386
  if (errors[0] !== void 0) {
283
387
  opts.stderr(errors[0] + "\n");
284
- return Promise.resolve(1);
388
+ return 1;
285
389
  }
286
390
  const extra = positionals[1];
287
391
  if (extra !== void 0) {
288
392
  opts.stderr(`unexpected argument: ${extra}\n`);
289
- return Promise.resolve(1);
393
+ return 1;
290
394
  }
291
395
  const [brand] = positionals;
292
396
  const count = parseCount(values);
293
397
  if (typeof count === "string") {
294
398
  opts.stderr(count + "\n");
295
- return Promise.resolve(1);
296
- }
297
- const opaque = flags.has("--opaque");
298
- const reverse = flags.has("--reverse");
299
- const signed = flags.has("--signed");
300
- if (reverse && opaque) {
301
- opts.stderr("cannot use --reverse and --opaque together\n");
302
- return Promise.resolve(1);
303
- }
304
- if (signed && opaque) {
305
- opts.stderr("cannot use --signed and --opaque together\n");
306
- return Promise.resolve(1);
307
- }
308
- if (signed && reverse) {
309
- opts.stderr("cannot use --signed and --reverse together\n");
310
- return Promise.resolve(1);
311
- }
312
- if (!opaque && !signed && flags.has("--key-format")) {
313
- opts.stderr("--key-format requires --opaque or --signed\n");
314
- return Promise.resolve(1);
315
- }
316
- if (opaque) {
317
- const format = parseOpaqueKeyFormat(values, opts);
318
- if (isKeyFormatError(format)) {
319
- opts.stderr(format + "\n");
320
- return Promise.resolve(1);
321
- }
322
- return runOpaqueGenerate(brand ?? "", count, format, opts);
323
- }
324
- if (signed) {
325
- const format = parseSigningKeyFormat(values, opts);
326
- if (isKeyFormatError(format)) {
327
- opts.stderr(format + "\n");
328
- return Promise.resolve(1);
329
- }
330
- return runSignedGenerate(brand ?? "", count, format, opts);
331
- }
332
- if (reverse) {
333
- let codec;
334
- try {
335
- codec = createReverseTimestampId(brand ?? "", codecOpts(opts));
336
- } catch (err) {
337
- opts.stderr(formatCliError(err) + "\n");
338
- return Promise.resolve(1);
339
- }
340
- for (let i = 0; i < count; i++) opts.stdout(codec.generate() + "\n");
341
- return Promise.resolve(0);
342
- }
343
- let codec;
344
- try {
345
- codec = createTimestampId(brand ?? "", codecOpts(opts));
346
- } catch (err) {
347
- opts.stderr(formatCliError(err) + "\n");
348
- return Promise.resolve(1);
349
- }
350
- for (let i = 0; i < count; i++) opts.stdout(codec.generate() + "\n");
351
- return Promise.resolve(0);
352
- }
353
- async function runOpaqueGenerate(brand, count, format, opts) {
354
- const keyResult = await loadOpaqueKey(opts, format);
355
- if (typeof keyResult === "string") {
356
- opts.stderr(keyResult + "\n");
357
399
  return 1;
358
400
  }
359
- let codec;
360
- try {
361
- codec = createOpaqueTimestampId(brand, {
362
- key: keyResult,
363
- ...codecOpts(opts)
364
- });
365
- } catch (err) {
366
- opts.stderr(formatCliError(err) + "\n");
401
+ const variant = resolveVariant(generatePolicy, flags);
402
+ if (typeof variant === "string") {
403
+ opts.stderr(variant + "\n");
367
404
  return 1;
368
405
  }
369
- for (let i = 0; i < count; i++) opts.stdout(await codec.generate() + "\n");
370
- return 0;
371
- }
372
- async function runSignedGenerate(brand, count, format, opts) {
373
- const keyResult = await loadSigningKey(opts, format);
374
- if (typeof keyResult === "string") {
375
- opts.stderr(keyResult + "\n");
406
+ if (variant.key === void 0 && flags.has("--key-format")) {
407
+ opts.stderr("--key-format requires --opaque or --signed\n");
376
408
  return 1;
377
409
  }
378
- let codec;
379
- try {
380
- codec = createSignedTimestampId(brand, {
381
- keys: [keyResult],
382
- allowDuplicateBrand: true,
383
- ...codecOpts(opts)
384
- });
385
- } catch (err) {
386
- opts.stderr(formatCliError(err) + "\n");
410
+ const codec = await buildCodec(variant, brand ?? "", values, opts);
411
+ if (typeof codec === "string") {
412
+ opts.stderr(codec + "\n");
387
413
  return 1;
388
414
  }
389
415
  for (let i = 0; i < count; i++) opts.stdout(await codec.generate() + "\n");
390
416
  return 0;
391
417
  }
392
418
  //#endregion
393
- //#region src/cli/wrapping-key.ts
394
- const wrappingFacet = {
395
- envVar: "IDS_WRAPPING_KEY",
396
- formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
397
- decode: decodeWrappingKey,
398
- import: importWrappingKey
399
- };
400
- function parseWrappingKeyFormat(values, opts) {
401
- return parseKeyFormat(values, opts, wrappingFacet);
402
- }
403
- async function loadWrappingKey(opts, format) {
404
- return loadKey(opts, format, wrappingFacet);
405
- }
406
- //#endregion
407
419
  //#region src/cli/usage.ts
408
420
  function usage() {
409
421
  return [
@@ -432,294 +444,167 @@ function usage() {
432
444
  }
433
445
  //#endregion
434
446
  //#region src/cli/commands/inspect.ts
435
- function runInspect(args, opts) {
436
- const { flags, values, positionals, errors } = splitFlags(args);
437
- const unsupported = unsupportedFlagForCommand("inspect", flags, new Set([
438
- "--opaque",
439
- "--wrapped",
440
- "--reverse",
441
- "--signed",
442
- "--kind",
443
- "--key-format"
444
- ]));
447
+ async function runInspect(args, opts) {
448
+ const allowedFlags = deriveAllowedFlags(inspectPolicy);
449
+ const selectorFlags = new Set(inspectPolicy.selectable.map((v) => v.flag).filter((f) => f !== void 0));
450
+ const { flags, values, positionals, errors } = splitFlags(args, new Set([...allowedFlags].filter((f) => !selectorFlags.has(f))));
451
+ const unsupported = unsupportedFlagForCommand("inspect", flags, allowedFlags);
445
452
  if (unsupported !== void 0) {
446
453
  opts.stderr(unsupported + "\n");
447
- return Promise.resolve(1);
454
+ return 1;
448
455
  }
449
456
  if (errors[0] !== void 0) {
450
457
  opts.stderr(errors[0] + "\n");
451
- return Promise.resolve(1);
458
+ return 1;
452
459
  }
453
460
  const [input] = positionals;
454
461
  if (input === void 0) {
455
462
  opts.stderr(usage());
456
- return Promise.resolve(1);
463
+ return 1;
457
464
  }
458
465
  const extra = positionals[1];
459
466
  if (extra !== void 0) {
460
467
  opts.stderr(`unexpected argument: ${extra}\n`);
461
- return Promise.resolve(1);
462
- }
463
- const opaque = flags.has("--opaque");
464
- const wrapped = flags.has("--wrapped");
465
- const reverse = flags.has("--reverse");
466
- const signed = flags.has("--signed");
467
- if (opaque && wrapped) {
468
- opts.stderr("cannot use --wrapped and --opaque together\n");
469
- return Promise.resolve(1);
470
- }
471
- if (reverse && opaque) {
472
- opts.stderr("cannot use --reverse and --opaque together\n");
473
- return Promise.resolve(1);
474
- }
475
- if (reverse && wrapped) {
476
- opts.stderr("cannot use --reverse and --wrapped together\n");
477
- return Promise.resolve(1);
478
- }
479
- if (signed && opaque) {
480
- opts.stderr("cannot use --signed and --opaque together\n");
481
- return Promise.resolve(1);
482
- }
483
- if (signed && wrapped) {
484
- opts.stderr("cannot use --signed and --wrapped together\n");
485
- return Promise.resolve(1);
468
+ return 1;
486
469
  }
487
- if (signed && reverse) {
488
- opts.stderr("cannot use --signed and --reverse together\n");
489
- return Promise.resolve(1);
470
+ const variant = resolveVariant(inspectPolicy, flags);
471
+ if (typeof variant === "string") {
472
+ opts.stderr(variant + "\n");
473
+ return 1;
490
474
  }
491
- if (!opaque && !wrapped && !signed && flags.has("--key-format")) {
475
+ if (variant.key === void 0 && flags.has("--key-format")) {
492
476
  opts.stderr("--key-format requires --opaque, --wrapped, or --signed\n");
493
- return Promise.resolve(1);
477
+ return 1;
494
478
  }
495
479
  const brand = input.slice(0, 3).toLowerCase();
496
- if (wrapped) {
497
- const kind = parseKind(values);
498
- if (kind === void 0) {
499
- opts.stderr("--kind is required with --wrapped\n");
500
- return Promise.resolve(1);
501
- }
502
- if (isKindError(kind)) {
503
- opts.stderr(kind + "\n");
504
- return Promise.resolve(1);
505
- }
506
- const format = parseWrappingKeyFormat(values, opts);
507
- if (isKeyFormatError(format)) {
508
- opts.stderr(format + "\n");
509
- return Promise.resolve(1);
510
- }
511
- return runWrappedInspect(brand, input, kind, format, opts);
512
- }
513
- if (opaque) {
514
- const format = parseOpaqueKeyFormat(values, opts);
515
- if (isKeyFormatError(format)) {
516
- opts.stderr(format + "\n");
517
- return Promise.resolve(1);
518
- }
519
- return runOpaqueInspect(brand, input, format, opts);
520
- }
521
- if (signed) {
522
- const format = parseSigningKeyFormat(values, opts);
523
- if (isKeyFormatError(format)) {
524
- opts.stderr(format + "\n");
525
- return Promise.resolve(1);
480
+ let verifyTimestamp;
481
+ let verifyCanonical;
482
+ let verifyNowMs;
483
+ if (variant.inspectMode === "verify") {
484
+ const fmtCheck = parseKeyFormat(values, opts, variant.key);
485
+ if (isKeyFormatError(fmtCheck)) {
486
+ opts.stderr(fmtCheck + "\n");
487
+ return 1;
526
488
  }
527
- return runSignedInspect(brand, input, format, opts);
528
- }
529
- if (reverse) {
530
- let reverseCodec;
489
+ let tsCodec;
531
490
  try {
532
- reverseCodec = createReverseTimestampId(brand, codecOpts(opts));
491
+ tsCodec = createTimestampId(brand, codecOpts(opts));
533
492
  } catch (err) {
534
493
  opts.stderr(formatCliError(err) + "\n");
535
- return Promise.resolve(1);
494
+ return 1;
536
495
  }
537
- const reverseValidation = reverseCodec["~standard"].validate(input);
538
- if (reverseValidation.issues) {
539
- opts.stderr(reverseValidation.issues[0].message + "\n");
540
- return Promise.resolve(1);
496
+ const structValidation = tsCodec["~standard"].validate(input);
497
+ if (structValidation.issues) {
498
+ opts.stderr(structValidation.issues[0].message + "\n");
499
+ return 1;
541
500
  }
542
- const reverseCanonical = reverseValidation.value;
543
- const reverseTimestamp = reverseCodec.extractTimestamp(reverseCanonical);
544
- const reverseNowMs = (opts.now ?? Date.now)();
545
- opts.stdout(formatInspectOutput({
546
- brand,
547
- timestamp: reverseTimestamp,
548
- canonical: reverseCanonical,
549
- input,
550
- nowMs: reverseNowMs
551
- }));
552
- return Promise.resolve(0);
553
- }
554
- let codec;
555
- try {
556
- codec = createTimestampId(brand, codecOpts(opts));
557
- } catch (err) {
558
- opts.stderr(formatCliError(err) + "\n");
559
- return Promise.resolve(1);
501
+ verifyCanonical = structValidation.value;
502
+ verifyTimestamp = tsCodec.extractTimestamp(verifyCanonical);
503
+ verifyNowMs = (opts.now ?? Date.now)();
560
504
  }
561
- const validation = codec["~standard"].validate(input);
562
- if (validation.issues) {
563
- opts.stderr(validation.issues[0].message + "\n");
564
- return Promise.resolve(1);
565
- }
566
- const canonical = validation.value;
567
- const timestamp = codec.extractTimestamp(canonical);
568
- const nowMs = (opts.now ?? Date.now)();
569
- opts.stdout(formatInspectOutput({
570
- brand,
571
- timestamp,
572
- canonical,
573
- input,
574
- nowMs
575
- }));
576
- return Promise.resolve(0);
577
- }
578
- async function runWrappedInspect(brand, input, kind, format, opts) {
579
- const keyResult = await loadWrappingKey(opts, format);
580
- if (typeof keyResult === "string") {
581
- opts.stderr(keyResult + "\n");
582
- return 1;
583
- }
584
- let codec;
585
- try {
586
- codec = createWrappedKeyId(brand, {
587
- kind,
588
- keys: [keyResult],
589
- allowDuplicateBrand: true
590
- });
591
- } catch (err) {
592
- opts.stderr(formatCliError(err) + "\n");
593
- return 1;
594
- }
595
- const validation = codec["~standard"].validate(input);
596
- if (validation.issues) {
597
- opts.stderr(validation.issues[0].message + "\n");
598
- return 1;
599
- }
600
- const canonical = validation.value;
601
- let lookupKey;
602
- try {
603
- lookupKey = await codec.unwrap(canonical);
604
- } catch (err) {
605
- opts.stderr(formatCliError(err) + "\n");
606
- return 1;
607
- }
608
- opts.stdout(formatWrappedInspectOutput({
609
- brand,
610
- lookupKey,
611
- canonical,
612
- input
613
- }));
614
- return 0;
615
- }
616
- async function runOpaqueInspect(brand, input, format, opts) {
617
- const keyResult = await loadOpaqueKey(opts, format);
618
- if (typeof keyResult === "string") {
619
- opts.stderr(keyResult + "\n");
620
- return 1;
621
- }
622
- let codec;
623
- try {
624
- codec = createOpaqueTimestampId(brand, {
625
- key: keyResult,
626
- ...codecOpts(opts)
627
- });
628
- } catch (err) {
629
- opts.stderr(formatCliError(err) + "\n");
630
- return 1;
631
- }
632
- const validation = codec["~standard"].validate(input);
633
- if (validation.issues) {
634
- opts.stderr(validation.issues[0].message + "\n");
635
- return 1;
636
- }
637
- const canonical = validation.value;
638
- const timestamp = await codec.extractTimestamp(canonical);
639
- const nowMs = (opts.now ?? Date.now)();
640
- opts.stderr("note: timestamp assumes IDS_KEY matches the key used at generation; a wrong key yields a plausible but incorrect timestamp\n");
641
- opts.stdout(formatInspectOutput({
642
- brand,
643
- timestamp,
644
- canonical,
645
- input,
646
- nowMs
647
- }));
648
- return 0;
649
- }
650
- async function runSignedInspect(brand, input, format, opts) {
651
- let structCodec;
652
- try {
653
- structCodec = createTimestampId(brand, codecOpts(opts));
654
- } catch (err) {
655
- opts.stderr(formatCliError(err) + "\n");
656
- return 1;
657
- }
658
- const validation = structCodec["~standard"].validate(input);
659
- if (validation.issues) {
660
- opts.stderr(validation.issues[0].message + "\n");
661
- return 1;
662
- }
663
- const canonical = validation.value;
664
- const timestamp = structCodec.extractTimestamp(canonical);
665
- const nowMs = (opts.now ?? Date.now)();
666
- if ((opts.env ?? process.env).IDS_SIGNING_KEY === void 0) {
667
- opts.stdout(formatSignedInspectOutput({
505
+ const codecOrError = await buildCodec(variant, brand, values, opts);
506
+ if (typeof codecOrError === "string") {
507
+ if (variant.inspectMode === "verify") opts.stdout(formatSignedInspectOutput({
668
508
  brand,
669
- timestamp,
670
- canonical,
509
+ timestamp: verifyTimestamp,
510
+ canonical: verifyCanonical,
671
511
  input,
672
- nowMs
512
+ nowMs: verifyNowMs,
513
+ verification: "unavailable"
673
514
  }));
674
- return 0;
675
- }
676
- const keyResult = await loadSigningKey(opts, format);
677
- if (typeof keyResult === "string") {
678
- opts.stderr(keyResult + "\n");
515
+ opts.stderr(codecOrError + "\n");
679
516
  return 1;
680
517
  }
681
- const verifyResult = await createSignedTimestampId(brand, {
682
- keys: [keyResult],
683
- allowDuplicateBrand: true,
684
- ...codecOpts(opts)
685
- }).safeVerify(input);
686
- if (!verifyResult.ok) {
687
- /* v8 ignore next 4 -- defensive: both codecs share the same wire parse so ParseError
688
- is unreachable after the createTimestampId pre-validation above passes */
689
- if (verifyResult.error !== "verification_failed") {
690
- opts.stderr(verifyResult.error + "\n");
518
+ let canonical;
519
+ if (variant.inspectMode !== "verify") {
520
+ const validation = codecOrError["~standard"].validate(input);
521
+ if (validation.issues) {
522
+ opts.stderr(validation.issues[0].message + "\n");
691
523
  return 1;
692
524
  }
693
- opts.stdout(formatSignedInspectOutput({
694
- brand,
695
- timestamp,
696
- canonical,
697
- input,
698
- nowMs,
699
- verification: "failed"
700
- }));
701
- return 1;
525
+ canonical = validation.value;
526
+ }
527
+ switch (variant.inspectMode) {
528
+ case "readable": {
529
+ const timestamp = codecOrError.extractTimestamp(canonical);
530
+ const nowMs = (opts.now ?? Date.now)();
531
+ opts.stdout(formatInspectOutput({
532
+ brand,
533
+ timestamp,
534
+ canonical,
535
+ input,
536
+ nowMs
537
+ }));
538
+ return 0;
539
+ }
540
+ case "keyed-readable": {
541
+ const timestamp = await codecOrError.extractTimestamp(canonical);
542
+ const nowMs = (opts.now ?? Date.now)();
543
+ opts.stderr("note: timestamp assumes IDS_KEY matches the key used at generation; a wrong key yields a plausible but incorrect timestamp\n");
544
+ opts.stdout(formatInspectOutput({
545
+ brand,
546
+ timestamp,
547
+ canonical,
548
+ input,
549
+ nowMs
550
+ }));
551
+ return 0;
552
+ }
553
+ case "unwrap": {
554
+ let lookupKey;
555
+ try {
556
+ lookupKey = await codecOrError.unwrap(canonical);
557
+ } catch (err) {
558
+ opts.stderr(formatCliError(err) + "\n");
559
+ return 1;
560
+ }
561
+ opts.stdout(formatWrappedInspectOutput({
562
+ brand,
563
+ lookupKey,
564
+ canonical,
565
+ input
566
+ }));
567
+ return 0;
568
+ }
569
+ case "verify": {
570
+ const verifyResult = await codecOrError.safeVerify(input);
571
+ if (!verifyResult.ok) {
572
+ /* v8 ignore next 4 -- defensive: both codecs share the same wire parse so ParseError
573
+ is unreachable after the createTimestampId pre-validation above passes */
574
+ if (verifyResult.error !== "verification_failed") {
575
+ opts.stderr(verifyResult.error + "\n");
576
+ return 1;
577
+ }
578
+ opts.stdout(formatSignedInspectOutput({
579
+ brand,
580
+ timestamp: verifyTimestamp,
581
+ canonical: verifyCanonical,
582
+ input,
583
+ nowMs: verifyNowMs,
584
+ verification: "failed"
585
+ }));
586
+ opts.stderr("verification_failed: verification failed\n");
587
+ return 1;
588
+ }
589
+ opts.stdout(formatSignedInspectOutput({
590
+ brand,
591
+ timestamp: verifyTimestamp,
592
+ canonical: verifyResult.id,
593
+ input,
594
+ nowMs: verifyNowMs,
595
+ verification: "ok"
596
+ }));
597
+ return 0;
598
+ }
702
599
  }
703
- opts.stdout(formatSignedInspectOutput({
704
- brand,
705
- timestamp,
706
- canonical: verifyResult.id,
707
- input,
708
- nowMs,
709
- verification: "ok"
710
- }));
711
- return 0;
712
600
  }
713
601
  //#endregion
714
602
  //#region src/cli/commands/keygen.ts
715
603
  function runKeygen(args, opts) {
716
- const { flags, values, positionals, errors } = splitFlags(args);
717
- const unsupported = unsupportedFlagForCommand("keygen", flags, new Set([
718
- "--signed",
719
- "--wrapped",
720
- "--bits",
721
- "--key-format"
722
- ]));
604
+ const allowedFlags = deriveAllowedFlags(keygenPolicy);
605
+ const variantExtraFlags = new Set(keygenPolicy.selectable.flatMap((v) => v.extraFlags ?? []));
606
+ const { flags, values, positionals, errors } = splitFlags(args, allowedFlags);
607
+ const unsupported = unsupportedFlagForCommand("keygen", flags, new Set([...allowedFlags].filter((f) => !variantExtraFlags.has(f))));
723
608
  if (unsupported !== void 0) {
724
609
  opts.stderr(unsupported + "\n");
725
610
  return Promise.resolve(1);
@@ -733,10 +618,9 @@ function runKeygen(args, opts) {
733
618
  opts.stderr(`unexpected argument: ${extra}\n`);
734
619
  return Promise.resolve(1);
735
620
  }
736
- const signed = flags.has("--signed");
737
- const wrapped = flags.has("--wrapped");
738
- if (signed && wrapped) {
739
- opts.stderr("cannot use --signed and --wrapped together\n");
621
+ const variant = resolveVariant(keygenPolicy, flags);
622
+ if (typeof variant === "string") {
623
+ opts.stderr(variant + "\n");
740
624
  return Promise.resolve(1);
741
625
  }
742
626
  const bits = parseBits(values);
@@ -744,18 +628,19 @@ function runKeygen(args, opts) {
744
628
  opts.stderr(bits + "\n");
745
629
  return Promise.resolve(1);
746
630
  }
747
- const format = parseKeygenFormat(values);
631
+ const format = parseKeyFormatFromFlag(values);
748
632
  if (isKeyFormatError(format)) {
749
633
  opts.stderr(format + "\n");
750
634
  return Promise.resolve(1);
751
635
  }
636
+ /* v8 ignore next 4 -- defensive guard; all keygenPolicy variants have key defined */
637
+ if (variant.key === void 0) {
638
+ opts.stderr("internal: keygen policy variant has no key facet\n");
639
+ return Promise.resolve(1);
640
+ }
752
641
  const bytes = new Uint8Array(bits / 8);
753
642
  crypto.getRandomValues(bytes);
754
- let encoded;
755
- if (signed) encoded = encodeSigningKey(bytes, format);
756
- else if (wrapped) encoded = encodeWrappingKey(bytes, format);
757
- else encoded = encodeOpaqueKey(bytes, format);
758
- opts.stdout(encoded + "\n");
643
+ opts.stdout(variant.key.encode(bytes, format) + "\n");
759
644
  return Promise.resolve(0);
760
645
  }
761
646
  //#endregion