@smonn/ids 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +7 -6
  2. package/dist/{adapter-types-BY-wrYYB.mjs → adapter-types-7wWdELSh.mjs} +2 -2
  3. package/dist/adapter-types-7wWdELSh.mjs.map +1 -0
  4. package/dist/{adapter-types-unUcmMXC.d.mts → adapter-types-CdYJM6Sf.d.mts} +2 -2
  5. package/dist/adapter-types-CdYJM6Sf.d.mts.map +1 -0
  6. package/dist/cli.mjs +8 -7
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/{codec-shell-CW2sD6BU.mjs → codec-shell-DvrTDa65.mjs} +4 -4
  9. package/dist/codec-shell-DvrTDa65.mjs.map +1 -0
  10. package/dist/{digest-D-H1Tswt.mjs → digest-CknNw2wa.mjs} +9 -9
  11. package/dist/digest-CknNw2wa.mjs.map +1 -0
  12. package/dist/digest.d.mts +4 -4
  13. package/dist/digest.d.mts.map +1 -1
  14. package/dist/digest.mjs +1 -1
  15. package/dist/drizzle.d.mts +2 -2
  16. package/dist/drizzle.d.mts.map +1 -1
  17. package/dist/drizzle.mjs +2 -2
  18. package/dist/drizzle.mjs.map +1 -1
  19. package/dist/express.d.mts +2 -2
  20. package/dist/express.d.mts.map +1 -1
  21. package/dist/express.mjs +2 -2
  22. package/dist/express.mjs.map +1 -1
  23. package/dist/fastify.d.mts +3 -3
  24. package/dist/fastify.d.mts.map +1 -1
  25. package/dist/fastify.mjs +3 -3
  26. package/dist/fastify.mjs.map +1 -1
  27. package/dist/graphql.d.mts +31 -0
  28. package/dist/graphql.d.mts.map +1 -0
  29. package/dist/graphql.mjs +42 -0
  30. package/dist/graphql.mjs.map +1 -0
  31. package/dist/hono.d.mts +5 -4
  32. package/dist/hono.d.mts.map +1 -1
  33. package/dist/hono.mjs +4 -3
  34. package/dist/hono.mjs.map +1 -1
  35. package/dist/index.d.mts +1 -1
  36. package/dist/index.d.mts.map +1 -1
  37. package/dist/index.mjs +1 -1
  38. package/dist/{key-material-gOnqTNoV.mjs → key-material-f29JIyrz.mjs} +3 -3
  39. package/dist/key-material-f29JIyrz.mjs.map +1 -0
  40. package/dist/kysely.d.mts +2 -2
  41. package/dist/kysely.d.mts.map +1 -1
  42. package/dist/kysely.mjs +2 -2
  43. package/dist/kysely.mjs.map +1 -1
  44. package/dist/mikro-orm.d.mts +37 -0
  45. package/dist/mikro-orm.d.mts.map +1 -0
  46. package/dist/mikro-orm.mjs +48 -0
  47. package/dist/mikro-orm.mjs.map +1 -0
  48. package/dist/nestjs.d.mts +70 -0
  49. package/dist/nestjs.d.mts.map +1 -0
  50. package/dist/nestjs.mjs +61 -0
  51. package/dist/nestjs.mjs.map +1 -0
  52. package/dist/{opaque-BpqxV8oB.mjs → opaque-ayT0KdCt.mjs} +8 -8
  53. package/dist/opaque-ayT0KdCt.mjs.map +1 -0
  54. package/dist/opaque.d.mts +3 -3
  55. package/dist/opaque.d.mts.map +1 -1
  56. package/dist/opaque.mjs +1 -1
  57. package/dist/prisma.d.mts +2 -2
  58. package/dist/prisma.d.mts.map +1 -1
  59. package/dist/prisma.mjs +2 -2
  60. package/dist/prisma.mjs.map +1 -1
  61. package/dist/{reverse-d5uEoIET.mjs → reverse-BRZRc1_U.mjs} +6 -6
  62. package/dist/reverse-BRZRc1_U.mjs.map +1 -0
  63. package/dist/reverse.d.mts +1 -1
  64. package/dist/reverse.d.mts.map +1 -1
  65. package/dist/reverse.mjs +1 -1
  66. package/dist/{rng-CPJOx_nE.mjs → rng-DHxioKyI.mjs} +2 -2
  67. package/dist/rng-DHxioKyI.mjs.map +1 -0
  68. package/dist/{signed-BnRSC03a.mjs → signed-C8OMt3TJ.mjs} +10 -10
  69. package/dist/signed-C8OMt3TJ.mjs.map +1 -0
  70. package/dist/signed.d.mts +4 -4
  71. package/dist/signed.d.mts.map +1 -1
  72. package/dist/signed.mjs +1 -1
  73. package/dist/{timestamp-BbZL8hwg.mjs → timestamp-DBwVjDkg.mjs} +5 -5
  74. package/dist/timestamp-DBwVjDkg.mjs.map +1 -0
  75. package/dist/{timestamp-bytes-DoFjLjDp.mjs → timestamp-bytes-DvhWHDa-.mjs} +2 -2
  76. package/dist/timestamp-bytes-DvhWHDa-.mjs.map +1 -0
  77. package/dist/typeorm.d.mts +41 -0
  78. package/dist/typeorm.d.mts.map +1 -0
  79. package/dist/typeorm.mjs +49 -0
  80. package/dist/typeorm.mjs.map +1 -0
  81. package/dist/{wrapped-BI9UXnAm.mjs → wrapped-CDTiPwNM.mjs} +29 -12
  82. package/dist/wrapped-CDTiPwNM.mjs.map +1 -0
  83. package/dist/wrapped.d.mts +3 -3
  84. package/dist/wrapped.d.mts.map +1 -1
  85. package/dist/wrapped.mjs +1 -1
  86. package/package.json +30 -3
  87. package/dist/adapter-types-BY-wrYYB.mjs.map +0 -1
  88. package/dist/adapter-types-unUcmMXC.d.mts.map +0 -1
  89. package/dist/codec-shell-CW2sD6BU.mjs.map +0 -1
  90. package/dist/digest-D-H1Tswt.mjs.map +0 -1
  91. package/dist/key-material-gOnqTNoV.mjs.map +0 -1
  92. package/dist/opaque-BpqxV8oB.mjs.map +0 -1
  93. package/dist/reverse-d5uEoIET.mjs.map +0 -1
  94. package/dist/rng-CPJOx_nE.mjs.map +0 -1
  95. package/dist/signed-BnRSC03a.mjs.map +0 -1
  96. package/dist/timestamp-BbZL8hwg.mjs.map +0 -1
  97. package/dist/timestamp-bytes-DoFjLjDp.mjs.map +0 -1
  98. package/dist/wrapped-BI9UXnAm.mjs.map +0 -1
