@smonn/ids 0.3.1 → 0.3.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.
@@ -0,0 +1,95 @@
1
+ import { a as writeTimestamp, c as toWireId, i as readTimestampMsFromBase32Suffix, l as validateBrand, n as registerBrand, t as wireMethods } from "./codec-shell-C0arqqX3.mjs";
2
+ //#region src/layouts/timestamp.ts
3
+ const randomByteLength = 10;
4
+ /** Writes a 16-byte timestamp-layout payload into codec-owned scratch. */
5
+ function buildPayload(ms, rng, buffer, randomView) {
6
+ writeTimestamp(ms, buffer);
7
+ rng(randomView);
8
+ }
9
+ /** Writes sentinel min/max random bytes into codec-owned scratch. */
10
+ function buildSentinelPayload(ms, fill, buffer, randomView) {
11
+ writeTimestamp(ms, buffer);
12
+ randomView.fill(fill);
13
+ }
14
+ /** Decodes the creation timestamp from a trusted wire ID. */
15
+ function extractTimestampFromId(prefix, id) {
16
+ return new Date(readTimestampMsFromBase32Suffix(id.slice(prefix.length)));
17
+ }
18
+ /** Layout ops binder for the Timestamp variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */
19
+ function createTimestampLayoutOps(prefix, rng) {
20
+ const buffer = new Uint8Array(16);
21
+ const randomView = new Uint8Array(buffer.buffer, 6, randomByteLength);
22
+ return {
23
+ generateAt: (ms) => {
24
+ buildPayload(ms, rng, buffer, randomView);
25
+ return toWireId(prefix, buffer);
26
+ },
27
+ extractTimestamp: (id) => extractTimestampFromId(prefix, id),
28
+ minIdForTime: (ms) => {
29
+ buildSentinelPayload(ms, 0, buffer, randomView);
30
+ return toWireId(prefix, buffer);
31
+ },
32
+ maxIdForTime: (ms) => {
33
+ buildSentinelPayload(ms, 255, buffer, randomView);
34
+ return toWireId(prefix, buffer);
35
+ },
36
+ exampleWireId: (ms) => {
37
+ buildPayload(ms, rng, buffer, randomView);
38
+ return toWireId(prefix, buffer);
39
+ }
40
+ };
41
+ }
42
+ //#endregion
43
+ //#region src/id.ts
44
+ const hexCharCodeToNibble = new Uint8Array(128);
45
+ for (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;
46
+ for (let i = 0; i < 6; i++) hexCharCodeToNibble[97 + i] = 10 + i;
47
+ const defaultOptions = {
48
+ now: Date.now,
49
+ rng: (target) => {
50
+ const s = crypto.randomUUID();
51
+ target[0] = hexCharCodeToNibble[s.charCodeAt(0)] << 4 | hexCharCodeToNibble[s.charCodeAt(1)];
52
+ target[1] = hexCharCodeToNibble[s.charCodeAt(2)] << 4 | hexCharCodeToNibble[s.charCodeAt(3)];
53
+ target[2] = hexCharCodeToNibble[s.charCodeAt(4)] << 4 | hexCharCodeToNibble[s.charCodeAt(5)];
54
+ target[3] = hexCharCodeToNibble[s.charCodeAt(6)] << 4 | hexCharCodeToNibble[s.charCodeAt(7)];
55
+ target[4] = hexCharCodeToNibble[s.charCodeAt(9)] << 4 | hexCharCodeToNibble[s.charCodeAt(10)];
56
+ target[5] = hexCharCodeToNibble[s.charCodeAt(11)] << 4 | hexCharCodeToNibble[s.charCodeAt(12)];
57
+ target[6] = hexCharCodeToNibble[s.charCodeAt(24)] << 4 | hexCharCodeToNibble[s.charCodeAt(25)];
58
+ target[7] = hexCharCodeToNibble[s.charCodeAt(26)] << 4 | hexCharCodeToNibble[s.charCodeAt(27)];
59
+ target[8] = hexCharCodeToNibble[s.charCodeAt(28)] << 4 | hexCharCodeToNibble[s.charCodeAt(29)];
60
+ target[9] = hexCharCodeToNibble[s.charCodeAt(30)] << 4 | hexCharCodeToNibble[s.charCodeAt(31)];
61
+ }
62
+ };
63
+ /**
64
+ * Creates a codec for `brand` (three lowercase a–z characters).
65
+ *
66
+ * @param brand - Entity type brand validated once at construction.
67
+ * @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.
68
+ */
69
+ function createId(brand, opts = {}) {
70
+ validateBrand(brand);
71
+ registerBrand(brand, opts.allowDuplicateBrand);
72
+ const options = {
73
+ ...defaultOptions,
74
+ ...opts
75
+ };
76
+ const prefix = `${brand}_`;
77
+ const wire = wireMethods(prefix);
78
+ const layout = createTimestampLayoutOps(prefix, options.rng);
79
+ return {
80
+ generate: () => layout.generateAt(options.now()),
81
+ generateAt: (date) => layout.generateAt(date.getTime()),
82
+ is: wire.is,
83
+ parse: wire.parse,
84
+ safeParse: wire.safeParse,
85
+ extractTimestamp: layout.extractTimestamp,
86
+ minIdForTime: (date) => layout.minIdForTime(date.getTime()),
87
+ maxIdForTime: (date) => layout.maxIdForTime(date.getTime()),
88
+ toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId(options.now())),
89
+ "~standard": wire["~standard"]
90
+ };
91
+ }
92
+ //#endregion
93
+ export { createId as t };
94
+
95
+ //# sourceMappingURL=id-CcoPE2EX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-CcoPE2EX.mjs","names":[],"sources":["../src/layouts/timestamp.ts","../src/id.ts"],"sourcesContent":["import type { Id, Prefix } from \"../types.js\";\nimport { toWireId } from \"../wire/envelope.js\";\nimport { payloadByteLength } from \"../wire/invariants.js\";\nimport {\n readTimestampMsFromBase32Suffix,\n timestampByteLength,\n writeTimestamp,\n} from \"../wire/timestamp-bytes.js\";\n\nconst randomByteLength: number = payloadByteLength - timestampByteLength;\n\n/** Writes a 16-byte timestamp-layout payload into codec-owned scratch. */\nfunction buildPayload(\n ms: number,\n rng: (target: Uint8Array) => void,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n rng(randomView);\n}\n\n/** Writes sentinel min/max random bytes into codec-owned scratch. */\nfunction buildSentinelPayload(\n ms: number,\n fill: number,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n randomView.fill(fill);\n}\n\n/** Decodes the creation timestamp from a trusted wire ID. */\nfunction extractTimestampFromId<Brand extends string>(prefix: Prefix<Brand>, id: Id<Brand>): Date {\n return new Date(readTimestampMsFromBase32Suffix(id.slice(prefix.length)));\n}\n\n/** Layout ops binder for the Timestamp variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */\nexport function createTimestampLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n rng: (target: Uint8Array) => void,\n) {\n // Per-codec scratch buffer. Shared across generateAt(), minIdForTime(),\n // maxIdForTime(), and exampleWireId() — all are synchronous and overwrite both\n // the timestamp and random slices before encoding, so successive callers see\n // their own freshly-written bytes. toWireId reads the buffer and returns an\n // independent string, so the caller never sees the buffer itself.\n const buffer = new Uint8Array(payloadByteLength);\n const randomView = new Uint8Array(buffer.buffer, timestampByteLength, randomByteLength);\n\n return {\n generateAt: (ms: number): Id<Brand> => {\n buildPayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n extractTimestamp: (id: Id<Brand>): Date => extractTimestampFromId(prefix, id),\n minIdForTime: (ms: number): Id<Brand> => {\n buildSentinelPayload(ms, 0x00, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n maxIdForTime: (ms: number): Id<Brand> => {\n buildSentinelPayload(ms, 0xff, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n exampleWireId: (ms: number): Id<Brand> => {\n buildPayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n };\n}\n","import { validateBrand } from \"./brand.js\";\nimport { createTimestampLayoutOps } from \"./layouts/timestamp.js\";\nimport { registerBrand } from \"./registry.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"./types.js\";\nimport { wireMethods } from \"./wire/codec-shell.js\";\n\n/**\n * Configuration options for a codec instance.\n */\nexport type Options = {\n /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */\n now: () => number;\n /** Writes random bytes into `target` for ID generation. Defaults to a `crypto.randomUUID` fast path. */\n rng: (target: Uint8Array) => void;\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\n/**\n * A brand-scoped codec for generating and validating public-facing IDs.\n *\n * Wire format: `{brand}_` plus 26 lowercase Crockford base32 characters encoding a\n * 16-byte payload (6-byte ms timestamp + 10 random bytes). IDs sort by creation\n * time in ascending order.\n *\n * For encrypted IDs, use `createOpaqueId` from `@smonn/ids/opaque`.\n */\nexport type Codec<Brand extends string> = {\n /** Produces a new canonical ID using the codec's `now` and `rng`. */\n generate(): Id<Brand>;\n /** Produces a new canonical ID with timestamp bytes from `date` and a fresh random tail. Throws on invalid dates. */\n generateAt(date: Date): Id<Brand>;\n /**\n * Strict type guard: `true` only for already-canonical strings for this brand.\n * For untrusted input, use `safeParse()` or `parse()` instead. See ADR-0003.\n */\n is(value: unknown): value is Id<Brand>;\n /**\n * Lenient parse: normalises case and Crockford aliases, returns canonical `Id<Brand>`, or throws.\n */\n parse(value: unknown): Id<Brand>;\n /**\n * Lenient parse without throwing: normalises to canonical form, or returns `{ ok: false, error }`.\n */\n safeParse(value: unknown): ParseResult<Brand>;\n /**\n * Decodes the creation `Date` from an `Id<Brand>`. Trusts the type — use `safeParse()` at boundaries first. See ADR-0002.\n */\n extractTimestamp(id: Id<Brand>): Date;\n /** Tight lower bound for any ID generated at `date` (random portion `0x00`). Throws on invalid dates. */\n minIdForTime(date: Date): Id<Brand>;\n /** Tight upper bound for any ID generated at `date` (random portion `0xff`). Throws on invalid dates. */\n maxIdForTime(date: Date): Id<Brand>;\n /** JSON Schema for the canonical wire form (`pattern` is canonical-only). */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n};\n\n// hex charCode → 0–15 nibble, for decoding UUIDv4 strings into bytes.\n// Covers ['0'-'9' = 48–57] and ['a'-'f' = 97–102]; UUIDs are lowercase per spec.\nconst hexCharCodeToNibble = new Uint8Array(128);\nfor (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;\nfor (let i = 0; i < 6; i++) hexCharCodeToNibble[97 + i] = 10 + i;\n\nconst defaultOptions: Options = {\n now: Date.now,\n // crypto.randomUUID is ~7× faster than crypto.getRandomValues in Node 24\n // (~84 ns vs ~610 ns for a 16-byte fill — likely because the UUID path has\n // a tight fixed-format fast path). We use the 122 random bits of a UUIDv4\n // string as our entropy source, harvesting 10 fully-random bytes from\n // positions where no version (hex 12) or variant (hex 16) bits sit.\n // String layout: \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\" — bytes 0–5 are\n // string[0..7]+string[9..12], bytes 6–9 are string[24..31].\n rng: (target) => {\n const s = crypto.randomUUID();\n target[0] =\n (hexCharCodeToNibble[s.charCodeAt(0)]! << 4) | hexCharCodeToNibble[s.charCodeAt(1)]!;\n target[1] =\n (hexCharCodeToNibble[s.charCodeAt(2)]! << 4) | hexCharCodeToNibble[s.charCodeAt(3)]!;\n target[2] =\n (hexCharCodeToNibble[s.charCodeAt(4)]! << 4) | hexCharCodeToNibble[s.charCodeAt(5)]!;\n target[3] =\n (hexCharCodeToNibble[s.charCodeAt(6)]! << 4) | hexCharCodeToNibble[s.charCodeAt(7)]!;\n target[4] =\n (hexCharCodeToNibble[s.charCodeAt(9)]! << 4) | hexCharCodeToNibble[s.charCodeAt(10)]!;\n target[5] =\n (hexCharCodeToNibble[s.charCodeAt(11)]! << 4) | hexCharCodeToNibble[s.charCodeAt(12)]!;\n target[6] =\n (hexCharCodeToNibble[s.charCodeAt(24)]! << 4) | hexCharCodeToNibble[s.charCodeAt(25)]!;\n target[7] =\n (hexCharCodeToNibble[s.charCodeAt(26)]! << 4) | hexCharCodeToNibble[s.charCodeAt(27)]!;\n target[8] =\n (hexCharCodeToNibble[s.charCodeAt(28)]! << 4) | hexCharCodeToNibble[s.charCodeAt(29)]!;\n target[9] =\n (hexCharCodeToNibble[s.charCodeAt(30)]! << 4) | hexCharCodeToNibble[s.charCodeAt(31)]!;\n },\n};\n\n/**\n * Creates a codec for `brand` (three lowercase a–z characters).\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createId<Brand extends string>(\n brand: Brand,\n opts: Partial<Options> = {},\n): Codec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const options = {\n ...defaultOptions,\n ...opts,\n } satisfies Options;\n\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createTimestampLayoutOps(prefix, options.rng);\n\n return {\n generate: () => layout.generateAt(options.now()),\n generateAt: (date: Date) => layout.generateAt(date.getTime()),\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n extractTimestamp: layout.extractTimestamp,\n minIdForTime: (date: Date) => layout.minIdForTime(date.getTime()),\n maxIdForTime: (date: Date) => layout.maxIdForTime(date.getTime()),\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId(options.now())),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,23 +1,54 @@
1
- import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, r as ParseError, t as Id } from "./types-Dg2j_zlO.mjs";
1
+ import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, r as ParseError, t as Id } from "./types-Bv-63YK4.mjs";
2
2
 