@@ -0,0 +1,61 @@
1
+ import { n as resolveIdParamFailure } from "./adapter-types-7wWdELSh.mjs";
2
+ import { BadRequestException, HttpException, Injectable, NotFoundException } from "@nestjs/common";
3
+ //#region src/adapters/nestjs.ts
4
+ /**
5
+ * NestJS pipe that validates an untrusted route param against a codec via `safeParse`.
6
+ *
7
+ * Marked `@Injectable()` via `Injectable()(ParseIdPipe)` at module load time, making it
8
+ * available for NestJS DI.
9
+ *
10
+ * **Default (no options):** throws `NotFoundException` (404) for brand mismatches and
11
+ * `BadRequestException` (400) for malformed IDs.
12
+ *
13
+ * **`options.status`:** remaps the default HTTP status for a reason; when the resolved status
14
+ * differs from the default, the pipe throws `HttpException(reason, status)`.
15
+ *
16
+ * **`options.onError`:** escape hatch for custom error handling. The hook must throw — it
17
+ * cannot return a response because `PipeTransform.transform` has no HTTP context.
18
+ *
19
+ * - **Brand mismatch (`invalid_prefix`) → `reason: "brand_mismatch"`, default 404**
20
+ * - **Malformed or missing ID → `reason: "malformed"`, default 400**
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { ParseIdPipe } from "@smonn/ids/nestjs";
25
+ * import { createTimestampId } from "@smonn/ids";
26
+ *
27
+ * const usr = createTimestampId("usr");
28
+ *
29
+ * @Controller("users")
30
+ * class UsersController {
31
+ * @Get(":id")
32
+ * findOne(@Param("id", new ParseIdPipe(usr)) id: Id<"usr">) {
33
+ * return { id }; // Id<"usr">, canonical
34
+ * }
35
+ * }
36
+ * ```
37
+ */
38
+ var ParseIdPipe = class {
39
+ codec;
40
+ options;
41
+ constructor(codec, options) {
42
+ this.codec = codec;
43
+ this.options = options;
44
+ }
45
+ transform(value, _metadata) {
46
+ const result = this.codec.safeParse(value);
47
+ if (!result.ok) {
48
+ const failure = resolveIdParamFailure(result.error, this.options);
49
+ if (this.options?.onError) this.options.onError(failure);
50
+ if (failure.reason === "brand_mismatch" && failure.status === 404) throw new NotFoundException();
51
+ if (failure.reason === "malformed" && failure.status === 400) throw new BadRequestException();
52
+ throw new HttpException(failure.reason, failure.status);
53
+ }
54
+ return result.id;
55
+ }
56
+ };
57
+ Injectable()(ParseIdPipe);
58
+ //#endregion
59
+ export { ParseIdPipe };
60
+
61
+ //# sourceMappingURL=nestjs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nestjs.mjs","names":[],"sources":["../src/adapters/nestjs.ts"],"sourcesContent":["import { BadRequestException, HttpException, Injectable, NotFoundException } from \"@nestjs/common\";\nimport type { ArgumentMetadata, PipeTransform } from \"@nestjs/common\";\nimport { type IdCodec, type IdParamFailure, resolveIdParamFailure } from \"./adapter-types.js\";\nimport type { Id } from \"../types.js\";\n\nexport type { IdParamFailure };\n\n/**\n * Options for `ParseIdPipe`. All fields are optional.\n *\n * **`onError` constraint:** NestJS `transform()` receives only `value` and `ArgumentMetadata`\n * — there is no HTTP context object. The `onError` hook must throw (or re-throw); it cannot\n * write a response inline the way Hono/Express hooks can.\n */\nexport type IdParamOptions = {\n /**\n * Called instead of throwing when provided. The hook **must** throw or re-throw — it cannot\n * return a response because `PipeTransform.transform` has no HTTP context.\n */\n onError?: (failure: IdParamFailure) => never;\n /**\n * Remap the default HTTP status for a failure reason without a full handler.\n * e.g. `{ brand_mismatch: 400 }` treats both failure kinds as 400.\n */\n status?: { brand_mismatch?: number; malformed?: number };\n};\n\n/**\n * NestJS pipe that validates an untrusted route param against a codec via `safeParse`.\n *\n * Marked `@Injectable()` via `Injectable()(ParseIdPipe)` at module load time, making it\n * available for NestJS DI.\n *\n * **Default (no options):** throws `NotFoundException` (404) for brand mismatches and\n * `BadRequestException` (400) for malformed IDs.\n *\n * **`options.status`:** remaps the default HTTP status for a reason; when the resolved status\n * differs from the default, the pipe throws `HttpException(reason, status)`.\n *\n * **`options.onError`:** escape hatch for custom error handling. The hook must throw — it\n * cannot return a response because `PipeTransform.transform` has no HTTP context.\n *\n * - **Brand mismatch (`invalid_prefix`) → `reason: \"brand_mismatch\"`, default 404**\n * - **Malformed or missing ID → `reason: \"malformed\"`, default 400**\n *\n * @example\n * ```ts\n * import { ParseIdPipe } from \"@smonn/ids/nestjs\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n *\n * @Controller(\"users\")\n * class UsersController {\n * @Get(\":id\")\n * findOne(@Param(\"id\", new ParseIdPipe(usr)) id: Id<\"usr\">) {\n * return { id }; // Id<\"usr\">, canonical\n * }\n * }\n * ```\n */\nexport class ParseIdPipe<Brand extends string> implements PipeTransform<unknown, Id<Brand>> {\n private readonly codec: IdCodec<Brand>;\n private readonly options: IdParamOptions | undefined;\n\n constructor(codec: IdCodec<Brand>, options?: IdParamOptions) {\n this.codec = codec;\n this.options = options;\n }\n\n transform(value: unknown, _metadata: ArgumentMetadata): Id<Brand> {\n const result = this.codec.safeParse(value);\n if (!result.ok) {\n const failure = resolveIdParamFailure(result.error, this.options);\n if (this.options?.onError) {\n this.options.onError(failure);\n }\n if (failure.reason === \"brand_mismatch\" && failure.status === 404) {\n throw new NotFoundException();\n }\n if (failure.reason === \"malformed\" && failure.status === 400) {\n throw new BadRequestException();\n }\n throw new HttpException(failure.reason, failure.status);\n }\n return result.id;\n }\n}\n\n// Apply @Injectable() metadata so ParseIdPipe participates in NestJS DI when provided as a class.\n// Using a call instead of the @Injectable() decorator syntax to remain compatible with\n// TypeScript projects that do not enable experimentalDecorators.\nInjectable()(ParseIdPipe as unknown as new (...args: unknown[]) => unknown);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,IAAa,cAAb,MAA4F;CAC1F;CACA;CAEA,YAAY,OAAuB,SAA0B;EAC3D,KAAK,QAAQ;EACb,KAAK,UAAU;CACjB;CAEA,UAAU,OAAgB,WAAwC;EAChE,MAAM,SAAS,KAAK,MAAM,UAAU,KAAK;EACzC,IAAI,CAAC,OAAO,IAAI;GACd,MAAM,UAAU,sBAAsB,OAAO,OAAO,KAAK,OAAO;GAChE,IAAI,KAAK,SAAS,SAChB,KAAK,QAAQ,QAAQ,OAAO;GAE9B,IAAI,QAAQ,WAAW,oBAAoB,QAAQ,WAAW,KAC5D,MAAM,IAAI,kBAAkB;GAE9B,IAAI,QAAQ,WAAW,eAAe,QAAQ,WAAW,KACvD,MAAM,IAAI,oBAAoB;GAEhC,MAAM,IAAI,cAAc,QAAQ,QAAQ,QAAQ,MAAM;EACxD;EACA,OAAO,OAAO;CAChB;AACF;AAKA,WAAW,CAAC,CAAC,WAA6D"}
@@ -1,8 +1,8 @@
1
- import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-CW2sD6BU.mjs";
2
- import { r as writeTimestamp, t as readTimestampMs } from "./timestamp-bytes-DoFjLjDp.mjs";
3
- import { i as encodeKeyMaterial, r as decodeKeyMaterial, t as assertValidKeyMaterialByteLength } from "./key-material-gOnqTNoV.mjs";
4
- import { t as defaultRng } from "./rng-CPJOx_nE.mjs";
5
- //#region src/layouts/opaque.ts
1
+ import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-DvrTDa65.mjs";
2
+ import { r as writeTimestamp, t as readTimestampMs } from "./timestamp-bytes-DvhWHDa-.mjs";
3
+ import { i as encodeKeyMaterial, r as decodeKeyMaterial, t as assertValidKeyMaterialByteLength } from "./key-material-f29JIyrz.mjs";
4
+ import { t as defaultRng } from "./rng-DHxioKyI.mjs";
5
+ //#region src/codecs/opaque/layout.ts
6
6
  const zeroIv = /* @__PURE__ */ new Uint8Array(16);
7
7
  const pkcsPad = 16;
8
8
  function buildPlaintext(ms, rng) {
@@ -54,7 +54,7 @@ function createOpaqueLayoutOps(prefix, key, rng) {
54
54
  };
55
55
  }
56
56
  //#endregion
57
- //#region src/opaque-key.ts
57
+ //#region src/codecs/opaque/key.ts
58
58
  const opaqueKeyInternals = /* @__PURE__ */ new WeakMap();
59
59
  /**
60
60
  * Imports raw AES key bytes into an {@link OpaqueKey} handle for the Opaque
@@ -97,7 +97,7 @@ function decodeOpaqueKey(encoded, format) {
97
97
  return decodeKeyMaterial(encoded, format, "opaque", "AES");
98
98
  }
99
99
  //#endregion
100
- //#region src/opaque.ts
100
+ //#region src/codecs/opaque/index.ts
101
101
  /**
102
102
  * Creates an Opaque Timestamp codec for `brand` (three lowercase a–z characters).
103
103
  *
@@ -128,4 +128,4 @@ function createOpaqueTimestampId(brand, opts) {
128
128
  //#endregion
129
129
  export { importOpaqueKey as i, decodeOpaqueKey as n, encodeOpaqueKey as r, createOpaqueTimestampId as t };
130
130
 
131
- //# sourceMappingURL=opaque-BpqxV8oB.mjs.map
131
+ //# sourceMappingURL=opaque-ayT0KdCt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque-ayT0KdCt.mjs","names":[],"sources":["../src/codecs/opaque/layout.ts","../src/codecs/opaque/key.ts","../src/codecs/opaque/index.ts"],"sourcesContent":["import type { webcrypto } from \"node:crypto\";\nimport type { Id, Prefix } from \"../../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../../wire/invariants.js\";\nimport {\n readTimestampMs,\n timestampByteLength,\n writeTimestamp,\n} 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(\n key: webcrypto.CryptoKey,\n plaintext: Uint8Array,\n): 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: webcrypto.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: webcrypto.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: webcrypto.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 Timestamp 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: webcrypto.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","import type { webcrypto } from \"node:crypto\";\nimport {\n assertValidKeyMaterialByteLength,\n decodeKeyMaterial,\n encodeKeyMaterial,\n} from \"../_kernel/key-material.js\";\n\n/** Wire encoding for opaque AES key material (not Crockford base32). */\nexport type OpaqueKeyFormat = \"hex\" | \"base64url\";\n\ndeclare const opaqueKeyBrand: unique symbol;\n\n/**\n * Opaque imported handle for one AES key used by the Opaque Timestamp codec.\n *\n * Holds the underlying `webcrypto.CryptoKey` internally; callers never access it directly.\n * Obtain handles via {@link importOpaqueKey} and pass them to\n * `createOpaqueTimestampId` as the `key` option.\n *\n * Distinct from the `WrappingKey` used by `@smonn/ids/wrapped` — one raw\n * secret must not silently serve both codecs without an explicit import.\n */\nexport type OpaqueKey = {\n readonly [opaqueKeyBrand]: \"OpaqueKey\";\n};\n\nconst opaqueKeyInternals = new WeakMap<OpaqueKey, webcrypto.CryptoKey>();\n\n/**\n * Imports raw AES key bytes into an {@link OpaqueKey} handle for the Opaque\n * Timestamp codec.\n *\n * Accepts 16, 24, or 32 bytes (AES-128 / AES-192 / AES-256 strength).\n * To store or transport key material, use {@link encodeOpaqueKey} /\n * {@link decodeOpaqueKey} (`\"hex\"` or `\"base64url\"` — not Crockford base32).\n *\n * @param bytes - 16, 24, or 32 raw key bytes.\n */\nexport async function importOpaqueKey(bytes: Uint8Array): Promise<OpaqueKey> {\n assertValidKeyMaterialByteLength(bytes.length, \"AES\");\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n bytes as Uint8Array<ArrayBuffer>,\n \"AES-CBC\",\n false,\n [\"encrypt\", \"decrypt\"],\n );\n const key = Object.freeze({}) as OpaqueKey;\n opaqueKeyInternals.set(key, cryptoKey);\n return key;\n}\n\nexport function getOpaqueKeyCryptoKey(key: OpaqueKey): webcrypto.CryptoKey {\n const cryptoKey = opaqueKeyInternals.get(key);\n if (cryptoKey === undefined) {\n throw new Error(\"invalid opaque key\");\n }\n return cryptoKey;\n}\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 return encodeKeyMaterial(bytes, format, \"opaque\", \"AES\");\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 return decodeKeyMaterial(encoded, format, \"opaque\", \"AES\");\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { createOpaqueLayoutOps } from \"./layout.js\";\nimport { getOpaqueKeyCryptoKey, type OpaqueKey } from \"./key.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport { defaultRng } from \"../_kernel/rng.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nexport {\n decodeOpaqueKey,\n encodeOpaqueKey,\n importOpaqueKey,\n type OpaqueKey,\n type OpaqueKeyFormat,\n} from \"./key.js\";\n\n/**\n * Configuration options for an Opaque Timestamp codec instance.\n */\nexport type OpaqueTimestampOptions = {\n /**\n * {@link OpaqueKey} handle for AES-CBC encryption and decryption.\n * Obtain via {@link importOpaqueKey}.\n *\n * A single key, not a ring: rotation is forward-only and caller-tracked —\n * hold one codec per key epoch and select it from your own records. The\n * library cannot trial keys (the payload is unauthenticated). See ADR-0013.\n */\n key: OpaqueKey;\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 Opaque Timestamp 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 OpaqueTimestampCodec<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 * Requires the same key used at generation; a wrong key returns a plausible\n * but wrong `Date`, never an error. With rotation, select the codec for the\n * ID's key epoch from your own records — the library cannot. See ADR-0013.\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\n/**\n * Creates an Opaque Timestamp codec for `brand` (three lowercase a–z characters).\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Required `key` (an {@link OpaqueKey} from {@link importOpaqueKey}) plus\n * optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createOpaqueTimestampId<Brand extends string>(\n brand: Brand,\n opts: OpaqueTimestampOptions,\n): OpaqueTimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const cryptoKey = getOpaqueKeyCryptoKey(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, cryptoKey, 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,14 +1,14 @@
1
1
  import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-JIPylU_E.mjs";
2
2
  import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, t as Id } from "./types-g7CiQDyE.mjs";
3
3
 
4
- //#region src/opaque-key.d.ts
4
+ //#region src/codecs/opaque/key.d.ts
5
5
  /** Wire encoding for opaque AES key material (not Crockford base32). */
6
6
  type OpaqueKeyFormat = "hex" | "base64url";
7
7
  declare const opaqueKeyBrand: unique symbol;
8
8
  /**
9
9
  * Opaque imported handle for one AES key used by the Opaque Timestamp codec.
10
10
  *
11
- * Holds the underlying `CryptoKey` internally; callers never access it directly.
11
+ * Holds the underlying `webcrypto.CryptoKey` internally; callers never access it directly.
12
12
  * Obtain handles via {@link importOpaqueKey} and pass them to
13
13
  * `createOpaqueTimestampId` as the `key` option.
14
14
  *
@@ -44,7 +44,7 @@ declare function encodeOpaqueKey(bytes: Uint8Array, format: OpaqueKeyFormat): st
44
44
  */
45
45
  declare function decodeOpaqueKey(encoded: string, format: OpaqueKeyFormat): Uint8Array;
46
46
  //#endregion
47
- //#region src/opaque.d.ts
47
+ //#region src/codecs/opaque/index.d.ts
48
48
  /**
49
49
  * Configuration options for an Opaque Timestamp codec instance.
50
50
  */
@@ -1 +1 @@
1
- {"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/opaque-key.ts","../src/opaque.ts"],"mappings":";;;;;KAOY,eAAA;AAAA,cAEE,cAAA;;AAFd;;;;AAAY;AAA0B;;;;KAc1B,SAAA;EAAA,UACA,cAAA;AAAA;;;AAAA;AAeZ;;;;;;;iBAAsB,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;;;;;AAAA;AA4BlE;iBAAgB,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;;;;iBAU3C,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;;;KCtD/D,sBAAA;EDd0B;;;;AAExB;AAYd;;;ECSE,GAAA,EAAK,SAAA,EDRK;ECUV,GAAA,iBDKoB;ECHpB,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;;;;;;;;ADCgE;KCUtD,oBAAA;EDkBI,+EChBd,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA,IDgBkC;ECdzD,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;;EAKnC,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EDmBlC;;;ECfE,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;EAI1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EDWkC;AAAA;;;;ACtD3E;;EAmDE,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA,GAtC1B;EAwCf,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;AAxC1C;AAWF;;iBAuCgB,uBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,EAAM,sBAAA,GACL,oBAAA,CAAqB,KAAA"}
1
+ {"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/codecs/opaque/key.ts","../src/codecs/opaque/index.ts"],"mappings":";;;;;KAQY,eAAA;AAAA,cAEE,cAAA;AAFd;;;;AAAY;AAA0B;;;;AAExB;AAFd,KAcY,SAAA;EAAA,UACA,cAAA;AAAA;;AAAA;AAeZ;;;;;;;;iBAAsB,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;;;;AAAA;AA4BlE;;iBAAgB,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;;;;iBAU3C,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;;;KCvD/D,sBAAA;EDb0B;;;;AAExB;AAYd;;;ECQE,GAAA,EAAK,SAAA,EDPK;ECSV,GAAA,iBDMoB;ECJpB,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;;;;;;;;ADEgE;KCStD,oBAAA;EDmBI,+ECjBd,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA,IDiBkC;ECfzD,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;;EAKnC,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EDoBlC;;;EChBE,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;EAI1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EDYkC;AAAA;;;;ACvD3E;;EAmDE,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA,GAtC1B;EAwCf,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;AAxC1C;AAWF;;iBAuCgB,uBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,EAAM,sBAAA,GACL,oBAAA,CAAqB,KAAA"}
package/dist/opaque.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BpqxV8oB.mjs";
2
+ import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-ayT0KdCt.mjs";
3
3
  export { IdsError, createOpaqueTimestampId, decodeOpaqueKey, encodeOpaqueKey, importOpaqueKey, isIdsError };
package/dist/prisma.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-JIPylU_E.mjs";
2
2
  import { t as Id } from "./types-g7CiQDyE.mjs";
3
- import { n as IdColumnCodec } from "./adapter-types-unUcmMXC.mjs";
3
+ import { n as IdColumnCodec } from "./adapter-types-CdYJM6Sf.mjs";
4
4
 