3
3
  //#region src/id.d.ts
4
+ /**
5
+ * Configuration options for a codec instance.
6
+ */
4
7
  type Options = {
5
- now: () => number;
6
- rng: (target: Uint8Array) => void;
8
+ /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */now: () => number; /** Writes random bytes into `target` for ID generation. Defaults to a `crypto.randomUUID` fast path. */
9
+ rng: (target: Uint8Array) => void; /** If true, silences the duplicate-brand warning in non-production environments. */
7
10
  allowDuplicateBrand?: boolean;
8
11
  };
12
+ /**
13
+ * A brand-scoped codec for generating and validating public-facing IDs.
14
+ *
15
+ * Wire format: `{brand}_` plus 26 lowercase Crockford base32 characters encoding a
16
+ * 16-byte payload (6-byte ms timestamp + 10 random bytes). IDs sort by creation
17
+ * time in ascending order.
18
+ *
19
+ * For encrypted IDs, use `createOpaqueId` from `@smonn/ids/opaque`.
20
+ */
9
21
  type Codec<Brand extends string> = {
10
- generate(): Id<Brand>;
22
+ /** Produces a new canonical ID using the codec's `now` and `rng`. */generate(): Id<Brand>; /** Produces a new canonical ID with timestamp bytes from `date` and a fresh random tail. Throws on invalid dates. */
11
23
  generateAt(date: Date): Id<Brand>;
24
+ /**
25
+ * Strict type guard: `true` only for already-canonical strings for this brand.
26
+ * For untrusted input, use `safeParse()` or `parse()` instead. See ADR-0003.
27
+ */
12
28
  is(value: unknown): value is Id<Brand>;
29
+ /**
30
+ * Lenient parse: normalises case and Crockford aliases, returns canonical `Id<Brand>`, or throws.
31
+ */
13
32
  parse(value: unknown): Id<Brand>;
33
+ /**
34
+ * Lenient parse without throwing: normalises to canonical form, or returns `{ ok: false, error }`.
35
+ */
14
36
  safeParse(value: unknown): ParseResult<Brand>;
15
- extractTimestamp(id: Id<Brand>): Date;
16
- minIdForTime(date: Date): Id<Brand>;
17
- maxIdForTime(date: Date): Id<Brand>;
18
- toJsonSchema(): JsonSchema;
37
+ /**
38
+ * Decodes the creation `Date` from an `Id<Brand>`. Trusts the type — use `safeParse()` at boundaries first. See ADR-0002.
39
+ */
40
+ extractTimestamp(id: Id<Brand>): Date; /** Tight lower bound for any ID generated at `date` (random portion `0x00`). Throws on invalid dates. */
41
+ minIdForTime(date: Date): Id<Brand>; /** Tight upper bound for any ID generated at `date` (random portion `0xff`). Throws on invalid dates. */
42
+ maxIdForTime(date: Date): Id<Brand>; /** JSON Schema for the canonical wire form (`pattern` is canonical-only). */
43
+ toJsonSchema(): JsonSchema; /** Standard Schema validate entry point. */
19
44
  readonly "~standard": StandardSchemaProps<Brand>;
20
45
  };
46
+ /**
47
+ * Creates a codec for `brand` (three lowercase a–z characters).
48
+ *
49
+ * @param brand - Entity type brand validated once at construction.
50
+ * @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.
51
+ */
21
52
  declare function createId<Brand extends string>(brand: Brand, opts?: Partial<Options>): Codec<Brand>;
22
53
  //#endregion
23
54
  export { type Codec, type Id, type JsonSchema, type Options, type ParseError, type ParseResult, createId };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/id.ts"],"mappings":";;;KAgBY,OAAA;EACV,GAAA;EACA,GAAA,GAAM,MAAA,EAAQ,UAAA;EACd,mBAAA;AAAA;AAAA,KAGU,KAAA;EACV,QAAA,IAAY,EAAA,CAAG,KAAA;EACf,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAC3B,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EAChC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAC1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EACvC,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,IAAA;EACjC,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAC7B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAC7B,YAAA,IAAgB,UAAA;EAAA,SACP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;AAAA,iBA8C5B,QAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,GAAM,OAAA,CAAQ,OAAA,IACb,KAAA,CAAM,KAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/id.ts"],"mappings":";;;;;AASA;KAAY,OAAA;+EAEV,GAAA;EAEA,GAAA,GAAM,MAAA,EAAQ,UAAA;EAEd,mBAAA;AAAA;;AAAA;AAYF;;;;;;;KAAY,KAAA;uEAEV,QAAA,IAAY,EAAA,CAAG,KAAA;EAEf,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;;;;;EAK3B,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;;;;EAIhC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;EAI1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;;EAIvC,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,IAAA;EAEjC,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAE7B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAE7B,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;;iBAiD5B,QAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,GAAM,OAAA,CAAQ,OAAA,IACb,KAAA,CAAM,KAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createId } from "./id-B4GiFKYV.mjs";
1
+ import { t as createId } from "./id-CcoPE2EX.mjs";
2
2
  export { createId };