5
- //#region src/prisma.d.ts
5
+ //#region src/adapters/prisma.d.ts
6
6
  /**
7
7
  * Read/write transform pair for integrating `Id<Brand>` with Prisma extensions.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.mts","names":[],"sources":["../src/prisma.ts"],"mappings":";;;;;;;;;;;;;;KAkBY,WAAA;;;;;;;;EAQV,IAAA,CAAK,KAAA,YAAiB,EAAA,CAAG,KAAA;EAQT;AAsClB;;;;;;EAtCE,KAAA,CAAM,KAAA,EAAO,EAAA,CAAG,KAAA;AAAA;;;;;;;;;AAsCsE;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAxE,OAAA,uBAA8B,KAAA,EAAO,aAAA,CAAc,KAAA,IAAS,WAAA,CAAY,KAAA"}
1
+ {"version":3,"file":"prisma.d.mts","names":[],"sources":["../src/adapters/prisma.ts"],"mappings":";;;;;;;;;;;;;;KAkBY,WAAA;;;;;;;;EAQV,IAAA,CAAK,KAAA,YAAiB,EAAA,CAAG,KAAA;EAQT;AAsClB;;;;;;EAtCE,KAAA,CAAM,KAAA,EAAO,EAAA,CAAG,KAAA;AAAA;;;;;;;;;AAsCsE;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAxE,OAAA,uBAA8B,KAAA,EAAO,aAAA,CAAc,KAAA,IAAS,WAAA,CAAY,KAAA"}
package/dist/prisma.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { t as readIdColumn } from "./adapter-types-BY-wrYYB.mjs";
3
- //#region src/prisma.ts
2
+ import { t as readIdColumn } from "./adapter-types-7wWdELSh.mjs";
3
+ //#region src/adapters/prisma.ts
4
4
  /**
5
5
  * Creates a read/write transform pair for use with Prisma's `$extends` extension model.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.mjs","names":[],"sources":["../src/prisma.ts"],"sourcesContent":["import { IdsError, isIdsError, type IdsErrorCode } from \"./error.js\";\nimport { readIdColumn, type IdColumnCodec } from \"./adapter-types.js\";\nimport type { Id } from \"./types.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode };\n\nexport type { IdColumnCodec };\n\n/**\n * Read/write transform pair for integrating `Id<Brand>` with Prisma extensions.\n *\n * **Prisma casting caveat:** Prisma cannot fully brand a generated model field\n * type at the schema level. The `read` function asserts `Id<Brand>` at the\n * TypeScript level, but Prisma's generated types for the model field will not\n * reflect this branding. Callers consuming the validated value from a Prisma\n * result component may need an explicit `as Id<Brand>` cast at the call site.\n */\nexport type IdTransform<Brand extends string> = {\n /**\n * Read transform: validates the raw database value via `safeParse` and returns\n * `Id<Brand>`. Throws if the value is missing, malformed, or belongs to a\n * different brand.\n *\n * Use in a Prisma `$extends` result component's `compute` function.\n */\n read(value: unknown): Id<Brand>;\n /**\n * Write transform: passes `Id<Brand>` through as its canonical string form.\n * `Id<Brand>` is already the canonical string, so this is an identity function\n * at runtime.\n *\n * Use in a Prisma `$extends` query component or explicit `data` mapping.\n */\n write(value: Id<Brand>): string;\n};\n\n/**\n * Creates a read/write transform pair for use with Prisma's `$extends` extension model.\n *\n * Works with any codec variant exposing `safeParse`.\n *\n * **Prisma casting caveat:** Prisma's `$extends` result component can add\n * typed computed accessors to model instances, but cannot retroactively\n * re-type an existing schema field at the Prisma Client level. The `read`\n * function asserts `Id<Brand>`, but callers will need an explicit\n * `as Id<Brand>` cast at consumption sites where Prisma's generated types\n * are expected.\n *\n * @example\n * ```ts\n * import { idField } from \"@smonn/ids/prisma\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * const userIdField = idField(usr);\n *\n * const xprisma = prisma.$extends({\n * result: {\n * user: {\n * id: {\n * needs: { id: true },\n * compute(user) {\n * // Cast required: Prisma cannot brand the generated type at schema level\n * return userIdField.read(user.id) as Id<\"usr\">;\n * },\n * },\n * },\n * },\n * });\n * ```\n */\nexport function idField<Brand extends string>(codec: IdColumnCodec<Brand>): IdTransform<Brand> {\n return {\n read(value: unknown): Id<Brand> {\n return readIdColumn(codec, value);\n },\n write(value: Id<Brand>): string {\n return value;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,QAA8B,OAAiD;CAC7F,OAAO;EACL,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;EACA,MAAM,OAA0B;GAC9B,OAAO;EACT;CACF;AACF"}
1
+ {"version":3,"file":"prisma.mjs","names":[],"sources":["../src/adapters/prisma.ts"],"sourcesContent":["import { IdsError, isIdsError, type IdsErrorCode } from \"../error.js\";\nimport { readIdColumn, type IdColumnCodec } from \"./adapter-types.js\";\nimport type { Id } from \"../types.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode };\n\nexport type { IdColumnCodec };\n\n/**\n * Read/write transform pair for integrating `Id<Brand>` with Prisma extensions.\n *\n * **Prisma casting caveat:** Prisma cannot fully brand a generated model field\n * type at the schema level. The `read` function asserts `Id<Brand>` at the\n * TypeScript level, but Prisma's generated types for the model field will not\n * reflect this branding. Callers consuming the validated value from a Prisma\n * result component may need an explicit `as Id<Brand>` cast at the call site.\n */\nexport type IdTransform<Brand extends string> = {\n /**\n * Read transform: validates the raw database value via `safeParse` and returns\n * `Id<Brand>`. Throws if the value is missing, malformed, or belongs to a\n * different brand.\n *\n * Use in a Prisma `$extends` result component's `compute` function.\n */\n read(value: unknown): Id<Brand>;\n /**\n * Write transform: passes `Id<Brand>` through as its canonical string form.\n * `Id<Brand>` is already the canonical string, so this is an identity function\n * at runtime.\n *\n * Use in a Prisma `$extends` query component or explicit `data` mapping.\n */\n write(value: Id<Brand>): string;\n};\n\n/**\n * Creates a read/write transform pair for use with Prisma's `$extends` extension model.\n *\n * Works with any codec variant exposing `safeParse`.\n *\n * **Prisma casting caveat:** Prisma's `$extends` result component can add\n * typed computed accessors to model instances, but cannot retroactively\n * re-type an existing schema field at the Prisma Client level. The `read`\n * function asserts `Id<Brand>`, but callers will need an explicit\n * `as Id<Brand>` cast at consumption sites where Prisma's generated types\n * are expected.\n *\n * @example\n * ```ts\n * import { idField } from \"@smonn/ids/prisma\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * const userIdField = idField(usr);\n *\n * const xprisma = prisma.$extends({\n * result: {\n * user: {\n * id: {\n * needs: { id: true },\n * compute(user) {\n * // Cast required: Prisma cannot brand the generated type at schema level\n * return userIdField.read(user.id) as Id<\"usr\">;\n * },\n * },\n * },\n * },\n * });\n * ```\n */\nexport function idField<Brand extends string>(codec: IdColumnCodec<Brand>): IdTransform<Brand> {\n return {\n read(value: unknown): Id<Brand> {\n return readIdColumn(codec, value);\n },\n write(value: Id<Brand>): string {\n return value;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,QAA8B,OAAiD;CAC7F,OAAO;EACL,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;EACA,MAAM,OAA0B;GAC9B,OAAO;EACT;CACF;AACF"}
@@ -1,7 +1,7 @@
1
- import { a as toWireId, i as payloadBytesFromId, n as registerBrand, s as validateBrand, t as wireMethods } from "./codec-shell-CW2sD6BU.mjs";
2
- import { r as writeTimestamp } from "./timestamp-bytes-DoFjLjDp.mjs";
3
- import { t as defaultRng } from "./rng-CPJOx_nE.mjs";
4
- //#region src/layouts/reverse-timestamp.ts
1
+ import { a as toWireId, i as payloadBytesFromId, n as registerBrand, s as validateBrand, t as wireMethods } from "./codec-shell-DvrTDa65.mjs";
2
+ import { r as writeTimestamp } from "./timestamp-bytes-DvhWHDa-.mjs";
3
+ import { t as defaultRng } from "./rng-DHxioKyI.mjs";
4
+ //#region src/codecs/reverse/layout.ts
5
5
  const randomByteLength = 10;
6
6
  /** Writes inverted timestamp bytes, then fills random portion. */
7
7
  function buildReversePayload(ms, rng, buffer, randomView) {
@@ -47,7 +47,7 @@ function createReverseTimestampLayoutOps(prefix, rng) {
47
47
  };
48
48
  }
49
49
  //#endregion
50
- //#region src/reverse.ts
50
+ //#region src/codecs/reverse/index.ts
51
51
  /**
52
52
  * Creates a Reverse Timestamp codec for `brand` (three lowercase a–z characters).
53
53
  *
@@ -82,4 +82,4 @@ function createReverseTimestampId(brand, opts = {}) {
82
82
  //#endregion
83
83
  export { createReverseTimestampId as t };
84
84
 
85
- //# sourceMappingURL=reverse-d5uEoIET.mjs.map
85
+ //# sourceMappingURL=reverse-BRZRc1_U.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reverse-BRZRc1_U.mjs","names":[],"sources":["../src/codecs/reverse/layout.ts","../src/codecs/reverse/index.ts"],"sourcesContent":["import type { Id, Prefix } from \"../../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../../wire/envelope.js\";\nimport { payloadByteLength } from \"../../wire/invariants.js\";\nimport { timestampByteLength, writeTimestamp } from \"../../wire/timestamp-bytes.js\";\n\nconst randomByteLength: number = payloadByteLength - timestampByteLength;\n\n/** Writes inverted timestamp bytes, then fills random portion. */\nfunction buildReversePayload(\n ms: number,\n rng: (target: Uint8Array) => void,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n for (let i = 0; i < timestampByteLength; i++) {\n buffer[i] = ~buffer[i]! & 0xff;\n }\n rng(randomView);\n}\n\n/** Writes inverted timestamp bytes, then fills random portion with a sentinel. */\nfunction buildReverseSentinelPayload(\n ms: number,\n fill: number,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n for (let i = 0; i < timestampByteLength; i++) {\n buffer[i] = ~buffer[i]! & 0xff;\n }\n randomView.fill(fill);\n}\n\n/** Decodes the original timestamp by inverting the first 6 payload bytes. */\nfunction extractReverseTimestampFromId<Brand extends string>(\n prefix: Prefix<Brand>,\n id: Id<Brand>,\n): Date {\n const bytes = payloadBytesFromId(prefix, id);\n let ms = 0;\n for (let i = 0; i < timestampByteLength; i++) {\n ms = ms * 256 + (~bytes[i]! & 0xff);\n }\n return new Date(ms);\n}\n\n/** Layout ops binder for the Reverse Timestamp variant. */\nexport function createReverseTimestampLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n rng: (target: Uint8Array) => void,\n) {\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 buildReversePayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n extractTimestamp: (id: Id<Brand>): Date => extractReverseTimestampFromId(prefix, id),\n minIdForTime: (ms: number): Id<Brand> => {\n buildReverseSentinelPayload(ms, 0x00, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n maxIdForTime: (ms: number): Id<Brand> => {\n buildReverseSentinelPayload(ms, 0xff, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n exampleWireId: (ms: number): Id<Brand> => {\n buildReversePayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n };\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nimport { createReverseTimestampLayoutOps } from \"./layout.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport { defaultRng } from \"../_kernel/rng.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode };\n\n/**\n * Configuration options for a Reverse Timestamp codec instance.\n */\nexport type ReverseTimestampOptions = {\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 Reverse Timestamp IDs.\n *\n * Wire format: `{brand}_` plus 26 lowercase Crockford base32 characters encoding a\n * 16-byte payload (6-byte bitwise-inverted ms timestamp + 10 random bytes). IDs sort\n * by creation time in **descending** (newest-first) order.\n *\n * Range queries across a time interval [t_old, t_new] should scan from\n * `minIdForTime(t_new)` to `maxIdForTime(t_old)` — the reversed sort order means\n * newer timestamps produce lexicographically smaller IDs.\n *\n * Constructed via `createReverseTimestampId(brand)` from `@smonn/ids/reverse`.\n */\nexport type ReverseTimestampCodec<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>` by inverting the timestamp bytes.\n * Trusts the type — use `safeParse()` at boundaries first.\n */\n extractTimestamp(id: Id<Brand>): Date;\n /**\n * Lexicographically smallest ID for any ID generated at `date` (random portion `0x00`).\n * Because timestamps are inverted, a newer `date` yields a lexicographically smaller result —\n * use `minIdForTime(t_new)` as the lower bound when scanning [t_old, t_new].\n * Throws on invalid dates.\n */\n minIdForTime(date: Date): Id<Brand>;\n /**\n * Lexicographically largest ID for any ID generated at `date` (random portion `0xff`).\n * Because timestamps are inverted, an older `date` yields a lexicographically larger result —\n * use `maxIdForTime(t_old)` as the upper bound when scanning [t_old, t_new].\n * Throws on invalid dates.\n */\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/**\n * Creates a Reverse Timestamp codec for `brand` (three lowercase a–z characters).\n *\n * IDs sort newest-first: the 48-bit timestamp field is bitwise-inverted before encoding,\n * so lexicographic ID order equals descending creation-time order. `extractTimestamp`\n * inverts back to recover the original millisecond.\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createReverseTimestampId<Brand extends string>(\n brand: Brand,\n opts: ReverseTimestampOptions = {},\n): ReverseTimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\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 = createReverseTimestampLayoutOps(prefix, 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 minIdForTime: (date: Date) => layout.minIdForTime(date.getTime()),\n maxIdForTime: (date: Date) => layout.maxIdForTime(date.getTime()),\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId(now())),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":";;;;AAKA,MAAM,mBAAA;;AAGN,SAAS,oBACP,IACA,KACA,QACA,YACM;CACN,eAAe,IAAI,MAAM;CACzB,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KACvC,OAAO,KAAK,CAAC,OAAO,KAAM;CAE5B,IAAI,UAAU;AAChB;;AAGA,SAAS,4BACP,IACA,MACA,QACA,YACM;CACN,eAAe,IAAI,MAAM;CACzB,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KACvC,OAAO,KAAK,CAAC,OAAO,KAAM;CAE5B,WAAW,KAAK,IAAI;AACtB;;AAGA,SAAS,8BACP,QACA,IACM;CACN,MAAM,QAAQ,mBAAmB,QAAQ,EAAE;CAC3C,IAAI,KAAK;CACT,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KACvC,KAAK,KAAK,OAAO,CAAC,MAAM,KAAM;CAEhC,OAAO,IAAI,KAAK,EAAE;AACpB;;AAGA,SAAgB,gCACd,QACA,KACA;CACA,MAAM,yBAAS,IAAI,WAAA,EAA4B;CAC/C,MAAM,aAAa,IAAI,WAAW,OAAO,QAAA,GAA6B,gBAAgB;CAEtF,OAAO;EACL,aAAa,OAA0B;GACrC,oBAAoB,IAAI,KAAK,QAAQ,UAAU;GAC/C,OAAO,SAAS,QAAQ,MAAM;EAChC;EACA,mBAAmB,OAAwB,8BAA8B,QAAQ,EAAE;EACnF,eAAe,OAA0B;GACvC,4BAA4B,IAAI,GAAM,QAAQ,UAAU;GACxD,OAAO,SAAS,QAAQ,MAAM;EAChC;EACA,eAAe,OAA0B;GACvC,4BAA4B,IAAI,KAAM,QAAQ,UAAU;GACxD,OAAO,SAAS,QAAQ,MAAM;EAChC;EACA,gBAAgB,OAA0B;GACxC,oBAAoB,IAAI,KAAK,QAAQ,UAAU;GAC/C,OAAO,SAAS,QAAQ,MAAM;EAChC;CACF;AACF;;;;;;;;;;;;;ACcA,SAAgB,yBACd,OACA,OAAgC,CAAC,GACH;CAC9B,cAAc,KAAK;CACnB,cAAc,OAAO,KAAK,mBAAmB;CAE7C,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,MAAM,MAAM,KAAK,OAAO;CACxB,MAAM,SAAwB,GAAG,MAAM;CACvC,MAAM,OAAO,YAAY,MAAM;CAC/B,MAAM,SAAS,gCAAgC,QAAQ,GAAG;CAE1D,OAAO;EACL,gBAAgB,OAAO,WAAW,IAAI,CAAC;EACvC,aAAa,SAAe,OAAO,WAAW,KAAK,QAAQ,CAAC;EAC5D,IAAI,KAAK;EACT,OAAO,KAAK;EACZ,WAAW,KAAK;EAChB,kBAAkB,OAAO;EACzB,eAAe,SAAe,OAAO,aAAa,KAAK,QAAQ,CAAC;EAChE,eAAe,SAAe,OAAO,aAAa,KAAK,QAAQ,CAAC;EAChE,oBAAoB,KAAK,aAAa,OAAO,OAAO,cAAc,IAAI,CAAC,CAAC;EACxE,aAAa,KAAK;CACpB;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-JIPylU_E.mjs";
2
2
  import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, t as Id } from "./types-g7CiQDyE.mjs";
3
3
 
4
- //#region src/reverse.d.ts
4
+ //#region src/codecs/reverse/index.d.ts
5
5
  /**
6
6
  * Configuration options for a Reverse Timestamp codec instance.
7
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"reverse.d.mts","names":[],"sources":["../src/reverse.ts"],"mappings":";;;;AAcA;;;AAAA,KAAY,uBAAA;+EAEV,GAAA;EAEA,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;AAgBF;;;;;;;;;;;;;AAAA,KAAY,qBAAA;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;EA2BJ;;;EAvBtB,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;;;EAKvC,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,IAAA;;;;;;;EAOjC,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;;;;;;;EAO7B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAE7B,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;;;;;;iBAa5B,wBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,GAAM,uBAAA,GACL,qBAAA,CAAsB,KAAA"}
1
+ {"version":3,"file":"reverse.d.mts","names":[],"sources":["../src/codecs/reverse/index.ts"],"mappings":";;;;AAcA;;;AAAA,KAAY,uBAAA;+EAEV,GAAA;EAEA,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;AAgBF;;;;;;;;;;;;;AAAA,KAAY,qBAAA;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;EA2BJ;;;EAvBtB,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;;;EAKvC,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,IAAA;;;;;;;EAOjC,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;;;;;;;EAO7B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EAE7B,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;;;;;;iBAa5B,wBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,GAAM,uBAAA,GACL,qBAAA,CAAsB,KAAA"}
package/dist/reverse.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { t as createReverseTimestampId } from "./reverse-d5uEoIET.mjs";
2
+ import { t as createReverseTimestampId } from "./reverse-BRZRc1_U.mjs";
3
3
  export { IdsError, createReverseTimestampId, isIdsError };
@@ -1,4 +1,4 @@
1
- //#region src/rng.ts
1
+ //#region src/codecs/_kernel/rng.ts
2
2
  /** Default RNG: writes cryptographically random bytes via `crypto.getRandomValues`. */
3
3
  function defaultRng(target) {
4
4
  crypto.getRandomValues(target);
@@ -6,4 +6,4 @@ function defaultRng(target) {
6
6
  //#endregion
7
7
  export { defaultRng as t };
8
8
 
9
- //# sourceMappingURL=rng-CPJOx_nE.mjs.map
9
+ //# sourceMappingURL=rng-DHxioKyI.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng-DHxioKyI.mjs","names":[],"sources":["../src/codecs/_kernel/rng.ts"],"sourcesContent":["/** Default RNG: writes cryptographically random bytes via `crypto.getRandomValues`. */\nexport function defaultRng(target: Uint8Array): void {\n crypto.getRandomValues(target as Uint8Array<ArrayBuffer>);\n}\n"],"mappings":";;AACA,SAAgB,WAAW,QAA0B;CACnD,OAAO,gBAAgB,MAAiC;AAC1D"}
@@ -1,8 +1,8 @@
1
1
  import { t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-CW2sD6BU.mjs";
3
- import { n as readTimestampMsFromBase32Suffix, r as writeTimestamp } from "./timestamp-bytes-DoFjLjDp.mjs";
4
- import { i as encodeKeyMaterial, n as assertValidKeyring, r as decodeKeyMaterial, t as assertValidKeyMaterialByteLength } from "./key-material-gOnqTNoV.mjs";
5
- import { t as defaultRng } from "./rng-CPJOx_nE.mjs";
2
+ import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-DvrTDa65.mjs";
3
+ import { n as readTimestampMsFromBase32Suffix, r as writeTimestamp } from "./timestamp-bytes-DvhWHDa-.mjs";
4
+ import { i as encodeKeyMaterial, n as assertValidKeyring, r as decodeKeyMaterial, t as assertValidKeyMaterialByteLength } from "./key-material-f29JIyrz.mjs";
5
+ import { t as defaultRng } from "./rng-DHxioKyI.mjs";
6
6
  const tagByteLength = 5;
7
7
  const randomOffset = 6;
8
8
  const tagOffset = 11;
@@ -55,15 +55,15 @@ function createSignedTimestampLayoutOps(prefix, brand, rng, hmacKeys) {
55
55
  };
56
56
  }
57
57
  //#endregion
58
- //#region src/signing-key.ts
59
- const hmacInfo = new TextEncoder().encode("ids/signed-timestamp/hmac");
58
+ //#region src/codecs/signed/key.ts
59
+ const hmacInfo = new TextEncoder().encode("@smonn/ids/signed/hmac");
60
60
  const SHA256_DIGEST_BYTES = 32;
61
61
  const internals = /* @__PURE__ */ new WeakMap();
62
62
  /**
63
63
  * Import raw operator key material into a {@link SigningKey} handle.
64
64
  *
65
65
  * Derives a single HMAC-SHA-256 key via HKDF under the domain-separation label
66
- * `ids/signed-timestamp/hmac`. Accepts 16, 24, or 32 bytes. To store or
66
+ * `@smonn/ids/signed/hmac`. Accepts 16, 24, or 32 bytes. To store or
67
67
  * transport key material, use {@link encodeSigningKey} / {@link decodeSigningKey}
68
68
  * (`"hex"` or `"base64url"` — not Crockford base32).
69
69
  *
@@ -118,7 +118,7 @@ function signingKeysEqual(a, b) {
118
118
  return diff === 0;
119
119
  }
120
120
  /**
121
- * Returns the derived HMAC CryptoKey held inside the handle.
121
+ * Returns the derived HMAC webcrypto.CryptoKey held inside the handle.
122
122
  *
123
123
  * Intentional module-internal escape hatch for codec implementations (e.g. `createSignedTimestampId`).
124
124
  * Not re-exported from `@smonn/ids/signed`; external callers cannot reach this.
@@ -145,7 +145,7 @@ async function deriveHmacKey(bytes) {
145
145
  }, false, ["sign", "verify"]);
146
146
  }
147
147
  //#endregion
148
- //#region src/signed.ts
148
+ //#region src/codecs/signed/index.ts
149
149
  /**
150
150
  * Construct a {@link SignedTimestampCodec} for `brand`.
151
151
  *
@@ -204,4 +204,4 @@ function createSignedTimestampId(brand, opts) {
204
204
  //#endregion
205
205
  export { importSigningKey as i, decodeSigningKey as n, encodeSigningKey as r, createSignedTimestampId as t };
206
206
 
207
- //# sourceMappingURL=signed-BnRSC03a.mjs.map
207
+ //# sourceMappingURL=signed-C8OMt3TJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-C8OMt3TJ.mjs","names":[],"sources":["../src/codecs/signed/layout.ts","../src/codecs/signed/key.ts","../src/codecs/signed/index.ts"],"sourcesContent":["import type { webcrypto } from \"node:crypto\";\nimport type { Id, Prefix } from \"../../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../../wire/invariants.js\";\nimport {\n readTimestampMsFromBase32Suffix,\n timestampByteLength,\n writeTimestamp,\n} from \"../../wire/timestamp-bytes.js\";\n\nconst randomByteLength = 5;\nconst tagByteLength = 5;\nconst randomOffset = timestampByteLength; // 6\nconst tagOffset = randomOffset + randomByteLength; // 11\nconst signedContentByteLength = randomOffset + randomByteLength; // 11 (ts6 ‖ rand5)\n\nasync function computeTag(\n hmacKey: webcrypto.CryptoKey,\n brandBytes: Uint8Array,\n signedContent: Uint8Array,\n): Promise<Uint8Array> {\n const message = new Uint8Array(brandBytes.length + signedContent.length);\n message.set(brandBytes, 0);\n message.set(signedContent, brandBytes.length);\n const signature = new Uint8Array(\n await crypto.subtle.sign(\"HMAC\", hmacKey, message as Uint8Array<ArrayBuffer>),\n );\n return signature.subarray(0, tagByteLength);\n}\n\nfunction tagsEqual(a: Uint8Array, b: Uint8Array): boolean {\n /* v8 ignore next -- defensive guard; both call sites always pass tagByteLength-byte arrays */\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a[i]! ^ b[i]!;\n return diff === 0;\n}\n\nexport function createSignedTimestampLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n brand: Brand,\n rng: (target: Uint8Array) => void,\n hmacKeys: readonly webcrypto.CryptoKey[],\n) {\n const signKey = hmacKeys[0]!;\n const brandBytes = new TextEncoder().encode(brand);\n const syncBuffer = new Uint8Array(payloadByteLength);\n\n return {\n generateAt: async (ms: number): Promise<Id<Brand>> => {\n const buffer = new Uint8Array(payloadByteLength);\n writeTimestamp(ms, buffer);\n rng(buffer.subarray(randomOffset, tagOffset));\n const tag = await computeTag(\n signKey,\n brandBytes,\n buffer.subarray(0, signedContentByteLength),\n );\n buffer.set(tag, tagOffset);\n return toWireId(prefix, buffer);\n },\n tryVerify: async (id: Id<Brand>): Promise<boolean> => {\n const payload = payloadBytesFromId(prefix, id);\n const storedTag = payload.subarray(tagOffset, payloadByteLength);\n const signedContent = payload.subarray(0, signedContentByteLength);\n for (const hmacKey of hmacKeys) {\n const expected = await computeTag(hmacKey, brandBytes, signedContent);\n if (tagsEqual(storedTag, expected)) return true;\n }\n return false;\n },\n extractTimestamp: (id: Id<Brand>): Date =>\n new Date(readTimestampMsFromBase32Suffix(id.slice(prefix.length))),\n minIdForTime: (ms: number): Id<Brand> => {\n writeTimestamp(ms, syncBuffer);\n syncBuffer.fill(0x00, randomOffset, payloadByteLength);\n return toWireId(prefix, syncBuffer);\n },\n maxIdForTime: (ms: number): Id<Brand> => {\n writeTimestamp(ms, syncBuffer);\n syncBuffer.fill(0xff, randomOffset, payloadByteLength);\n return toWireId(prefix, syncBuffer);\n },\n exampleWireId: (): Id<Brand> => (prefix + \"0\".repeat(payloadBase32Length)) as Id<Brand>,\n };\n}\n","import type { webcrypto } from \"node:crypto\";\nimport {\n assertValidKeyMaterialByteLength,\n assertValidKeyring,\n decodeKeyMaterial,\n encodeKeyMaterial,\n} from \"../_kernel/key-material.js\";\n\nexport { assertValidKeyring };\n\n/** Wire encoding for signing key raw key bytes (not Crockford base32). */\nexport type SigningKeyFormat = \"hex\" | \"base64url\";\n\nconst hmacInfo = new TextEncoder().encode(\"@smonn/ids/signed/hmac\");\n\nconst SHA256_DIGEST_BYTES = 32;\n\ndeclare const signingKeyBrand: unique symbol;\n\n/**\n * Opaque imported handle for one operator signing key.\n *\n * Holds a single HMAC-SHA-256 key derived via HKDF under the domain-separation\n * label `@smonn/ids/signed/hmac`. The underlying `webcrypto.CryptoKey` is held\n * internally and never exposed to callers. Obtain handles via\n * {@link importSigningKey} and pass them to `createSignedTimestampId` as the\n * `keys` signing keyring.\n *\n * Distinct from both the **Opaque key** and the **Wrapping key** — the same\n * raw key material must not silently serve multiple codecs without an explicit import.\n */\nexport type SigningKey = {\n readonly [signingKeyBrand]: \"SigningKey\";\n};\n\ntype SigningKeyInternals = {\n keyDigest: Uint8Array;\n hmacKey: webcrypto.CryptoKey;\n};\n\nconst internals = new WeakMap<SigningKey, SigningKeyInternals>();\n\n/**\n * Import raw operator key material into a {@link SigningKey} handle.\n *\n * Derives a single HMAC-SHA-256 key via HKDF under the domain-separation label\n * `@smonn/ids/signed/hmac`. Accepts 16, 24, or 32 bytes. To store or\n * transport key material, use {@link encodeSigningKey} / {@link decodeSigningKey}\n * (`\"hex\"` or `\"base64url\"` — not Crockford base32).\n *\n * @param bytes - 16, 24, or 32 raw key bytes.\n * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.\n */\nexport async function importSigningKey(bytes: Uint8Array): Promise<SigningKey> {\n assertValidKeyMaterialByteLength(bytes.length, \"signing\");\n const [hmacKey, digestBuffer] = await Promise.all([\n deriveHmacKey(bytes),\n crypto.subtle.digest(\"SHA-256\", bytes as Uint8Array<ArrayBuffer>),\n ]);\n const key = Object.freeze({}) as SigningKey;\n internals.set(key, { keyDigest: new Uint8Array(digestBuffer), hmacKey });\n return key;\n}\n\n/**\n * Encode raw signing operator key material for storage in env vars or secret managers.\n *\n * Supports `\"hex\"` (lowercase) and `\"base64url\"`. Output round-trips through\n * {@link decodeSigningKey} back to the original bytes.\n *\n * @throws {IdsError} `invalid_key_format` if `format` is not `\"hex\"` or `\"base64url\"`.\n * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.\n */\nexport function encodeSigningKey(bytes: Uint8Array, format: SigningKeyFormat): string {\n return encodeKeyMaterial(bytes, format, \"signing\", \"signing\");\n}\n\n/**\n * Decode key material emitted by {@link encodeSigningKey} back to raw bytes.\n *\n * The result can be passed directly to {@link importSigningKey}.\n *\n * @throws {IdsError} `invalid_key_format` if `format` is not `\"hex\"` or `\"base64url\"`.\n * @throws {IdsError} `invalid_key_encoding` if the string is malformed for its format.\n * @throws {IdsError} `invalid_key_length` if the decoded bytes are not 16, 24, or 32 bytes.\n */\nexport function decodeSigningKey(encoded: string, format: SigningKeyFormat): Uint8Array {\n return decodeKeyMaterial(encoded, format, \"signing\", \"signing\");\n}\n\n/**\n * Returns true when two handles were imported from the same raw key material.\n *\n * Uses a constant-time comparison so duplicate detection over key material does\n * not leak the position of the first differing byte through a timing side channel.\n */\nexport function signingKeysEqual(a: SigningKey, b: SigningKey): boolean {\n const aDigest = getSigningKeyInternals(a).keyDigest;\n const bDigest = getSigningKeyInternals(b).keyDigest;\n let diff = 0;\n for (let i = 0; i < SHA256_DIGEST_BYTES; i++) {\n diff |= aDigest[i]! ^ bDigest[i]!;\n }\n return diff === 0;\n}\n\n/**\n * Returns the derived HMAC webcrypto.CryptoKey held inside the handle.\n *\n * Intentional module-internal escape hatch for codec implementations (e.g. `createSignedTimestampId`).\n * Not re-exported from `@smonn/ids/signed`; external callers cannot reach this.\n */\nexport function getSigningKeyHmacKey(key: SigningKey): webcrypto.CryptoKey {\n return getSigningKeyInternals(key).hmacKey;\n}\n\nfunction getSigningKeyInternals(key: SigningKey): SigningKeyInternals {\n const keyInternals = internals.get(key);\n if (keyInternals === undefined) {\n throw new Error(\"invalid signing key\");\n }\n return keyInternals;\n}\n\nasync function deriveHmacKey(bytes: Uint8Array): Promise<webcrypto.CryptoKey> {\n const base = await crypto.subtle.importKey(\n \"raw\",\n bytes as Uint8Array<ArrayBuffer>,\n \"HKDF\",\n false,\n [\"deriveKey\"],\n );\n return crypto.subtle.deriveKey(\n { name: \"HKDF\", hash: \"SHA-256\", salt: new Uint8Array(), info: hmacInfo },\n base,\n { name: \"HMAC\", hash: \"SHA-256\", length: 256 },\n false,\n [\"sign\", \"verify\"],\n );\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nimport { createSignedTimestampLayoutOps } from \"./layout.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport { defaultRng } from \"../_kernel/rng.js\";\nimport type {\n Id,\n JsonSchema,\n ParseError,\n ParseResult,\n Prefix,\n StandardSchemaProps,\n} from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\nimport {\n assertValidKeyring,\n decodeSigningKey,\n encodeSigningKey,\n getSigningKeyHmacKey,\n importSigningKey,\n signingKeysEqual,\n type SigningKey,\n type SigningKeyFormat,\n} from \"./key.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode };\nexport {\n decodeSigningKey,\n encodeSigningKey,\n importSigningKey,\n type SigningKey,\n type SigningKeyFormat,\n};\n\n/**\n * Configuration options for a Signed Timestamp codec instance.\n */\nexport type SignedTimestampOptions = {\n /**\n * Non-empty ordered signing keyring. The first entry is current — the only one\n * `generate` / `generateAt` sign with. `verify` / `safeVerify` trial every entry\n * until the tag matches. Duplicate raw secrets are rejected at construction.\n */\n keys: [SigningKey, ...SigningKey[]];\n /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */\n now?: () => number;\n /** Writes 5 random bytes into `target` for the random tail. 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 * Result returned by {@link SignedTimestampCodec.safeVerify}.\n *\n * On success, `id` is the canonical {@link Id}.\n * On failure, `error` is a {@link ParseError} for structural problems or\n * `\"verification_failed\"` when the HMAC tag does not match any entry in the\n * signing keyring.\n */\nexport type SafeVerifyResult<Brand extends string> =\n | { ok: true; id: Id<Brand> }\n | { ok: false; error: ParseError | \"verification_failed\" };\n\n/**\n * Codec returned by {@link createSignedTimestampId}.\n *\n * Keeps the 6-byte millisecond timestamp **readable and sortable** like the\n * Timestamp codec, but replaces half of the 10-byte random tail with a truncated\n * HMAC tag, making IDs **tamper-evident and verifiable without a database lookup**.\n *\n * Byte layout: `ts6 ‖ rand5 ‖ tag5` where the 40-bit tag =\n * `trunc(HMAC-SHA256(hmacKey, brand ‖ ts6 ‖ rand5), 40)`.\n *\n * - Async (HMAC): `generate`, `generateAt`, `verify`, `safeVerify`.\n * - Sync (no key / plaintext timestamp): all other methods.\n */\nexport type SignedTimestampCodec<Brand extends string> = {\n /** Produces a canonical ID signed with the current (first) key. */\n generate(): Promise<Id<Brand>>;\n /**\n * Produces a canonical ID with timestamp from `date`, signed with the current key.\n * Throws on invalid dates.\n */\n generateAt(date: Date): Promise<Id<Brand>>;\n /**\n * Recomputes the HMAC tag across every keyring entry.\n *\n * Throws `IdsError` with `code: \"verification_failed\"` if no entry matches.\n * Tamper of the brand, timestamp bytes, or random bytes all fail here.\n */\n verify(id: Id<Brand>): Promise<void>;\n /**\n * Non-throwing path for untrusted input.\n *\n * Structurally parses `input` first (same rules as {@link safeParse}), then\n * verifies the HMAC tag. Returns `{ ok: false, error }` on any failure —\n * {@link ParseError} for structural problems or `\"verification_failed\"` for tag\n * mismatch — without throwing.\n */\n safeVerify(input: unknown): Promise<SafeVerifyResult<Brand>>;\n /**\n * Decodes the creation `Date` from an `Id<Brand>`.\n * Sync — the 6-byte timestamp is plaintext. Trusts the type; use `safeParse()` at boundaries first.\n */\n extractTimestamp(id: Id<Brand>): Date;\n /**\n * Tight lower bound sentinel for range scans (`ts(t) ‖ 0x00×10`).\n * **Not verifiable** — carries no valid tag.\n */\n minIdForTime(date: Date): Id<Brand>;\n /**\n * Tight upper bound sentinel for range scans (`ts(t) ‖ 0xff×10`).\n * **Not verifiable** — carries no valid tag.\n */\n maxIdForTime(date: Date): Id<Brand>;\n /**\n * Strict type guard: `true` only for already-canonical `Id<Brand>` strings.\n * For untrusted input, use `safeParse()` or `safeVerify()` instead.\n */\n is(value: unknown): value is Id<Brand>;\n /** Normalise to canonical form, or throw on parse failure. */\n parse(value: unknown): Id<Brand>;\n /** Normalise to canonical form, or return `{ ok: false, error }`. */\n safeParse(value: unknown): ParseResult<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/**\n * Construct a {@link SignedTimestampCodec} for `brand`.\n *\n * `opts.keys` is a non-empty ordered signing keyring — the first entry is current\n * (used by `generate` / `generateAt`); all entries are tried on `verify` /\n * `safeVerify`; duplicate operator secrets are rejected at construction.\n *\n * @example\n * ```ts\n * const key = await importSigningKey(new Uint8Array(32));\n * const usr = createSignedTimestampId(\"usr\", { keys: [key] });\n *\n * const id = await usr.generate(); // Id<\"usr\">\n * await usr.verify(id); // passes\n * usr.extractTimestamp(id); // Date — sync, timestamp is plaintext\n * ```\n */\nexport function createSignedTimestampId<Brand extends string>(\n brand: Brand,\n opts: SignedTimestampOptions,\n): SignedTimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n assertValidKeyring(opts.keys, signingKeysEqual, \"signing\");\n\n const hmacKeys = opts.keys.map(getSigningKeyHmacKey);\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 = createSignedTimestampLayoutOps(prefix, brand, rng, hmacKeys);\n\n return {\n generate: () => layout.generateAt(now()),\n generateAt: (date: Date) => layout.generateAt(date.getTime()),\n verify: async (id) => {\n const ok = await layout.tryVerify(id);\n if (!ok) throw new IdsError(\"verification_failed\", \"verification failed\");\n },\n safeVerify: async (input) => {\n const parsed = wire.safeParse(input);\n if (!parsed.ok) return parsed;\n const ok = await layout.tryVerify(parsed.id);\n if (!ok) return { ok: false, error: \"verification_failed\" };\n return { ok: true, id: parsed.id };\n },\n extractTimestamp: layout.extractTimestamp,\n minIdForTime: (date: Date) => layout.minIdForTime(date.getTime()),\n maxIdForTime: (date: Date) => layout.maxIdForTime(date.getTime()),\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId()),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
package/dist/signed.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-JIPylU_E.mjs";
2
2
  import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, r as ParseError, t as Id } from "./types-g7CiQDyE.mjs";
3
3
 
4
- //#region src/signing-key.d.ts
4
+ //#region src/codecs/signed/key.d.ts
5
5
  /** Wire encoding for signing key raw key bytes (not Crockford base32). */
6
6
  type SigningKeyFormat = "hex" | "base64url";
7
7
  declare const signingKeyBrand: unique symbol;
@@ -9,7 +9,7 @@ declare const signingKeyBrand: unique symbol;
9
9
  * Opaque imported handle for one operator signing key.
10
10
  *
11
11
  * Holds a single HMAC-SHA-256 key derived via HKDF under the domain-separation
12
- * label `ids/signed-timestamp/hmac`. The underlying `CryptoKey` is held
12
+ * label `@smonn/ids/signed/hmac`. The underlying `webcrypto.CryptoKey` is held
13
13
  * internally and never exposed to callers. Obtain handles via
14
14
  * {@link importSigningKey} and pass them to `createSignedTimestampId` as the
15
15
  * `keys` signing keyring.
@@ -24,7 +24,7 @@ type SigningKey = {
24
24
  * Import raw operator key material into a {@link SigningKey} handle.
25
25
  *
26
26
  * Derives a single HMAC-SHA-256 key via HKDF under the domain-separation label
27
- * `ids/signed-timestamp/hmac`. Accepts 16, 24, or 32 bytes. To store or
27
+ * `@smonn/ids/signed/hmac`. Accepts 16, 24, or 32 bytes. To store or
28
28
  * transport key material, use {@link encodeSigningKey} / {@link decodeSigningKey}
29
29
  * (`"hex"` or `"base64url"` — not Crockford base32).
30
30
  *
@@ -53,7 +53,7 @@ declare function encodeSigningKey(bytes: Uint8Array, format: SigningKeyFormat):
53
53
  */
54
54
  declare function decodeSigningKey(encoded: string, format: SigningKeyFormat): Uint8Array;
55
55
  //#endregion
56
- //#region src/signed.d.ts
56
+ //#region src/codecs/signed/index.d.ts
57
57
  /**
58
58
  * Configuration options for a Signed Timestamp codec instance.
59
59
  */
@@ -1 +1 @@
1
- {"version":3,"file":"signed.d.mts","names":[],"sources":["../src/signing-key.ts","../src/signed.ts"],"mappings":";;;;;KAUY,gBAAA;AAAA,cAME,eAAA;;;;AANF;AAA2B;;;;AAMzB;AAcd;;;KAAY,UAAA;EAAA,UACA,eAAA;AAAA;;;;;;;;;;;;iBAqBU,gBAAA,CAAiB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,UAAA;AAAA;AAoBnE;;;;;;;;AApBmE,iBAoBnD,gBAAA,CAAiB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,gBAAA;AAAA;AAa5D;;;;;;;;AAb4D,iBAa5C,gBAAA,CAAiB,OAAA,UAAiB,MAAA,EAAQ,gBAAA,GAAmB,UAAA;;;;;AA3EjE;KC4BA,sBAAA;EDtBE;;;AAAA;AAcd;ECcE,IAAA,GAAO,UAAA,KAAe,UAAA;EAEtB,GAAA,iBDfU;ECiBV,GAAA,IAAO,MAAA,EAAQ,UAAA,WDIK;ECFpB,mBAAA;AAAA;;;;;;;;;KAWU,gBAAA;EACN,EAAA;EAAU,EAAA,EAAI,EAAA,CAAG,KAAA;AAAA;EACjB,EAAA;EAAW,KAAA,EAAO,UAAA;AAAA;;;;;ADSoC;AAa5D;;;;;;;;KCPY,oBAAA;EDOiE,mECL3E,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA;;;AA1CzB;;EA+CE,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;;;;EAOnC,MAAA,CAAO,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA;;;;;;;;AA1CvB;EAmDA,UAAA,CAAW,KAAA,YAAiB,OAAA,CAAQ,gBAAA,CAAiB,KAAA;EAxC3C;;;;EA6CV,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,IAAA;EA3CX;;;;EAgDtB,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;;;;;EAK7B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EArDP;AAAA;AAexB;;EA2CE,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EAEhC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAE1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EAEvC,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoB5B,uBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,EAAM,sBAAA,GACL,oBAAA,CAAqB,KAAA"}
1
+ {"version":3,"file":"signed.d.mts","names":[],"sources":["../src/codecs/signed/key.ts","../src/codecs/signed/index.ts"],"mappings":";;;;;KAWY,gBAAA;AAAA,cAME,eAAA;;;AANF;AAA2B;;;;AAMzB;AAcd;;;;KAAY,UAAA;EAAA,UACA,eAAA;AAAA;;;;;;;;;;;;iBAqBU,gBAAA,CAAiB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,UAAA;AAoBnE;;;;;;;;;AAAA,iBAAgB,gBAAA,CAAiB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,gBAAA;AAa5D;;;;;;;;;AAAA,iBAAgB,gBAAA,CAAiB,OAAA,UAAiB,MAAA,EAAQ,gBAAA,GAAmB,UAAA;;;;;AA3EjE;KC2BA,sBAAA;EDrBE;;;AAAA;AAcd;ECaE,IAAA,GAAO,UAAA,KAAe,UAAA;EAEtB,GAAA,iBDdU;ECgBV,GAAA,IAAO,MAAA,EAAQ,UAAA,WDKK;ECHpB,mBAAA;AAAA;;;;;;;;;KAWU,gBAAA;EACN,EAAA;EAAU,EAAA,EAAI,EAAA,CAAG,KAAA;AAAA;EACjB,EAAA;EAAW,KAAA,EAAO,UAAA;AAAA;;;;;ADUoC;AAa5D;;;;;;;;KCRY,oBAAA;EDQiE,mECN3E,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA;;;AA1CzB;;EA+CE,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;;;;EAOnC,MAAA,CAAO,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA;;;;;;;;AA1CvB;EAmDA,UAAA,CAAW,KAAA,YAAiB,OAAA,CAAQ,gBAAA,CAAiB,KAAA;EAxC3C;;;;EA6CV,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,IAAA;EA3CX;;;;EAgDtB,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;;;;;EAK7B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,EAAA,CAAG,KAAA;EArDP;AAAA;AAexB;;EA2CE,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EAEhC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAE1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EAEvC,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoB5B,uBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,EAAM,sBAAA,GACL,oBAAA,CAAqB,KAAA"}
package/dist/signed.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-BnRSC03a.mjs";
2
+ import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-C8OMt3TJ.mjs";
3
3
  export { IdsError, createSignedTimestampId, decodeSigningKey, encodeSigningKey, importSigningKey, isIdsError };
@@ -1,6 +1,6 @@
1
- import { a as toWireId, n as registerBrand, s as validateBrand, t as wireMethods } from "./codec-shell-CW2sD6BU.mjs";
2
- import { n as readTimestampMsFromBase32Suffix, r as writeTimestamp } from "./timestamp-bytes-DoFjLjDp.mjs";
3
- //#region src/layouts/timestamp.ts
1
+ import { a as toWireId, n as registerBrand, s as validateBrand, t as wireMethods } from "./codec-shell-DvrTDa65.mjs";
2
+ import { n as readTimestampMsFromBase32Suffix, r as writeTimestamp } from "./timestamp-bytes-DvhWHDa-.mjs";
3
+ //#region src/codecs/timestamp/layout.ts
4
4
  const randomByteLength = 10;
5
5
  /** Writes a 16-byte timestamp-layout payload into codec-owned scratch. */
6
6
  function buildPayload(ms, rng, buffer, randomView) {
@@ -41,7 +41,7 @@ function createTimestampLayoutOps(prefix, rng) {
41
41
  };
42
42
  }
43
43
  //#endregion
44
- //#region src/timestamp.ts
44
+ //#region src/codecs/timestamp/index.ts
45
45
  const hexCharCodeToNibble = /* @__PURE__ */ new Uint8Array(128);
46
46
  for (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;
47
47
  for (let i = 0; i < 6; i++) hexCharCodeToNibble[97 + i] = 10 + i;
@@ -93,4 +93,4 @@ function createTimestampId(brand, opts = {}) {
93
93
  //#endregion
94
94
  export { createTimestampId as t };
95
95
 
96
- //# sourceMappingURL=timestamp-BbZL8hwg.mjs.map
96
+ //# sourceMappingURL=timestamp-DBwVjDkg.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp-DBwVjDkg.mjs","names":[],"sources":["../src/codecs/timestamp/layout.ts","../src/codecs/timestamp/index.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 \"../_kernel/brand.js\";\nimport { createTimestampLayoutOps } from \"./layout.js\";\nimport { registerBrand } from \"../_kernel/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 TimestampOptions = {\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\ntype ResolvedTimestampOptions = Required<Pick<TimestampOptions, \"now\" | \"rng\">> &\n Pick<TimestampOptions, \"allowDuplicateBrand\">;\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 `createOpaqueTimestampId` from `@smonn/ids/opaque`.\n */\nexport type TimestampCodec<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 defaultTimestampOptions: ResolvedTimestampOptions = {\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 createTimestampId<Brand extends string>(\n brand: Brand,\n opts: TimestampOptions = {},\n): TimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const options = {\n now: opts.now ?? defaultTimestampOptions.now,\n rng: opts.rng ?? defaultTimestampOptions.rng,\n } satisfies ResolvedTimestampOptions;\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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"}
@@ -1,4 +1,4 @@
1
- import { o as decodeBase32 } from "./codec-shell-CW2sD6BU.mjs";
1
+ import { o as decodeBase32 } from "./codec-shell-DvrTDa65.mjs";
2
2
  const timestampBase32Length = Math.ceil(48 / 5);
3
3
  /** Write the timestamp in big-endian; encoded via mod-256 to avoid 32-bit bitwise coercion. */
4
4
  function writeTimestamp(ms, buffer) {
@@ -24,4 +24,4 @@ function readTimestampMsFromBase32Suffix(base32Suffix) {
24
24
  //#endregion
25
25
  export { readTimestampMsFromBase32Suffix as n, writeTimestamp as r, readTimestampMs as t };
26
26
 
27
- //# sourceMappingURL=timestamp-bytes-DoFjLjDp.mjs.map
27
+ //# sourceMappingURL=timestamp-bytes-DvhWHDa-.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp-bytes-DvhWHDa-.mjs","names":[],"sources":["../src/wire/timestamp-bytes.ts"],"sourcesContent":["import { decodeBase32 } from \"./base32.js\";\n\n// Timestamp byte layout: first N bytes of the plaintext payload encode a\n// big-endian Unix-ms timestamp. Shared by timestamp-family layouts.\nexport const timestampByteLength: number = 6;\n\nconst timestampBase32Length: number = Math.ceil((timestampByteLength * 8) / 5);\n\n/** Write the timestamp in big-endian; encoded via mod-256 to avoid 32-bit bitwise coercion. */\nexport function writeTimestamp(ms: number, buffer: Uint8Array): void {\n if (Number.isNaN(ms)) throw new Error(\"timestamp is not a number\");\n if (!Number.isInteger(ms)) throw new Error(\"timestamp is not an integer\");\n if (ms < 0) throw new Error(\"timestamp is negative\");\n if (ms >= 2 ** (timestampByteLength * 8)) {\n throw new Error(\"timestamp exceeds 48-bit range\");\n }\n for (let i = timestampByteLength - 1; i >= 0; i--) {\n buffer[i] = ms % 256;\n ms = Math.floor(ms / 256);\n }\n}\n\n/** Decode the first `timestampByteLength` bytes of a buffer as a big-endian unsigned millisecond timestamp. */\nexport function readTimestampMs(buffer: Uint8Array): number {\n let ms = 0;\n for (let i = 0; i < timestampByteLength; i++) ms = ms * 256 + buffer[i]!;\n return ms;\n}\n\n/** Decodes ms from the first 10 base32 chars of a payload suffix (partial decode). */\nexport function readTimestampMsFromBase32Suffix(base32Suffix: string): number {\n return readTimestampMs(decodeBase32(base32Suffix.slice(0, timestampBase32Length)));\n}\n"],"mappings":";AAMA,MAAM,wBAAgC,KAAK,KAAA,KAAiC,CAAC;;AAG7E,SAAgB,eAAe,IAAY,QAA0B;CACnE,IAAI,OAAO,MAAM,EAAE,GAAG,MAAM,IAAI,MAAM,2BAA2B;CACjE,IAAI,CAAC,OAAO,UAAU,EAAE,GAAG,MAAM,IAAI,MAAM,6BAA6B;CACxE,IAAI,KAAK,GAAG,MAAM,IAAI,MAAM,uBAAuB;CACnD,IAAI,MAAM,KAAA,IACR,MAAM,IAAI,MAAM,gCAAgC;CAElD,KAAK,IAAI,IAAA,GAA6B,KAAK,GAAG,KAAK;EACjD,OAAO,KAAK,KAAK;EACjB,KAAK,KAAK,MAAM,KAAK,GAAG;CAC1B;AACF;;AAGA,SAAgB,gBAAgB,QAA4B;CAC1D,IAAI,KAAK;CACT,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KAAK,KAAK,KAAK,MAAM,OAAO;CACrE,OAAO;AACT;;AAGA,SAAgB,gCAAgC,cAA8B;CAC5E,OAAO,gBAAgB,aAAa,aAAa,MAAM,GAAG,qBAAqB,CAAC,CAAC;AACnF"}
@@ -0,0 +1,41 @@
1
+ import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-JIPylU_E.mjs";
2
+ import { n as IdColumnCodec } from "./adapter-types-CdYJM6Sf.mjs";
3
+ import { ValueTransformer } from "typeorm";
4
+
5
+ //#region src/adapters/typeorm.d.ts
6
+ /**
7
+ * TypeORM column transformer for `Id<Brand>`.
8
+ *
9
+ * Returns a `ValueTransformer` object suitable for use in a TypeORM `@Column`
10
+ * decorator's `transformer` option.
11
+ *
12
+ * **Write path (`to`):** passes the `Id<Brand>` directly to the database — it is
13
+ * already the canonical string form.
14
+ *
15
+ * **Read path (`from`):** normalises the raw database value via `codec.safeParse()`.
16
+ * Throws `IdsError` with code `"invalid_id"` if the value does not parse as a valid
17
+ * `Id<Brand>`.
18
+ *
19
+ * **TypeORM branding caveat:** TypeORM cannot brand a generated entity field type at
20
+ * the schema level. Annotate the entity field explicitly: `id!: Id<"usr">`.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { idTransformer } from "@smonn/ids/typeorm";
25
+ * import { createTimestampId } from "@smonn/ids";
26
+ * import type { Id } from "@smonn/ids";
27
+ * import { Column, Entity } from "typeorm";
28
+ *
29
+ * const usr = createTimestampId("usr");
30
+ *
31
+ * @Entity()
32
+ * class User {
33
+ * @Column({ type: "text", transformer: idTransformer(usr) })
34
+ * id!: Id<"usr">;
35
+ * }
36
+ * ```
37
+ */
38
+ declare function idTransformer<Brand extends string>(codec: IdColumnCodec<Brand>): ValueTransformer;
39
+ //#endregion
40
+ export { type IdColumnCodec, IdsError, type IdsErrorCode, idTransformer, isIdsError };
41
+ //# sourceMappingURL=typeorm.d.mts.map