@@ -0,0 +1,186 @@
1
+ import { a as writeTimestamp, c as toWireId, l as validateBrand, n as registerBrand, o as payloadBase32Length, r as readTimestampMs, s as payloadBytesFromId, t as wireMethods } from "./codec-shell-C0arqqX3.mjs";
2
+ //#region src/layouts/opaque.ts
3
+ const zeroIv = new Uint8Array(16);
4
+ const pkcsPad = 16;
5
+ function buildPlaintext(ms, rng) {
6
+ const plaintext = new Uint8Array(16);
7
+ writeTimestamp(ms, plaintext);
8
+ rng(plaintext.subarray(6, 16));
9
+ return plaintext;
10
+ }
11
+ async function encryptPayload(key, plaintext) {
12
+ return new Uint8Array(await crypto.subtle.encrypt({
13
+ name: "AES-CBC",
14
+ iv: zeroIv
15
+ }, key, plaintext)).subarray(0, 16);
16
+ }
17
+ async function decryptPayload(key, c1) {
18
+ const c2Input = new Uint8Array(16);
19
+ for (let i = 0; i < 16; i++) c2Input[i] = pkcsPad ^ c1[i];
20
+ const c2Encrypted = new Uint8Array(await crypto.subtle.encrypt({
21
+ name: "AES-CBC",
22
+ iv: zeroIv
23
+ }, key, c2Input));
24
+ const ciphertext = new Uint8Array(32);
25
+ ciphertext.set(c1, 0);
26
+ ciphertext.set(c2Encrypted.subarray(0, 16), 16);
27
+ return new Uint8Array(await crypto.subtle.decrypt({
28
+ name: "AES-CBC",
29
+ iv: zeroIv
30
+ }, key, ciphertext));
31
+ }
32
+ async function extractTimestampFromId(prefix, key, id) {
33
+ const plaintext = await decryptPayload(key, payloadBytesFromId(prefix, id));
34
+ return new Date(readTimestampMs(plaintext));
35
+ }
36
+ /** Produces a canonical encrypted wire ID. Per-call plaintext/ciphertext buffers —
37
+ * subtle dominates this path; reuse would be safe but not worth pinning to spec detail. */
38
+ async function generateWireId(prefix, key, rng, ms) {
39
+ return toWireId(prefix, await encryptPayload(key, buildPlaintext(ms, rng)));
40
+ }
41
+ /** Structural placeholder for JSON Schema (encrypt is async). */
42
+ function schemaExample(prefix) {
43
+ return prefix + "0".repeat(payloadBase32Length);
44
+ }
45
+ /** Layout ops binder for the Opaque variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */
46
+ function createOpaqueLayoutOps(prefix, key, rng) {
47
+ return {
48
+ generateAt: (ms) => generateWireId(prefix, key, rng, ms),
49
+ extractTimestamp: (id) => extractTimestampFromId(prefix, key, id),
50
+ exampleWireId: () => schemaExample(prefix)
51
+ };
52
+ }
53
+ //#endregion
54
+ //#region src/bytes.ts
55
+ const hexDigits = "0123456789abcdef";
56
+ const invalidNibble = 255;
57
+ const hexCharCodeToNibble = new Uint8Array(128).fill(invalidNibble);
58
+ for (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;
59
+ for (let i = 0; i < 6; i++) {
60
+ hexCharCodeToNibble[97 + i] = 10 + i;
61
+ hexCharCodeToNibble[65 + i] = 10 + i;
62
+ }
63
+ /** Lowercase hex encoding of raw bytes. */
64
+ function encodeHex(bytes) {
65
+ const codes = new Array(bytes.length * 2);
66
+ for (let i = 0; i < bytes.length; i++) {
67
+ const b = bytes[i];
68
+ codes[i * 2] = hexDigits.charCodeAt(b >>> 4);
69
+ codes[i * 2 + 1] = hexDigits.charCodeAt(b & 15);
70
+ }
71
+ return String.fromCharCode(...codes);
72
+ }
73
+ /** Decodes a hex string to raw bytes. Throws on non-hex input. */
74
+ function decodeHex(encoded) {
75
+ if (encoded.length % 2 !== 0) throw new Error("invalid hex");
76
+ const out = new Uint8Array(encoded.length / 2);
77
+ for (let i = 0; i < out.length; i++) {
78
+ const hiCode = encoded.charCodeAt(i * 2);
79
+ const loCode = encoded.charCodeAt(i * 2 + 1);
80
+ if (hiCode >= hexCharCodeToNibble.length || loCode >= hexCharCodeToNibble.length) throw new Error("invalid hex");
81
+ const hi = hexCharCodeToNibble[hiCode];
82
+ const lo = hexCharCodeToNibble[loCode];
83
+ if (hi === invalidNibble || lo === invalidNibble) throw new Error("invalid hex");
84
+ out[i] = hi << 4 | lo;
85
+ }
86
+ return out;
87
+ }
88
+ /** Base64url encoding without padding. */
89
+ function encodeBase64Url(bytes) {
90
+ let binary = "";
91
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
92
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
93
+ }
94
+ /** Decodes a base64url string to raw bytes. Throws on invalid input. */
95
+ function decodeBase64Url(encoded) {
96
+ const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
97
+ const pad = (4 - base64.length % 4) % 4;
98
+ const binary = atob(base64 + "=".repeat(pad));
99
+ const out = new Uint8Array(binary.length);
100
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
101
+ return out;
102
+ }
103
+ //#endregion
104
+ //#region src/opaque-key.ts
105
+ const validAesKeyByteLengths = new Set([
106
+ 16,
107
+ 24,
108
+ 32
109
+ ]);
110
+ /**
111
+ * Encodes raw AES key bytes for storage in env vars or secret managers.
112
+ *
113
+ * @param bytes - 16, 24, or 32 raw key bytes (AES-128/192/256).
114
+ * @param format - `hex` (lowercase) or `base64url`.
115
+ */
116
+ function encodeOpaqueKey(bytes, format) {
117
+ assertValidAesKeyByteLength(bytes.length);
118
+ if (format === "hex") return encodeHex(bytes);
119
+ return encodeBase64Url(bytes);
120
+ }
121
+ /**
122
+ * Decodes key material emitted by `encodeOpaqueKey` (or `ids keygen`) back to raw bytes.
123
+ *
124
+ * @param encoded - Hex or base64url string.
125
+ * @param format - Must match how the string was encoded.
126
+ */
127
+ function decodeOpaqueKey(encoded, format) {
128
+ let bytes;
129
+ if (format === "hex") {
130
+ if (encoded.length === 0 || encoded.length % 2 !== 0) throw new Error("invalid hex key: length must be a positive even number of characters");
131
+ if (!/^[0-9a-fA-F]+$/.test(encoded)) throw new Error("invalid hex key: expected [0-9a-fA-F] only");
132
+ bytes = decodeHex(encoded);
133
+ } else try {
134
+ bytes = decodeBase64Url(encoded);
135
+ } catch {
136
+ throw new Error("invalid base64url key");
137
+ }
138
+ assertValidAesKeyByteLength(bytes.length);
139
+ return bytes;
140
+ }
141
+ function assertValidAesKeyByteLength(byteLength) {
142
+ if (!validAesKeyByteLengths.has(byteLength)) throw new Error(`invalid AES key length: expected 16, 24, or 32 bytes, got ${byteLength}`);
143
+ }
144
+ //#endregion
145
+ //#region src/opaque.ts
146
+ function defaultRng(target) {
147
+ crypto.getRandomValues(target);
148
+ }
149
+ /**
150
+ * Imports a raw AES key for use with the Opaque codec.
151
+ *
152
+ * @param bytes - Raw key bytes (16, 24, or 32 bytes for AES-128/192/256).
153
+ */
154
+ function importOpaqueKey(bytes) {
155
+ return crypto.subtle.importKey("raw", bytes, "AES-CBC", false, ["encrypt", "decrypt"]);
156
+ }
157
+ /**
158
+ * Creates an Opaque codec for `brand` (three lowercase a–z characters).
159
+ *
160
+ * @param brand - Entity type brand validated once at construction.
161
+ * @param opts - Required `key` plus optional `now`, `rng`, and `allowDuplicateBrand` overrides.
162
+ */
163
+ function createOpaqueId(brand, opts) {
164
+ validateBrand(brand);
165
+ registerBrand(brand, opts.allowDuplicateBrand);
166
+ const key = opts.key;
167
+ const now = opts.now ?? Date.now;
168
+ const rng = opts.rng ?? defaultRng;
169
+ const prefix = `${brand}_`;
170
+ const wire = wireMethods(prefix);
171
+ const layout = createOpaqueLayoutOps(prefix, key, rng);
172
+ return {
173
+ generate: () => layout.generateAt(now()),
174
+ generateAt: (date) => layout.generateAt(date.getTime()),
175
+ is: wire.is,
176
+ parse: wire.parse,
177
+ safeParse: wire.safeParse,
178
+ extractTimestamp: layout.extractTimestamp,
179
+ toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId()),
180
+ "~standard": wire["~standard"]
181
+ };
182
+ }
183
+ //#endregion
184
+ export { encodeOpaqueKey as i, importOpaqueKey as n, decodeOpaqueKey as r, createOpaqueId as t };
185
+
186
+ //# sourceMappingURL=opaque-B-ttBfHO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque-B-ttBfHO.mjs","names":[],"sources":["../src/layouts/opaque.ts","../src/bytes.ts","../src/opaque-key.ts","../src/opaque.ts"],"sourcesContent":["import type { Id, Prefix } from \"../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../wire/invariants.js\";\nimport { readTimestampMs, timestampByteLength, writeTimestamp } from \"../wire/timestamp-bytes.js\";\n\nconst zeroIv = new Uint8Array(payloadByteLength);\nconst pkcsPad = 0x10;\n\nfunction buildPlaintext(ms: number, rng: (target: Uint8Array) => void): Uint8Array {\n const plaintext = new Uint8Array(payloadByteLength);\n writeTimestamp(ms, plaintext);\n rng(plaintext.subarray(timestampByteLength, payloadByteLength));\n return plaintext;\n}\n\nasync function encryptPayload(key: CryptoKey, plaintext: Uint8Array): Promise<Uint8Array> {\n const encrypted = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-CBC\", iv: zeroIv },\n key,\n plaintext as Uint8Array<ArrayBuffer>,\n ),\n );\n return encrypted.subarray(0, payloadByteLength);\n}\n\n// AES-CBC strip-and-reconstruct decrypt (ADR-0004). The wire carries only C1\n// (16 bytes); C2 = AES_K(P2 XOR C1) where P2 is the PKCS#7 pad block (0x10×16).\n// Recompute C2 via CBC encrypt of (P2 XOR C1) with IV=0, then decrypt C1‖C2.\nasync function decryptPayload(key: CryptoKey, c1: Uint8Array): Promise<Uint8Array> {\n const c2Input = new Uint8Array(payloadByteLength);\n for (let i = 0; i < payloadByteLength; i++) c2Input[i] = pkcsPad ^ c1[i]!;\n const c2Encrypted = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-CBC\", iv: zeroIv },\n key,\n c2Input as Uint8Array<ArrayBuffer>,\n ),\n );\n const ciphertext = new Uint8Array(payloadByteLength * 2);\n ciphertext.set(c1, 0);\n ciphertext.set(c2Encrypted.subarray(0, payloadByteLength), payloadByteLength);\n return new Uint8Array(\n await crypto.subtle.decrypt(\n { name: \"AES-CBC\", iv: zeroIv },\n key,\n ciphertext as Uint8Array<ArrayBuffer>,\n ),\n );\n}\n\nasync function extractTimestampFromId<Brand extends string>(\n prefix: Prefix<Brand>,\n key: CryptoKey,\n id: Id<Brand>,\n): Promise<Date> {\n const plaintext = await decryptPayload(key, payloadBytesFromId(prefix, id));\n return new Date(readTimestampMs(plaintext));\n}\n\n/** Produces a canonical encrypted wire ID. Per-call plaintext/ciphertext buffers —\n * subtle dominates this path; reuse would be safe but not worth pinning to spec detail. */\nasync function generateWireId<Brand extends string>(\n prefix: Prefix<Brand>,\n key: CryptoKey,\n rng: (target: Uint8Array) => void,\n ms: number,\n): Promise<Id<Brand>> {\n const plaintext = buildPlaintext(ms, rng);\n const encrypted = await encryptPayload(key, plaintext);\n return toWireId(prefix, encrypted);\n}\n\n/** Structural placeholder for JSON Schema (encrypt is async). */\nfunction schemaExample<Brand extends string>(prefix: Prefix<Brand>): string {\n return prefix + \"0\".repeat(payloadBase32Length);\n}\n\n/** Layout ops binder for the Opaque variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */\nexport function createOpaqueLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n key: CryptoKey,\n rng: (target: Uint8Array) => void,\n) {\n return {\n generateAt: (ms: number): Promise<Id<Brand>> => generateWireId(prefix, key, rng, ms),\n extractTimestamp: (id: Id<Brand>): Promise<Date> => extractTimestampFromId(prefix, key, id),\n exampleWireId: (): Id<Brand> => schemaExample(prefix) as Id<Brand>,\n };\n}\n","const hexDigits = \"0123456789abcdef\";\n\nconst invalidNibble = 0xff;\nconst hexCharCodeToNibble = new Uint8Array(128).fill(invalidNibble);\nfor (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;\nfor (let i = 0; i < 6; i++) {\n hexCharCodeToNibble[97 + i] = 10 + i;\n hexCharCodeToNibble[65 + i] = 10 + i;\n}\n\n/** Lowercase hex encoding of raw bytes. */\nexport function encodeHex(bytes: Uint8Array): string {\n // oxlint-disable-next-line no-new-array\n const codes = new Array<number>(bytes.length * 2);\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n codes[i * 2] = hexDigits.charCodeAt(b >>> 4);\n codes[i * 2 + 1] = hexDigits.charCodeAt(b & 0x0f);\n }\n return String.fromCharCode(...codes);\n}\n\n/** Decodes a hex string to raw bytes. Throws on non-hex input. */\nexport function decodeHex(encoded: string): Uint8Array {\n if (encoded.length % 2 !== 0) throw new Error(\"invalid hex\");\n const out = new Uint8Array(encoded.length / 2);\n for (let i = 0; i < out.length; i++) {\n const hiCode = encoded.charCodeAt(i * 2);\n const loCode = encoded.charCodeAt(i * 2 + 1);\n if (hiCode >= hexCharCodeToNibble.length || loCode >= hexCharCodeToNibble.length) {\n throw new Error(\"invalid hex\");\n }\n const hi = hexCharCodeToNibble[hiCode]!;\n const lo = hexCharCodeToNibble[loCode]!;\n if (hi === invalidNibble || lo === invalidNibble) {\n throw new Error(\"invalid hex\");\n }\n out[i] = (hi << 4) | lo;\n }\n return out;\n}\n\n/** Base64url encoding without padding. */\nexport function encodeBase64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Decodes a base64url string to raw bytes. Throws on invalid input. */\nexport function decodeBase64Url(encoded: string): Uint8Array {\n const base64 = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = (4 - (base64.length % 4)) % 4;\n const binary = atob(base64 + \"=\".repeat(pad));\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n","import { decodeBase64Url, decodeHex, encodeBase64Url, encodeHex } from \"./bytes.js\";\n\n/** Wire encoding for opaque AES key material (not Crockford base32). */\nexport type OpaqueKeyFormat = \"hex\" | \"base64url\";\n\nconst validAesKeyByteLengths = new Set([16, 24, 32]);\n\n/**\n * Encodes raw AES key bytes for storage in env vars or secret managers.\n *\n * @param bytes - 16, 24, or 32 raw key bytes (AES-128/192/256).\n * @param format - `hex` (lowercase) or `base64url`.\n */\nexport function encodeOpaqueKey(bytes: Uint8Array, format: OpaqueKeyFormat): string {\n assertValidAesKeyByteLength(bytes.length);\n if (format === \"hex\") return encodeHex(bytes);\n return encodeBase64Url(bytes);\n}\n\n/**\n * Decodes key material emitted by `encodeOpaqueKey` (or `ids keygen`) back to raw bytes.\n *\n * @param encoded - Hex or base64url string.\n * @param format - Must match how the string was encoded.\n */\nexport function decodeOpaqueKey(encoded: string, format: OpaqueKeyFormat): Uint8Array {\n let bytes: Uint8Array;\n if (format === \"hex\") {\n if (encoded.length === 0 || encoded.length % 2 !== 0) {\n throw new Error(\"invalid hex key: length must be a positive even number of characters\");\n }\n if (!/^[0-9a-fA-F]+$/.test(encoded)) {\n throw new Error(\"invalid hex key: expected [0-9a-fA-F] only\");\n }\n bytes = decodeHex(encoded);\n } else {\n try {\n bytes = decodeBase64Url(encoded);\n } catch {\n throw new Error(\"invalid base64url key\");\n }\n }\n assertValidAesKeyByteLength(bytes.length);\n return bytes;\n}\n\nfunction assertValidAesKeyByteLength(byteLength: number): void {\n if (!validAesKeyByteLengths.has(byteLength)) {\n throw new Error(`invalid AES key length: expected 16, 24, or 32 bytes, got ${byteLength}`);\n }\n}\n","import { validateBrand } from \"./brand.js\";\nimport { createOpaqueLayoutOps } from \"./layouts/opaque.js\";\nimport { registerBrand } from \"./registry.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"./types.js\";\nimport { wireMethods } from \"./wire/codec-shell.js\";\n\nexport { decodeOpaqueKey, encodeOpaqueKey, type OpaqueKeyFormat } from \"./opaque-key.js\";\n\n/**\n * Configuration options for an Opaque codec instance.\n */\nexport type OpaqueOptions = {\n /** AES-CBC key used for encryption and decryption. */\n key: CryptoKey;\n /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */\n now: () => number;\n /** Writes random bytes into `target` for ID generation. Defaults to `crypto.getRandomValues`. */\n rng: (target: Uint8Array) => void;\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\n/**\n * A brand-scoped codec for generating and validating encrypted (opaque) IDs.\n *\n * Same wire shape as the Timestamp codec (`{brand}_` + 26 base32 chars) but the\n * payload is AES-CBC encrypted. `generate`, `generateAt`, and `extractTimestamp`\n * are async; parsing methods are sync. No `minIdForTime` / `maxIdForTime` —\n * encrypted payloads do not sort by creation time.\n */\nexport type OpaqueCodec<Brand extends string> = {\n /** Produces a new canonical encrypted ID using the codec's `now` and `rng`. */\n generate(): Promise<Id<Brand>>;\n /** Produces a new canonical encrypted ID with timestamp bytes from `date`. Throws on invalid dates. */\n generateAt(date: Date): Promise<Id<Brand>>;\n /**\n * Strict type guard: `true` only for already-canonical strings for this brand.\n * For untrusted input, use `safeParse()` or `parse()` instead. See ADR-0003.\n */\n is(value: unknown): value is Id<Brand>;\n /**\n * Lenient parse: normalises case and Crockford aliases, returns canonical `Id<Brand>`, or throws.\n */\n parse(value: unknown): Id<Brand>;\n /**\n * Lenient parse without throwing: normalises to canonical form, or returns `{ ok: false, error }`.\n */\n safeParse(value: unknown): ParseResult<Brand>;\n /**\n * Decrypts and decodes the creation `Date` from an `Id<Brand>`. Trusts the type — use `safeParse()` at boundaries first. See ADR-0002.\n */\n extractTimestamp(id: Id<Brand>): Promise<Date>;\n /** JSON Schema for the canonical wire form (`example` is a structural placeholder). */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n};\n\nfunction defaultRng(target: Uint8Array): void {\n crypto.getRandomValues(target as Uint8Array<ArrayBuffer>);\n}\n\n/**\n * Imports a raw AES key for use with the Opaque codec.\n *\n * @param bytes - Raw key bytes (16, 24, or 32 bytes for AES-128/192/256).\n */\nexport function importOpaqueKey(bytes: Uint8Array): Promise<CryptoKey> {\n return crypto.subtle.importKey(\"raw\", bytes as Uint8Array<ArrayBuffer>, \"AES-CBC\", false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\n/**\n * Creates an Opaque codec for `brand` (three lowercase a–z characters).\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Required `key` plus optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createOpaqueId<Brand extends string>(\n brand: Brand,\n opts: { key: CryptoKey } & Partial<Omit<OpaqueOptions, \"key\">>,\n): OpaqueCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const key = opts.key;\n const now = opts.now ?? Date.now;\n const rng = opts.rng ?? defaultRng;\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createOpaqueLayoutOps(prefix, key, rng);\n\n return {\n generate: () => layout.generateAt(now()),\n generateAt: (date: Date) => layout.generateAt(date.getTime()),\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n extractTimestamp: layout.extractTimestamp,\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId()),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
package/dist/opaque.d.mts CHANGED
@@ -1,26 +1,79 @@
1
- import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, t as Id } from "./types-Dg2j_zlO.mjs";
1
+ import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, t as Id } from "./types-Bv-63YK4.mjs";
2
2
 
3
+ //#region src/opaque-key.d.ts
4
+ /** Wire encoding for opaque AES key material (not Crockford base32). */
5
+ type OpaqueKeyFormat = "hex" | "base64url";
6
+ /**
7
+ * Encodes raw AES key bytes for storage in env vars or secret managers.
8
+ *
9
+ * @param bytes - 16, 24, or 32 raw key bytes (AES-128/192/256).
10
+ * @param format - `hex` (lowercase) or `base64url`.
11
+ */
12
+ declare function encodeOpaqueKey(bytes: Uint8Array, format: OpaqueKeyFormat): string;
13
+ /**
14
+ * Decodes key material emitted by `encodeOpaqueKey` (or `ids keygen`) back to raw bytes.
15
+ *
16
+ * @param encoded - Hex or base64url string.
17
+ * @param format - Must match how the string was encoded.
18
+ */
19
+ declare function decodeOpaqueKey(encoded: string, format: OpaqueKeyFormat): Uint8Array;
20
+ //#endregion
3
21
  //#region src/opaque.d.ts
22
+ /**
23
+ * Configuration options for an Opaque codec instance.
24
+ */
4
25
  type OpaqueOptions = {
5
- key: CryptoKey;
6
- now: () => number;
7
- rng: (target: Uint8Array) => void;
26
+ /** AES-CBC key used for encryption and decryption. */key: CryptoKey; /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */
27
+ now: () => number; /** Writes random bytes into `target` for ID generation. Defaults to `crypto.getRandomValues`. */
28
+ rng: (target: Uint8Array) => void; /** If true, silences the duplicate-brand warning in non-production environments. */
8
29
  allowDuplicateBrand?: boolean;
9
30
  };
31
+ /**
32
+ * A brand-scoped codec for generating and validating encrypted (opaque) IDs.
33
+ *
34
+ * Same wire shape as the Timestamp codec (`{brand}_` + 26 base32 chars) but the
35
+ * payload is AES-CBC encrypted. `generate`, `generateAt`, and `extractTimestamp`
36
+ * are async; parsing methods are sync. No `minIdForTime` / `maxIdForTime` —
37
+ * encrypted payloads do not sort by creation time.
38
+ */
10
39
  type OpaqueCodec<Brand extends string> = {
11
- generate(): Promise<Id<Brand>>;
40
+ /** Produces a new canonical encrypted ID using the codec's `now` and `rng`. */generate(): Promise<Id<Brand>>; /** Produces a new canonical encrypted ID with timestamp bytes from `date`. Throws on invalid dates. */
12
41
  generateAt(date: Date): Promise<Id<Brand>>;
42
+ /**
43
+ * Strict type guard: `true` only for already-canonical strings for this brand.
44
+ * For untrusted input, use `safeParse()` or `parse()` instead. See ADR-0003.
45
+ */
13
46
  is(value: unknown): value is Id<Brand>;
47
+ /**
48
+ * Lenient parse: normalises case and Crockford aliases, returns canonical `Id<Brand>`, or throws.
49
+ */
14
50
  parse(value: unknown): Id<Brand>;
51
+ /**
52
+ * Lenient parse without throwing: normalises to canonical form, or returns `{ ok: false, error }`.
53
+ */
15
54
  safeParse(value: unknown): ParseResult<Brand>;
16
- extractTimestamp(id: Id<Brand>): Promise<Date>;
17
- toJsonSchema(): JsonSchema;
55
+ /**
56
+ * Decrypts and decodes the creation `Date` from an `Id<Brand>`. Trusts the type — use `safeParse()` at boundaries first. See ADR-0002.
57
+ */
58
+ extractTimestamp(id: Id<Brand>): Promise<Date>; /** JSON Schema for the canonical wire form (`example` is a structural placeholder). */
59
+ toJsonSchema(): JsonSchema; /** Standard Schema validate entry point. */
18
60
  readonly "~standard": StandardSchemaProps<Brand>;
19
61
  };
62
+ /**
63
+ * Imports a raw AES key for use with the Opaque codec.
64
+ *
65
+ * @param bytes - Raw key bytes (16, 24, or 32 bytes for AES-128/192/256).
66
+ */
20
67
  declare function importOpaqueKey(bytes: Uint8Array): Promise<CryptoKey>;
68
+ /**
69
+ * Creates an Opaque codec for `brand` (three lowercase a–z characters).
70
+ *
71
+ * @param brand - Entity type brand validated once at construction.
72
+ * @param opts - Required `key` plus optional `now`, `rng`, and `allowDuplicateBrand` overrides.
73
+ */
21
74
  declare function createOpaqueId<Brand extends string>(brand: Brand, opts: {
22
75
  key: CryptoKey;
23
76
  } & Partial<Omit<OpaqueOptions, "key">>): OpaqueCodec<Brand>;
24
77
  //#endregion
25
- export { OpaqueCodec, OpaqueOptions, createOpaqueId, importOpaqueKey };
78
+ export { OpaqueCodec, type OpaqueKeyFormat, OpaqueOptions, createOpaqueId, decodeOpaqueKey, encodeOpaqueKey, importOpaqueKey };
26
79
  //# sourceMappingURL=opaque.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/opaque.ts"],"mappings":";;;KAgBY,aAAA;EACV,GAAA,EAAK,SAAA;EACL,GAAA;EACA,GAAA,GAAM,MAAA,EAAQ,UAAA;EACd,mBAAA;AAAA;AAAA,KAGU,WAAA;EACV,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA;EACvB,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;EACnC,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EAChC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAC1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EACvC,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA;EACzC,YAAA,IAAgB,UAAA;EAAA,SACP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;AAAA,iBAU5B,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;AAAA,iBAO5C,cAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA;EAAQ,GAAA,EAAK,SAAA;AAAA,IAAc,OAAA,CAAQ,IAAA,CAAK,aAAA,YACvC,WAAA,CAAY,KAAA"}
1
+ {"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/opaque-key.ts","../src/opaque.ts"],"mappings":";;;;KAGY,eAAA;;AAAZ;;;;AAAY;iBAUI,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;;;;iBAY3C,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;AAtB3E;;KCQY,aAAA;EDRA,sDCUV,GAAA,EAAK,SAAA,EDAP;ECEE,GAAA;EAEA,GAAA,GAAM,MAAA,EAAQ,UAAA;EAEd,mBAAA;AAAA;;;ADNyD;AAY3D;;;;;KCKY,WAAA;iFAEV,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA,IDPkD;ECSzE,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;AAvBrC;EA4BE,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;;;;EAIhC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;EAI1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;AA5BvC;EAgCA,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA,GArB/B;EAuBV,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;iBAY5B,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;;;;;;;iBAa5C,cAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA;EAAQ,GAAA,EAAK,SAAA;AAAA,IAAc,OAAA,CAAQ,IAAA,CAAK,aAAA,YACvC,WAAA,CAAY,KAAA"}
package/dist/opaque.mjs CHANGED
@@ -1,70 +1,2 @@
1
- import { a as readTimestampMs, c as writeTimestamp, d as decodeBase32, f as encodeBase32, i as payloadBase32Length, l as registerBrand, n as is, o as safeParse, r as parse, s as standardValidate, t as base32CharClass, u as validateBrand } from "./shared-CmbAeUdM.mjs";
2
- //#region src/opaque.ts
3
- const zeroIv = new Uint8Array(16);
4
- const pkcsPad = 16;
5
- function defaultRng(target) {
6
- crypto.getRandomValues(target);
7
- }
8
- function importOpaqueKey(bytes) {
9
- return crypto.subtle.importKey("raw", bytes, "AES-CBC", false, ["encrypt", "decrypt"]);
10
- }
11
- function createOpaqueId(brand, opts) {
12
- validateBrand(brand);
13
- registerBrand(brand, opts.allowDuplicateBrand);
14
- const key = opts.key;
15
- const now = opts.now ?? Date.now;
16
- const rng = opts.rng ?? defaultRng;
17
- const prefix = `${brand}_`;
18
- return {
19
- generate: () => generate(prefix, key, rng, now()),
20
- generateAt: (date) => generate(prefix, key, rng, date.getTime()),
21
- is: (value) => is(prefix, value),
22
- parse: (value) => parse(prefix, value),
23
- safeParse: (value) => safeParse(prefix, value),
24
- extractTimestamp: (id) => extractTimestamp(prefix, key, id),
25
- toJsonSchema: () => toJsonSchema(brand, prefix),
26
- "~standard": {
27
- version: 1,
28
- vendor: "@smonn/ids",
29
- validate: (value) => standardValidate(prefix, value)
30
- }
31
- };
32
- }
33
- async function generate(prefix, key, rng, ms) {
34
- const plaintext = new Uint8Array(16);
35
- writeTimestamp(ms, plaintext);
36
- rng(plaintext.subarray(6, 16));
37
- return prefix + encodeBase32(new Uint8Array(await crypto.subtle.encrypt({
38
- name: "AES-CBC",
39
- iv: zeroIv
40
- }, key, plaintext)).subarray(0, 16));
41
- }
42
- async function extractTimestamp(prefix, key, id) {
43
- const c1 = decodeBase32(id.slice(prefix.length));
44
- const c2Input = new Uint8Array(16);
45
- for (let i = 0; i < 16; i++) c2Input[i] = pkcsPad ^ c1[i];
46
- const c2Encrypted = new Uint8Array(await crypto.subtle.encrypt({
47
- name: "AES-CBC",
48
- iv: zeroIv
49
- }, key, c2Input));
50
- const ciphertext = new Uint8Array(32);
51
- ciphertext.set(c1, 0);
52
- ciphertext.set(c2Encrypted.subarray(0, 16), 16);
53
- const plaintext = new Uint8Array(await crypto.subtle.decrypt({
54
- name: "AES-CBC",
55
- iv: zeroIv
56
- }, key, ciphertext));
57
- return new Date(readTimestampMs(plaintext));
58
- }
59
- function toJsonSchema(brand, prefix) {
60
- return {
61
- type: "string",
62
- pattern: `^${prefix}${base32CharClass}{${payloadBase32Length}}$`,
63
- description: `Branded ID for '${brand}'`,
64
- example: prefix + "0".repeat(payloadBase32Length)
65
- };
66
- }
67
- //#endregion
68
- export { createOpaqueId, importOpaqueKey };
69
-
70
- //# sourceMappingURL=opaque.mjs.map
1
+ import { i as encodeOpaqueKey, n as importOpaqueKey, r as decodeOpaqueKey, t as createOpaqueId } from "./opaque-B-ttBfHO.mjs";
2
+ export { createOpaqueId, decodeOpaqueKey, encodeOpaqueKey, importOpaqueKey };
@@ -1,9 +1,13 @@
1
1
  //#region src/types.d.ts
2
+ /** The brand plus trailing separator — e.g. `usr_` for brand `usr`. */
2
3
  type Prefix<Brand extends string> = `${Brand}_`;
4
+ /** A canonical branded ID string for `Brand`. Produced by `generate()` and `safeParse()`. */
3
5
  type Id<Brand extends string> = `${Prefix<Brand>}${string}` & {
4
6
  readonly __brand: Brand;
5
7
  };
8
+ /** Parse failure reason returned by `safeParse()`. */
6
9
  type ParseError = "not_string" | "invalid_prefix" | "invalid_base32";
10
+ /** Result of `safeParse()`: canonical `Id<Brand>` or a `ParseError`. */
7
11
  type ParseResult<Brand extends string> = {
8
12
  ok: true;
9
13
  id: Id<Brand>;
@@ -11,12 +15,14 @@ type ParseResult<Brand extends string> = {
11
15
  ok: false;
12
16
  error: ParseError;
13
17
  };
18
+ /** JSON Schema for the canonical wire form returned by `toJsonSchema()`. */
14
19
  type JsonSchema = {
15
20
  readonly type: "string";
16
21
  readonly pattern: string;
17
22
  readonly description: string;
18
23
  readonly example: string;
19
24
  };
25
+ /** Standard Schema validate entry point exposed on `Codec["~standard"]`. */
20
26
  type StandardSchemaProps<Brand extends string> = {
21
27
  readonly version: 1;
22
28
  readonly vendor: "@smonn/ids";
@@ -37,4 +43,4 @@ type StandardSchemaProps<Brand extends string> = {
37
43
  };
38
44
  //#endregion
39
45
  export { StandardSchemaProps as a, ParseResult as i, JsonSchema as n, ParseError as r, Id as t };
40
- //# sourceMappingURL=types-Dg2j_zlO.d.mts.map
46
+ //# sourceMappingURL=types-Bv-63YK4.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-Bv-63YK4.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KACY,MAAA,4BAAkC,KAAA;;KAGlC,EAAA,4BAA8B,MAAA,CAAO,KAAA;EAAA,SACtC,OAAA,EAAS,KAAA;AAAA;AADpB;AAAA,KAKY,UAAA;;KAGA,WAAA;EACN,EAAA;EAAU,EAAA,EAAI,EAAA,CAAG,KAAA;AAAA;EACjB,EAAA;EAAW,KAAA,EAAO,UAAA;AAAA;;KAGZ,UAAA;EAAA,SACD,IAAA;EAAA,SACA,OAAA;EAAA,SACA,WAAA;EAAA,SACA,OAAA;AAAA;;KAIC,mBAAA;EAAA,SACD,OAAA;EAAA,SACA,MAAA;EAAA,SACA,QAAA,GACP,KAAA,WACA,OAAA;IAAA,SAAqB,cAAA,GAAiB,MAAA;EAAA;IAAA,SAEzB,KAAA,EAAO,EAAA,CAAG,KAAA;IAAA,SAAiB,MAAA;EAAA;IAAA,SAC3B,MAAA,EAAQ,aAAA;MAAA,SAAyB,OAAA;IAAA;EAAA;EAAA,SACvC,KAAA;IAAA,SAAmB,KAAA;IAAA,SAAyB,MAAA,EAAQ,EAAA,CAAG,KAAA;EAAA;AAAA"}