@smonn/ids 0.15.0 → 1.0.0-rc.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 (46) hide show
  1. package/README.md +3 -3
  2. package/dist/{adapter-types-7wWdELSh.mjs → adapter-types-CjzFNDcJ.mjs} +7 -2
  3. package/dist/adapter-types-CjzFNDcJ.mjs.map +1 -0
  4. package/dist/cli.mjs +30 -22
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/drizzle.d.mts +87 -3
  7. package/dist/drizzle.d.mts.map +1 -1
  8. package/dist/drizzle.mjs +115 -5
  9. package/dist/drizzle.mjs.map +1 -1
  10. package/dist/express.d.mts +44 -2
  11. package/dist/express.d.mts.map +1 -1
  12. package/dist/express.mjs +60 -2
  13. package/dist/express.mjs.map +1 -1
  14. package/dist/fastify.d.mts +49 -2
  15. package/dist/fastify.d.mts.map +1 -1
  16. package/dist/fastify.mjs +61 -2
  17. package/dist/fastify.mjs.map +1 -1
  18. package/dist/hono.d.mts +44 -2
  19. package/dist/hono.d.mts.map +1 -1
  20. package/dist/hono.mjs +54 -2
  21. package/dist/hono.mjs.map +1 -1
  22. package/dist/kysely.d.mts +73 -2
  23. package/dist/kysely.d.mts.map +1 -1
  24. package/dist/kysely.mjs +84 -2
  25. package/dist/kysely.mjs.map +1 -1
  26. package/dist/mikro-orm.d.mts +42 -3
  27. package/dist/mikro-orm.d.mts.map +1 -1
  28. package/dist/mikro-orm.mjs +54 -4
  29. package/dist/mikro-orm.mjs.map +1 -1
  30. package/dist/nestjs.mjs +1 -1
  31. package/dist/{opaque-COAcIIY4.mjs → opaque-Dle3CmSE.mjs} +18 -10
  32. package/dist/opaque-Dle3CmSE.mjs.map +1 -0
  33. package/dist/opaque.d.mts +16 -10
  34. package/dist/opaque.d.mts.map +1 -1
  35. package/dist/opaque.mjs +1 -1
  36. package/dist/prisma.d.mts +99 -3
  37. package/dist/prisma.d.mts.map +1 -1
  38. package/dist/prisma.mjs +101 -3
  39. package/dist/prisma.mjs.map +1 -1
  40. package/dist/typeorm.d.mts +25 -1
  41. package/dist/typeorm.d.mts.map +1 -1
  42. package/dist/typeorm.mjs +35 -2
  43. package/dist/typeorm.mjs.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/adapter-types-7wWdELSh.mjs.map +0 -1
  46. package/dist/opaque-COAcIIY4.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque-Dle3CmSE.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, LayoutOps, Prefix } from \"../../types.js\";\nimport { decryptPayload, encryptPayload } from \"../_kernel/crypto.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\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 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): LayoutOps<Brand> & {\n generateAt(ms: number): Promise<Id<Brand>>;\n extractTimestamp(id: Id<Brand>): Promise<Date>;\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: (_ms?: number): Id<Brand> => schemaExample(prefix) as Id<Brand>,\n };\n}\n","import type { webcrypto } from \"node:crypto\";\nimport { deriveKey } from \"../_kernel/crypto.js\";\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\n// HKDF domain-separation label for the Opaque AES key; see ADR-0019 / ADR-0027.\nconst aesInfo = new TextEncoder().encode(\"@smonn/ids/opaque/aes\");\n\ndeclare const opaqueKeyBrand: unique symbol;\n\n/**\n * Opaque imported handle for the Opaque Timestamp codec's AES-256 key.\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 * The same raw secret may safely back an `OpaqueKey` and any other codec's\n * handle (a **primary secret**): each codec derives its key under a distinct\n * HKDF label, so the derived keys are independent — but each codec needs its\n * own explicit import. See ADR-0027.\n */\nexport type OpaqueKey = {\n readonly [opaqueKeyBrand]: \"OpaqueKey\";\n};\n\nconst opaqueKeyInternals = new WeakMap<OpaqueKey, webcrypto.CryptoKey>();\n\n/**\n * Imports operator key material into an {@link OpaqueKey} handle for the Opaque\n * Timestamp codec.\n *\n * The bytes are HKDF **input keying material**, not the AES key itself: the\n * codec derives an **AES-256** key from them via HKDF under the label\n * `@smonn/ids/opaque/aes` (ADR-0027). Accepts 16, 24, or 32 bytes; the input\n * size sets the entropy floor only — a 16-byte handle still yields AES-256 with\n * a 128-bit entropy floor. To store or transport key material, use\n * {@link encodeOpaqueKey} / {@link decodeOpaqueKey} (`\"hex\"` or `\"base64url\"` —\n * not Crockford base32).\n *\n * @param bytes - 16, 24, or 32 bytes of raw key material.\n * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.\n */\nexport async function importOpaqueKey(bytes: Uint8Array): Promise<OpaqueKey> {\n assertValidKeyMaterialByteLength(bytes.length, \"AES\");\n const cryptoKey = await deriveKey(bytes, aesInfo, { name: \"AES-CBC\", length: 256 }, [\n \"encrypt\",\n \"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 Opaque key material bytes for storage in env vars or secret managers.\n *\n * @param bytes - 16, 24, or 32 raw Opaque key material bytes.\n * @param format - `hex` (lowercase) or `base64url`.\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 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 * @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 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 {\n Id,\n JsonSchema,\n ParseResult,\n Prefix,\n StandardSchemaProps,\n ValidBrand,\n} 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 *\n * @remarks\n * **Security properties (unauthenticated, deterministic, and malleable by design):**\n *\n * - The payload is AES-CBC encrypted but **unauthenticated** — there is no\n * integrity tag. A tampered or wrong-key payload decrypts to garbage bytes\n * without throwing.\n * - Opaque IDs must be treated as **opaque handles**, not as trusted or\n * authenticated tokens.\n * - `extractTimestamp` is best-effort on untrusted input: a wrong or tampered\n * key returns a plausible-looking `Date` without error, not a verification\n * failure. Do not treat the returned timestamp as proof of origin.\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 /**\n * JSON Schema for the canonical wire form. The `pattern` matches the canonical stored\n * form only and is deliberately stricter than `parse()`/`safeParse()`, which accept\n * uppercase letters and Crockford aliases (`o`/`i`/`l`) before normalising. See ADR-0003.\n * The `example` is a structural placeholder (generated at construction time).\n */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n /**\n * Converts a trusted `Id<Brand>` to an RFC 9562 canonical (lowercase, hyphenated)\n * UUID string by reinterpreting the 16-byte payload verbatim. The payload is the\n * encrypted ciphertext — `toUUID` does not decrypt it. Total — cannot fail.\n * Returns a plain `string` (brand is shed). See ADR-0024.\n */\n toUUID(id: Id<Brand>): string;\n /**\n * Parses a UUID string into an `Id<Brand>`. Accepts case-insensitive `8-4-4-4-12`\n * hyphenated form only. Throws `IdsError` with `code: \"invalid_id\"` on bad input.\n * See ADR-0024.\n */\n fromUUID(value: string): Id<Brand>;\n /**\n * Non-throwing UUID parse. Returns `{ ok: true, id }` or\n * `{ ok: false, error: \"not_string\" | \"invalid_uuid\" }`. See ADR-0024.\n */\n safeFromUUID(value: unknown): ParseResult<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 & ValidBrand<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 toUUID: wire.toUUID,\n fromUUID: wire.fromUUID,\n safeFromUUID: wire.safeFromUUID,\n };\n}\n"],"mappings":";;;;AAWA,SAAS,eAAe,IAAY,KAA+C;CACjF,MAAM,4BAAY,IAAI,WAAA,EAA4B;CAClD,eAAe,IAAI,SAAS;CAC5B,IAAI,UAAU,SAAA,GAAA,EAA+C,CAAC;CAC9D,OAAO;AACT;AAEA,eAAe,uBACb,QACA,KACA,IACe;CACf,MAAM,YAAY,MAAM,eAAe,KAAK,mBAAmB,QAAQ,EAAE,CAAC;CAC1E,OAAO,IAAI,KAAK,gBAAgB,SAAS,CAAC;AAC5C;;;AAIA,eAAe,eACb,QACA,KACA,KACA,IACoB;CAGpB,OAAO,SAAS,QAAQ,MADA,eAAe,KADrB,eAAe,IAAI,GACe,CAAC,CACpB;AACnC;;AAGA,SAAS,cAAoC,QAA+B;CAC1E,OAAO,SAAS,IAAI,OAAO,mBAAmB;AAChD;;AAGA,SAAgB,sBACd,QACA,KACA,KAIA;CACA,OAAO;EACL,aAAa,OAAmC,eAAe,QAAQ,KAAK,KAAK,EAAE;EACnF,mBAAmB,OAAiC,uBAAuB,QAAQ,KAAK,EAAE;EAC1F,gBAAgB,QAA4B,cAAc,MAAM;CAClE;AACF;;;AC/CA,MAAM,UAAU,IAAI,YAAY,CAAC,CAAC,OAAO,uBAAuB;AAoBhE,MAAM,qCAAqB,IAAI,QAAwC;;;;;;;;;;;;;;;;AAiBvE,eAAsB,gBAAgB,OAAuC;CAC3E,iCAAiC,MAAM,QAAQ,KAAK;CACpD,MAAM,YAAY,MAAM,UAAU,OAAO,SAAS;EAAE,MAAM;EAAW,QAAQ;CAAI,GAAG,CAClF,WACA,SACF,CAAC;CACD,MAAM,MAAM,OAAO,OAAO,CAAC,CAAC;CAC5B,mBAAmB,IAAI,KAAK,SAAS;CACrC,OAAO;AACT;AAEA,SAAgB,sBAAsB,KAAqC;CACzE,MAAM,YAAY,mBAAmB,IAAI,GAAG;CAC5C,IAAI,cAAc,KAAA,GAChB,MAAM,IAAI,MAAM,oBAAoB;CAEtC,OAAO;AACT;;;;;;;;;AAUA,SAAgB,gBAAgB,OAAmB,QAAiC;CAClF,OAAO,kBAAkB,OAAO,QAAQ,UAAU,KAAK;AACzD;;;;;;;;;;AAWA,SAAgB,gBAAgB,SAAiB,QAAqC;CACpF,OAAO,kBAAkB,SAAS,QAAQ,UAAU,KAAK;AAC3D;;;;;;;;;;ACqCA,SAAgB,wBACd,OACA,MAC6B;CAC7B,cAAc,KAAK;CACnB,cAAc,OAAO,KAAK,mBAAmB;CAE7C,MAAM,YAAY,sBAAsB,KAAK,GAAG;CAChD,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,MAAM,MAAM,KAAK,OAAO;CACxB,MAAM,SAAwB,GAAG,MAAM;CACvC,MAAM,OAAO,YAAY,MAAM;CAC/B,MAAM,SAAS,sBAAsB,QAAQ,WAAW,GAAG;CAE3D,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,oBAAoB,KAAK,aAAa,OAAO,OAAO,cAAc,CAAC;EACnE,aAAa,KAAK;EAClB,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,cAAc,KAAK;CACrB;AACF"}
package/dist/opaque.d.mts CHANGED
@@ -6,34 +6,40 @@ import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-CifcK
6
6
  type OpaqueKeyFormat = "hex" | "base64url";
7
7
  declare const opaqueKeyBrand: unique symbol;
8
8
  /**
9
- * Opaque imported handle for one AES key used by the Opaque Timestamp codec.
9
+ * Opaque imported handle for the Opaque Timestamp codec's AES-256 key.
10
10
  *
11
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
  *
15
- * Distinct from the `WrappingKey` used by `@smonn/ids/wrapped` one raw
16
- * secret must not silently serve both codecs without an explicit import.
15
+ * The same raw secret may safely back an `OpaqueKey` and any other codec's
16
+ * handle (a **primary secret**): each codec derives its key under a distinct
17
+ * HKDF label, so the derived keys are independent — but each codec needs its
18
+ * own explicit import. See ADR-0027.
17
19
  */
18
20
  type OpaqueKey = {
19
21
  readonly [opaqueKeyBrand]: "OpaqueKey";
20
22
  };
21
23
  /**
22
- * Imports raw AES key bytes into an {@link OpaqueKey} handle for the Opaque
24
+ * Imports operator key material into an {@link OpaqueKey} handle for the Opaque
23
25
  * Timestamp codec.
24
26
  *
25
- * Accepts 16, 24, or 32 bytes (AES-128 / AES-192 / AES-256 strength).
26
- * To store or transport key material, use {@link encodeOpaqueKey} /
27
- * {@link decodeOpaqueKey} (`"hex"` or `"base64url"` not Crockford base32).
27
+ * The bytes are HKDF **input keying material**, not the AES key itself: the
28
+ * codec derives an **AES-256** key from them via HKDF under the label
29
+ * `@smonn/ids/opaque/aes` (ADR-0027). Accepts 16, 24, or 32 bytes; the input
30
+ * size sets the entropy floor only — a 16-byte handle still yields AES-256 with
31
+ * a 128-bit entropy floor. To store or transport key material, use
32
+ * {@link encodeOpaqueKey} / {@link decodeOpaqueKey} (`"hex"` or `"base64url"` —
33
+ * not Crockford base32).
28
34
  *
29
- * @param bytes - 16, 24, or 32 raw key bytes.
35
+ * @param bytes - 16, 24, or 32 bytes of raw key material.
30
36
  * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.
31
37
  */
32
38
  declare function importOpaqueKey(bytes: Uint8Array): Promise<OpaqueKey>;
33
39
  /**
34
- * Encodes raw AES key bytes for storage in env vars or secret managers.
40
+ * Encodes raw Opaque key material bytes for storage in env vars or secret managers.
35
41
  *
36
- * @param bytes - 16, 24, or 32 raw key bytes (AES-128/192/256).
42
+ * @param bytes - 16, 24, or 32 raw Opaque key material bytes.
37
43
  * @param format - `hex` (lowercase) or `base64url`.
38
44
  * @throws {IdsError} `invalid_key_format` if `format` is not `"hex"` or `"base64url"`.
39
45
  * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.
@@ -1 +1 @@
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;AAgBZ;;;;;;;;;iBAAsB,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;;;AAAA;AA8BlE;;;;;iBAAgB,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;AAAA;AAa3D;;;;;iBAAgB,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;;;KCtD/D,sBAAA;EDpB0B;;;;AAExB;AAYd;;;ECeE,GAAA,EAAK,SAAA,EDdK;ECgBV,GAAA,iBDAoB;ECEpB,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;;;;;;;;ADJgE;AA8BlE;;;;;;;;;AAA2D;AAa3D;;KChBY,oBAAA;EDgB+D,+ECdzE,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA;EAEvB,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;ADYsC;;ECPzE,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;;AA/ClC;;EAmDE,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAtCX;;;EA0Cf,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;;;;AAxCvC;AAuBF;EAyBE,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA;;;;;;;EAOzC,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;;;;;;;EAO1C,MAAA,CAAO,EAAA,EAAI,EAAA,CAAG,KAAA;;;;;;EAMd,QAAA,CAAS,KAAA,WAAgB,EAAA,CAAG,KAAA;;;;;EAK5B,YAAA,CAAa,KAAA,YAAiB,WAAA,CAAY,KAAA;AAAA;;;;;;;;iBAU5B,uBAAA,uBACd,KAAA,EAAO,KAAA,GAAQ,UAAA,CAAW,KAAA,GAC1B,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":";;;;;KASY,eAAA;AAAA,cAKE,cAAA;AALd;;;;AAAY;AAA0B;;;;AAKxB;AAcd;;AAnBA,KAmBY,SAAA;EAAA,UACA,cAAA;AAAA;AAoBZ;;;;;;;;;;;;;AAAkE;AA2BlE;AA3BA,iBAAsB,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;;;;;;;AA2BP;AAa3D;iBAbgB,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;;;;;AAagB;;iBAA3D,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;;;KC7D/D,sBAAA;EDnB0B;;;;AAKxB;AAcd;;;ECSE,GAAA,EAAK,SAAA,EDRK;ECUV,GAAA,iBDUoB;ECRpB,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;;;;;;;;ADMgE;AA2BlE;;;;;;;;;AAA2D;AAa3D;;KCvBY,oBAAA;EDuB+D,+ECrBzE,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA;EAEvB,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;ADmBsC;;ECdzE,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;;AA/ClC;;EAmDE,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAtCX;;;EA0Cf,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;;;;AAxCvC;AAuBF;EAyBE,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA;;;;;;;EAOzC,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;;;;;;;EAO1C,MAAA,CAAO,EAAA,EAAI,EAAA,CAAG,KAAA;;;;;;EAMd,QAAA,CAAS,KAAA,WAAgB,EAAA,CAAG,KAAA;;;;;EAK5B,YAAA,CAAa,KAAA,YAAiB,WAAA,CAAY,KAAA;AAAA;;;;;;;;iBAU5B,uBAAA,uBACd,KAAA,EAAO,KAAA,GAAQ,UAAA,CAAW,KAAA,GAC1B,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-COAcIIY4.mjs";
2
+ import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-Dle3CmSE.mjs";
3
3
  export { IdsError, createOpaqueTimestampId, decodeOpaqueKey, encodeOpaqueKey, importOpaqueKey, isIdsError };
package/dist/prisma.d.mts CHANGED
@@ -1,9 +1,29 @@
1
1
  import { t as Id } from "./types-hGBnCpJj.mjs";
2
2
  import { n as IdColumnCodec } from "./adapter-types-Bia_w9sg.mjs";
3
3
  import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-CifcKKOG.mjs";
4
+ import { ModelQueryOptionsCb } from "@prisma/client/runtime/library";
4
5
 
5
6
  //#region src/adapters/prisma.d.ts
6
7
  /**
8
+ * Extension of {@link IdColumnCodec} that also exposes synchronous `generate()`.
9
+ * Required by {@link idField} so that {@link IdTransform.defaultQuery} can produce
10
+ * IDs at write time. Every full codec variant (Timestamp, Reverse Timestamp) satisfies
11
+ * this; async-generate codecs (Opaque, Signed, Wrapped, Digest) do not and are
12
+ * therefore unsupported by `defaultQuery`.
13
+ */
14
+ type IdGeneratingCodec<Brand extends string> = IdColumnCodec<Brand> & {
15
+ generate(): Id<Brand>;
16
+ };
17
+ /**
18
+ * The per-model object returned by {@link IdTransform.defaultQuery}, suitable for
19
+ * the model-level value inside a Prisma `$extends({ query: { modelName: … } })` block.
20
+ * Structurally identical to `{ [operation: string]: ModelQueryOptionsCb }` from
21
+ * `@prisma/client/runtime/library`.
22
+ */
23
+ type IdQueryField = {
24
+ [operation: string]: ModelQueryOptionsCb;
25
+ };
26
+ /**
7
27
  * Typed `$extends` result-component field definition produced by
8
28
  * {@link IdTransform.computeField} — a `{ needs, compute }` pair whose `compute`
9
29
  * return type is statically `Id<Brand>`, so the extended-client model field is
@@ -14,6 +34,15 @@ type IdComputeField<Brand extends string> = {
14
34
  compute: (model: Record<string, unknown>) => Id<Brand>;
15
35
  };
16
36
  /**
37
+ * Typed `$extends` result-component field definition produced by
38
+ * {@link IdTransform.computeNullableField} — like {@link IdComputeField} but
39
+ * `compute` returns `Id<Brand> | null` for nullable columns.
40
+ */
41
+ type NullableIdComputeField<Brand extends string> = {
42
+ needs: Record<string, boolean>;
43
+ compute: (model: Record<string, unknown>) => Id<Brand> | null;
44
+ };
45
+ /**
17
46
  * Read/write transform pair and `$extends` result-component factory for
18
47
  * integrating `Id<Brand>` with Prisma extensions.
19
48
  */
@@ -25,6 +54,11 @@ type IdTransform<Brand extends string> = {
25
54
  */
26
55
  read(value: unknown): Id<Brand>;
27
56
  /**
57
+ * Nullable read transform: returns `null` when `value` is `null` or `undefined`;
58
+ * otherwise delegates to {@link read}. Use for optional foreign keys.
59
+ */
60
+ readNullable(value: unknown): Id<Brand> | null;
61
+ /**
28
62
  * Write transform: passes `Id<Brand>` through as its canonical string form.
29
63
  * `Id<Brand>` is already the canonical string, so this is an identity function
30
64
  * at runtime.
@@ -51,16 +85,50 @@ type IdTransform<Brand extends string> = {
51
85
  * ```
52
86
  */
53
87
  computeField(fieldName: string): IdComputeField<Brand>;
88
+ /**
89
+ * Creates a `$extends` query-component model slice that auto-generates
90
+ * `Id<Brand>` values for `create`, `createMany`, and `upsert` operations
91
+ * when the field is absent, `undefined`, or `null` in `args.data` (or
92
+ * `args.create` for upsert). Explicitly supplied values are always passed
93
+ * through unchanged.
94
+ *
95
+ * @param fieldName - The model field to auto-generate (e.g. `"id"`).
96
+ * @returns An {@link IdQueryField} suitable for the model-level value inside
97
+ * a Prisma `$extends({ query: { modelName: … } })` block.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const xprisma = prisma.$extends({
102
+ * query: { user: userIdField.defaultQuery("id") },
103
+ * result: { user: { id: userIdField.computeField("id") } },
104
+ * });
105
+ * // id is auto-filled on create, and typed as Id<"usr"> on read
106
+ * await xprisma.user.create({ data: { name: "Alice" } });
107
+ * ```
108
+ */
109
+ defaultQuery(fieldName: string): IdQueryField;
110
+ /**
111
+ * Like {@link computeField} but for nullable columns — `compute` returns
112
+ * `Id<Brand> | null` instead of `Id<Brand>`.
113
+ *
114
+ * @param fieldName - The nullable model field to read from.
115
+ * @returns A {@link NullableIdComputeField} whose `compute` returns `Id<Brand> | null`.
116
+ */
117
+ computeNullableField(fieldName: string): NullableIdComputeField<Brand>;
54
118
  };
55
119
  /**
56
120
  * Creates a read/write transform pair for use with Prisma's `$extends` extension model.
57
121
  *
58
- * Works with any codec variant exposing `safeParse`.
122
+ * Requires a codec variant that exposes a synchronous `generate()` in addition to `safeParse` — see {@link IdGeneratingCodec}. Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed, Wrapped, and Digest codecs cannot be passed to `idField()`.
59
123
  *
60
124
  * Use `computeField(fieldName)` to produce a typed `$extends` result-component
61
125
  * field definition — the brand is carried through Prisma's type machinery
62
126
  * automatically and no per-call-site cast is required.
63
127
  *
128
+ * For codecs that do not expose a synchronous `generate()` (Opaque Timestamp,
129
+ * Signed Timestamp, Wrapped key, Digest), use {@link idFieldReadOnly} instead —
130
+ * it accepts any {@link IdColumnCodec} and omits `defaultQuery`.
131
+ *
64
132
  * @example
65
133
  * ```ts
66
134
  * import { idField } from "@smonn/ids/prisma";
@@ -77,7 +145,35 @@ type IdTransform<Brand extends string> = {
77
145
  * // xprisma.user.findUnique(…).id is typed as Id<"usr"> — no cast required
78
146
  * ```
79
147
  */
80
- declare function idField<Brand extends string>(codec: IdColumnCodec<Brand>): IdTransform<Brand>;
148
+ declare function idField<Brand extends string>(codec: IdGeneratingCodec<Brand>): IdTransform<Brand>;
149
+ /**
150
+ * Read-only sibling of {@link idField} for codec variants that do not expose a
151
+ * synchronous `generate()` — Opaque Timestamp, Signed Timestamp, Wrapped key,
152
+ * and Digest codecs all qualify.
153
+ *
154
+ * Accepts any {@link IdColumnCodec} (the wider constraint that only requires
155
+ * `safeParse`) and returns the full read/transform surface of {@link IdTransform}
156
+ * **minus `defaultQuery`**. Because `defaultQuery` is the only method that calls
157
+ * `generate()`, callers who only need the read path are not forced to provide a
158
+ * synchronous generator.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * import { idFieldReadOnly } from "@smonn/ids/prisma";
163
+ * import { createOpaqueTimestampId } from "@smonn/ids/opaque";
164
+ *
165
+ * const inv = createOpaqueTimestampId("inv", { key });
166
+ * const invoiceIdField = idFieldReadOnly(inv);
167
+ *
168
+ * const xprisma = prisma.$extends({
169
+ * result: {
170
+ * invoice: { id: invoiceIdField.computeField("id") },
171
+ * },
172
+ * });
173
+ * // xprisma.invoice.findUnique(…).id is typed as Id<"inv"> — no cast required
174
+ * ```
175
+ */
176
+ declare function idFieldReadOnly<Brand extends string>(codec: IdColumnCodec<Brand>): Omit<IdTransform<Brand>, "defaultQuery">;
81
177
  //#endregion
82
- export { type IdColumnCodec, IdComputeField, IdTransform, IdsError, type IdsErrorCode, idField, isIdsError };
178
+ export { type IdColumnCodec, IdComputeField, IdGeneratingCodec, IdQueryField, IdTransform, IdsError, type IdsErrorCode, NullableIdComputeField, idField, idFieldReadOnly, isIdsError };
83
179
  //# sourceMappingURL=prisma.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.mts","names":[],"sources":["../src/adapters/prisma.ts"],"mappings":";;;;;AAcA;;;;;;AAAA,KAAY,cAAA;EACV,KAAA,EAAO,MAAA;EACP,OAAA,GAAU,KAAA,EAAO,MAAA,sBAA4B,EAAA,CAAG,KAAA;AAAA;;;;;KAOtC,WAAA;;;;AAPsC;AAOlD;EAME,IAAA,CAAK,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;;;;;EAQzB,KAAA,CAAM,KAAA,EAAO,EAAA,CAAG,KAAA;EAmBiB;;;;;;;;;;;;;;;AAAe;AA4BlD;;EA5BE,YAAA,CAAa,SAAA,WAAoB,cAAA,CAAe,KAAA;AAAA;;;;;;;;;;;;;AA4BsC;;;;;;;;;;;;;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":";;;;;;AAgBA;;;;;;;AAAA,KAAY,iBAAA,yBAA0C,aAAA,CAAc,KAAA;EAClE,QAAA,IAAY,EAAA,CAAG,KAAA;AAAA;;;;;;;KASL,YAAA;EAAA,CAAsC,SAAA,WAAA,mBAAA;AAAA;;;AAAA;AAQlD;;;KAAY,cAAA;EACV,KAAA,EAAO,MAAA;EACP,OAAA,GAAU,KAAA,EAAO,MAAA,sBAA4B,EAAA,CAAG,KAAA;AAAA;;;;;;KAQtC,sBAAA;EACV,KAAA,EAAO,MAAA;EACP,OAAA,GAAU,KAAA,EAAO,MAAA,sBAA4B,EAAA,CAAG,KAAA;AAAA;;;AAVA;AAQlD;KASY,WAAA;;;;;;EAMV,IAAA,CAAK,KAAA,YAAiB,EAAA,CAAG,KAAA;EAboB;;;;EAkB7C,YAAA,CAAa,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;;;AAlBe;AAOlD;EAmBE,KAAA,CAAM,KAAA,EAAO,EAAA,CAAG,KAAA;;;;;;;;;;;;;;;;;;;EAmBhB,YAAA,CAAa,SAAA,WAAoB,cAAA,CAAe,KAAA;;;;;;;;;;;;;;;;;;;;;AA8BgB;EARhE,YAAA,CAAa,SAAA,WAAoB,YAAA;EAwCnB;;;;;;;EAhCd,oBAAA,CAAqB,SAAA,WAAoB,sBAAA,CAAuB,KAAA;AAAA;;;;;;;AAgC0B;AA+F5F;;;;;;;;;;;;;;;;;;AAEoB;;;;iBAjGJ,OAAA,uBAA8B,KAAA,EAAO,iBAAA,CAAkB,KAAA,IAAS,WAAA,CAAY,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+F5E,eAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,IACpB,IAAA,CAAK,WAAA,CAAY,KAAA"}
package/dist/prisma.mjs CHANGED
@@ -1,15 +1,19 @@
1
1
  import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { t as readIdColumn } from "./adapter-types-7wWdELSh.mjs";
2
+ import { n as readIdColumnNullable, t as readIdColumn } from "./adapter-types-CjzFNDcJ.mjs";
3
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
  *
7
- * Works with any codec variant exposing `safeParse`.
7
+ * Requires a codec variant that exposes a synchronous `generate()` in addition to `safeParse` — see {@link IdGeneratingCodec}. Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed, Wrapped, and Digest codecs cannot be passed to `idField()`.
8
8
  *
9
9
  * Use `computeField(fieldName)` to produce a typed `$extends` result-component
10
10
  * field definition — the brand is carried through Prisma's type machinery
11
11
  * automatically and no per-call-site cast is required.
12
12
  *
13
+ * For codecs that do not expose a synchronous `generate()` (Opaque Timestamp,
14
+ * Signed Timestamp, Wrapped key, Digest), use {@link idFieldReadOnly} instead —
15
+ * it accepts any {@link IdColumnCodec} and omits `defaultQuery`.
16
+ *
13
17
  * @example
14
18
  * ```ts
15
19
  * import { idField } from "@smonn/ids/prisma";
@@ -27,10 +31,98 @@ import { t as readIdColumn } from "./adapter-types-7wWdELSh.mjs";
27
31
  * ```
28
32
  */
29
33
  function idField(codec) {
34
+ const { generate } = codec;
35
+ return {
36
+ read(value) {
37
+ return readIdColumn(codec, value);
38
+ },
39
+ readNullable(value) {
40
+ return readIdColumnNullable(codec, value);
41
+ },
42
+ write(value) {
43
+ return value;
44
+ },
45
+ computeField(fieldName) {
46
+ return {
47
+ needs: { [fieldName]: true },
48
+ compute: (model) => readIdColumn(codec, model[fieldName])
49
+ };
50
+ },
51
+ defaultQuery(fieldName) {
52
+ function injectIfAbsent(data) {
53
+ if (data[fieldName] == null) return {
54
+ ...data,
55
+ [fieldName]: generate()
56
+ };
57
+ return data;
58
+ }
59
+ return {
60
+ async create({ args, query }) {
61
+ const data = args.data;
62
+ return query(data != null ? {
63
+ ...args,
64
+ data: injectIfAbsent(data)
65
+ } : args);
66
+ },
67
+ async createMany({ args, query }) {
68
+ const data = args.data;
69
+ return query(Array.isArray(data) ? {
70
+ ...args,
71
+ data: data.map((item) => injectIfAbsent(item))
72
+ } : args);
73
+ },
74
+ async upsert({ args, query }) {
75
+ const createData = args.create;
76
+ return query(createData != null ? {
77
+ ...args,
78
+ create: injectIfAbsent(createData)
79
+ } : args);
80
+ }
81
+ };
82
+ },
83
+ computeNullableField(fieldName) {
84
+ return {
85
+ needs: { [fieldName]: true },
86
+ compute: (model) => readIdColumnNullable(codec, model[fieldName])
87
+ };
88
+ }
89
+ };
90
+ }
91
+ /**
92
+ * Read-only sibling of {@link idField} for codec variants that do not expose a
93
+ * synchronous `generate()` — Opaque Timestamp, Signed Timestamp, Wrapped key,
94
+ * and Digest codecs all qualify.
95
+ *
96
+ * Accepts any {@link IdColumnCodec} (the wider constraint that only requires
97
+ * `safeParse`) and returns the full read/transform surface of {@link IdTransform}
98
+ * **minus `defaultQuery`**. Because `defaultQuery` is the only method that calls
99
+ * `generate()`, callers who only need the read path are not forced to provide a
100
+ * synchronous generator.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * import { idFieldReadOnly } from "@smonn/ids/prisma";
105
+ * import { createOpaqueTimestampId } from "@smonn/ids/opaque";
106
+ *
107
+ * const inv = createOpaqueTimestampId("inv", { key });
108
+ * const invoiceIdField = idFieldReadOnly(inv);
109
+ *
110
+ * const xprisma = prisma.$extends({
111
+ * result: {
112
+ * invoice: { id: invoiceIdField.computeField("id") },
113
+ * },
114
+ * });
115
+ * // xprisma.invoice.findUnique(…).id is typed as Id<"inv"> — no cast required
116
+ * ```
117
+ */
118
+ function idFieldReadOnly(codec) {
30
119
  return {
31
120
  read(value) {
32
121
  return readIdColumn(codec, value);
33
122
  },
123
+ readNullable(value) {
124
+ return readIdColumnNullable(codec, value);
125
+ },
34
126
  write(value) {
35
127
  return value;
36
128
  },
@@ -39,10 +131,16 @@ function idField(codec) {
39
131
  needs: { [fieldName]: true },
40
132
  compute: (model) => readIdColumn(codec, model[fieldName])
41
133
  };
134
+ },
135
+ computeNullableField(fieldName) {
136
+ return {
137
+ needs: { [fieldName]: true },
138
+ compute: (model) => readIdColumnNullable(codec, model[fieldName])
139
+ };
42
140
  }
43
141
  };
44
142
  }
45
143
  //#endregion
46
- export { IdsError, idField, isIdsError };
144
+ export { IdsError, idField, idFieldReadOnly, isIdsError };
47
145
 
48
146
  //# sourceMappingURL=prisma.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.mjs","names":[],"sources":["../src/adapters/prisma.ts"],"sourcesContent":["import { 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 } from \"../error.js\";\n\nexport type { IdColumnCodec };\n\n/**\n * Typed `$extends` result-component field definition produced by\n * {@link IdTransform.computeField} — a `{ needs, compute }` pair whose `compute`\n * return type is statically `Id<Brand>`, so the extended-client model field is\n * typed correctly without a per-call-site cast.\n */\nexport type IdComputeField<Brand extends string> = {\n needs: Record<string, boolean>;\n compute: (model: Record<string, unknown>) => Id<Brand>;\n};\n\n/**\n * Read/write transform pair and `$extends` result-component factory for\n * integrating `Id<Brand>` with Prisma extensions.\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 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 * Creates a typed `$extends` result-component field definition that carries\n * `Id<Brand>` through Prisma's type machinery without a per-call-site cast.\n *\n * @param fieldName - The model field to read from (e.g. `\"id\"`).\n * @returns An {@link IdComputeField} whose `compute` return type is statically\n * `Id<Brand>`, so the extended-client model field is typed correctly.\n *\n * @example\n * ```ts\n * const xprisma = prisma.$extends({\n * result: {\n * user: { id: userIdField.computeField(\"id\") },\n * },\n * });\n * // xprisma.user.findUnique(…).id is typed as Id<\"usr\"> — no cast required\n * ```\n */\n computeField(fieldName: string): IdComputeField<Brand>;\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 * Use `computeField(fieldName)` to produce a typed `$extends` result-component\n * field definition — the brand is carried through Prisma's type machinery\n * automatically and no per-call-site cast is required.\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: { id: userIdField.computeField(\"id\") },\n * },\n * });\n * // xprisma.user.findUnique(…).id is typed as Id<\"usr\"> — no cast required\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 computeField(fieldName: string) {\n return {\n needs: { [fieldName]: true },\n // Prisma's $extends types `compute` as returning `any` in its constraint\n // type (DynamicResultExtensionArgs). Returning a pre-built object with an\n // explicit Id<Brand> return type on `compute` causes TypeScript to infer\n // the brand through the `& R` intersection in $extends — encapsulating\n // the single necessary cast here rather than pushing it to every call site.\n compute: (model: Record<string, unknown>): Id<Brand> =>\n readIdColumn(codec, model[fieldName]),\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,SAAgB,QAA8B,OAAiD;CAC7F,OAAO;EACL,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;EACA,MAAM,OAA0B;GAC9B,OAAO;EACT;EACA,aAAa,WAAmB;GAC9B,OAAO;IACL,OAAO,GAAG,YAAY,KAAK;IAM3B,UAAU,UACR,aAAa,OAAO,MAAM,UAAU;GACxC;EACF;CACF;AACF"}
1
+ {"version":3,"file":"prisma.mjs","names":[],"sources":["../src/adapters/prisma.ts"],"sourcesContent":["import type { ModelQueryOptionsCb, ModelQueryOptionsCbArgs } from \"@prisma/client/runtime/library\";\nimport { readIdColumn, readIdColumnNullable, 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 } from \"../error.js\";\n\nexport type { IdColumnCodec };\n\n/**\n * Extension of {@link IdColumnCodec} that also exposes synchronous `generate()`.\n * Required by {@link idField} so that {@link IdTransform.defaultQuery} can produce\n * IDs at write time. Every full codec variant (Timestamp, Reverse Timestamp) satisfies\n * this; async-generate codecs (Opaque, Signed, Wrapped, Digest) do not and are\n * therefore unsupported by `defaultQuery`.\n */\nexport type IdGeneratingCodec<Brand extends string> = IdColumnCodec<Brand> & {\n generate(): Id<Brand>;\n};\n\n/**\n * The per-model object returned by {@link IdTransform.defaultQuery}, suitable for\n * the model-level value inside a Prisma `$extends({ query: { modelName: … } })` block.\n * Structurally identical to `{ [operation: string]: ModelQueryOptionsCb }` from\n * `@prisma/client/runtime/library`.\n */\nexport type IdQueryField = { [operation: string]: ModelQueryOptionsCb };\n\n/**\n * Typed `$extends` result-component field definition produced by\n * {@link IdTransform.computeField} — a `{ needs, compute }` pair whose `compute`\n * return type is statically `Id<Brand>`, so the extended-client model field is\n * typed correctly without a per-call-site cast.\n */\nexport type IdComputeField<Brand extends string> = {\n needs: Record<string, boolean>;\n compute: (model: Record<string, unknown>) => Id<Brand>;\n};\n\n/**\n * Typed `$extends` result-component field definition produced by\n * {@link IdTransform.computeNullableField} — like {@link IdComputeField} but\n * `compute` returns `Id<Brand> | null` for nullable columns.\n */\nexport type NullableIdComputeField<Brand extends string> = {\n needs: Record<string, boolean>;\n compute: (model: Record<string, unknown>) => Id<Brand> | null;\n};\n\n/**\n * Read/write transform pair and `$extends` result-component factory for\n * integrating `Id<Brand>` with Prisma extensions.\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 read(value: unknown): Id<Brand>;\n /**\n * Nullable read transform: returns `null` when `value` is `null` or `undefined`;\n * otherwise delegates to {@link read}. Use for optional foreign keys.\n */\n readNullable(value: unknown): Id<Brand> | null;\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 * Creates a typed `$extends` result-component field definition that carries\n * `Id<Brand>` through Prisma's type machinery without a per-call-site cast.\n *\n * @param fieldName - The model field to read from (e.g. `\"id\"`).\n * @returns An {@link IdComputeField} whose `compute` return type is statically\n * `Id<Brand>`, so the extended-client model field is typed correctly.\n *\n * @example\n * ```ts\n * const xprisma = prisma.$extends({\n * result: {\n * user: { id: userIdField.computeField(\"id\") },\n * },\n * });\n * // xprisma.user.findUnique(…).id is typed as Id<\"usr\"> — no cast required\n * ```\n */\n computeField(fieldName: string): IdComputeField<Brand>;\n /**\n * Creates a `$extends` query-component model slice that auto-generates\n * `Id<Brand>` values for `create`, `createMany`, and `upsert` operations\n * when the field is absent, `undefined`, or `null` in `args.data` (or\n * `args.create` for upsert). Explicitly supplied values are always passed\n * through unchanged.\n *\n * @param fieldName - The model field to auto-generate (e.g. `\"id\"`).\n * @returns An {@link IdQueryField} suitable for the model-level value inside\n * a Prisma `$extends({ query: { modelName: … } })` block.\n *\n * @example\n * ```ts\n * const xprisma = prisma.$extends({\n * query: { user: userIdField.defaultQuery(\"id\") },\n * result: { user: { id: userIdField.computeField(\"id\") } },\n * });\n * // id is auto-filled on create, and typed as Id<\"usr\"> on read\n * await xprisma.user.create({ data: { name: \"Alice\" } });\n * ```\n */\n defaultQuery(fieldName: string): IdQueryField;\n /**\n * Like {@link computeField} but for nullable columns — `compute` returns\n * `Id<Brand> | null` instead of `Id<Brand>`.\n *\n * @param fieldName - The nullable model field to read from.\n * @returns A {@link NullableIdComputeField} whose `compute` returns `Id<Brand> | null`.\n */\n computeNullableField(fieldName: string): NullableIdComputeField<Brand>;\n};\n\n/**\n * Creates a read/write transform pair for use with Prisma's `$extends` extension model.\n *\n * Requires a codec variant that exposes a synchronous `generate()` in addition to `safeParse` — see {@link IdGeneratingCodec}. Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed, Wrapped, and Digest codecs cannot be passed to `idField()`.\n *\n * Use `computeField(fieldName)` to produce a typed `$extends` result-component\n * field definition — the brand is carried through Prisma's type machinery\n * automatically and no per-call-site cast is required.\n *\n * For codecs that do not expose a synchronous `generate()` (Opaque Timestamp,\n * Signed Timestamp, Wrapped key, Digest), use {@link idFieldReadOnly} instead —\n * it accepts any {@link IdColumnCodec} and omits `defaultQuery`.\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: { id: userIdField.computeField(\"id\") },\n * },\n * });\n * // xprisma.user.findUnique(…).id is typed as Id<\"usr\"> — no cast required\n * ```\n */\nexport function idField<Brand extends string>(codec: IdGeneratingCodec<Brand>): IdTransform<Brand> {\n const { generate } = codec;\n return {\n read(value: unknown): Id<Brand> {\n return readIdColumn(codec, value);\n },\n readNullable(value: unknown): Id<Brand> | null {\n return readIdColumnNullable(codec, value);\n },\n write(value: Id<Brand>): string {\n return value;\n },\n computeField(fieldName: string) {\n return {\n needs: { [fieldName]: true },\n // Prisma's $extends types `compute` as returning `any` in its constraint\n // type (DynamicResultExtensionArgs). Returning a pre-built object with an\n // explicit Id<Brand> return type on `compute` causes TypeScript to infer\n // the brand through the `& R` intersection in $extends — encapsulating\n // the single necessary cast here rather than pushing it to every call site.\n compute: (model: Record<string, unknown>): Id<Brand> =>\n readIdColumn(codec, model[fieldName]),\n };\n },\n defaultQuery(fieldName: string): IdQueryField {\n function injectIfAbsent(data: Record<string, unknown>): Record<string, unknown> {\n if (data[fieldName] == null) {\n return { ...data, [fieldName]: generate() };\n }\n return data;\n }\n\n type QueryArg = Parameters<ModelQueryOptionsCbArgs[\"query\"]>[0];\n\n return {\n async create({ args, query }) {\n const data = args.data as Record<string, unknown> | null | undefined;\n const nextArgs =\n data != null ? ({ ...args, data: injectIfAbsent(data) } as unknown as QueryArg) : args;\n return query(nextArgs);\n },\n async createMany({ args, query }) {\n const data = args.data as Array<Record<string, unknown>> | null | undefined;\n const nextArgs = Array.isArray(data)\n ? ({ ...args, data: data.map((item) => injectIfAbsent(item)) } as unknown as QueryArg)\n : args;\n return query(nextArgs);\n },\n async upsert({ args, query }) {\n const createData = args.create as Record<string, unknown> | null | undefined;\n const nextArgs =\n createData != null\n ? ({ ...args, create: injectIfAbsent(createData) } as unknown as QueryArg)\n : args;\n return query(nextArgs);\n },\n };\n },\n computeNullableField(fieldName: string) {\n return {\n needs: { [fieldName]: true },\n compute: (model: Record<string, unknown>): Id<Brand> | null =>\n readIdColumnNullable(codec, model[fieldName]),\n };\n },\n };\n}\n\n/**\n * Read-only sibling of {@link idField} for codec variants that do not expose a\n * synchronous `generate()` — Opaque Timestamp, Signed Timestamp, Wrapped key,\n * and Digest codecs all qualify.\n *\n * Accepts any {@link IdColumnCodec} (the wider constraint that only requires\n * `safeParse`) and returns the full read/transform surface of {@link IdTransform}\n * **minus `defaultQuery`**. Because `defaultQuery` is the only method that calls\n * `generate()`, callers who only need the read path are not forced to provide a\n * synchronous generator.\n *\n * @example\n * ```ts\n * import { idFieldReadOnly } from \"@smonn/ids/prisma\";\n * import { createOpaqueTimestampId } from \"@smonn/ids/opaque\";\n *\n * const inv = createOpaqueTimestampId(\"inv\", { key });\n * const invoiceIdField = idFieldReadOnly(inv);\n *\n * const xprisma = prisma.$extends({\n * result: {\n * invoice: { id: invoiceIdField.computeField(\"id\") },\n * },\n * });\n * // xprisma.invoice.findUnique(…).id is typed as Id<\"inv\"> — no cast required\n * ```\n */\nexport function idFieldReadOnly<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n): Omit<IdTransform<Brand>, \"defaultQuery\"> {\n return {\n read(value: unknown): Id<Brand> {\n return readIdColumn(codec, value);\n },\n readNullable(value: unknown): Id<Brand> | null {\n return readIdColumnNullable(codec, value);\n },\n write(value: Id<Brand>): string {\n return value;\n },\n computeField(fieldName: string) {\n return {\n needs: { [fieldName]: true },\n compute: (model: Record<string, unknown>): Id<Brand> =>\n readIdColumn(codec, model[fieldName]),\n };\n },\n computeNullableField(fieldName: string) {\n return {\n needs: { [fieldName]: true },\n compute: (model: Record<string, unknown>): Id<Brand> | null =>\n readIdColumnNullable(codec, model[fieldName]),\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyJA,SAAgB,QAA8B,OAAqD;CACjG,MAAM,EAAE,aAAa;CACrB,OAAO;EACL,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;EACA,aAAa,OAAkC;GAC7C,OAAO,qBAAqB,OAAO,KAAK;EAC1C;EACA,MAAM,OAA0B;GAC9B,OAAO;EACT;EACA,aAAa,WAAmB;GAC9B,OAAO;IACL,OAAO,GAAG,YAAY,KAAK;IAM3B,UAAU,UACR,aAAa,OAAO,MAAM,UAAU;GACxC;EACF;EACA,aAAa,WAAiC;GAC5C,SAAS,eAAe,MAAwD;IAC9E,IAAI,KAAK,cAAc,MACrB,OAAO;KAAE,GAAG;MAAO,YAAY,SAAS;IAAE;IAE5C,OAAO;GACT;GAIA,OAAO;IACL,MAAM,OAAO,EAAE,MAAM,SAAS;KAC5B,MAAM,OAAO,KAAK;KAGlB,OAAO,MADL,QAAQ,OAAQ;MAAE,GAAG;MAAM,MAAM,eAAe,IAAI;KAAE,IAA4B,IAC/D;IACvB;IACA,MAAM,WAAW,EAAE,MAAM,SAAS;KAChC,MAAM,OAAO,KAAK;KAIlB,OAAO,MAHU,MAAM,QAAQ,IAAI,IAC9B;MAAE,GAAG;MAAM,MAAM,KAAK,KAAK,SAAS,eAAe,IAAI,CAAC;KAAE,IAC3D,IACiB;IACvB;IACA,MAAM,OAAO,EAAE,MAAM,SAAS;KAC5B,MAAM,aAAa,KAAK;KAKxB,OAAO,MAHL,cAAc,OACT;MAAE,GAAG;MAAM,QAAQ,eAAe,UAAU;KAAE,IAC/C,IACe;IACvB;GACF;EACF;EACA,qBAAqB,WAAmB;GACtC,OAAO;IACL,OAAO,GAAG,YAAY,KAAK;IAC3B,UAAU,UACR,qBAAqB,OAAO,MAAM,UAAU;GAChD;EACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,gBACd,OAC0C;CAC1C,OAAO;EACL,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;EACA,aAAa,OAAkC;GAC7C,OAAO,qBAAqB,OAAO,KAAK;EAC1C;EACA,MAAM,OAA0B;GAC9B,OAAO;EACT;EACA,aAAa,WAAmB;GAC9B,OAAO;IACL,OAAO,GAAG,YAAY,KAAK;IAC3B,UAAU,UACR,aAAa,OAAO,MAAM,UAAU;GACxC;EACF;EACA,qBAAqB,WAAmB;GACtC,OAAO;IACL,OAAO,GAAG,YAAY,KAAK;IAC3B,UAAU,UACR,qBAAqB,OAAO,MAAM,UAAU;GAChD;EACF;CACF;AACF"}
@@ -36,6 +36,30 @@ import { ValueTransformer } from "typeorm";
36
36
  * ```
37
37
  */
38
38
  declare function idTransformer<Brand extends string>(codec: IdColumnCodec<Brand>): ValueTransformer;
39
+ /**
40
+ * TypeORM column transformer for a **nullable** `Id<Brand>` column.
41
+ *
42
+ * Behaves like {@link idTransformer} but `from` returns `null` for `null` /
43
+ * `undefined` database values and `to` passes `null` / `undefined` through
44
+ * unchanged. Use for optional foreign keys.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { nullableIdTransformer } from "@smonn/ids/typeorm";
49
+ * import { createTimestampId } from "@smonn/ids";
50
+ * import type { Id } from "@smonn/ids";
51
+ * import { Column, Entity } from "typeorm";
52
+ *
53
+ * const usr = createTimestampId("usr");
54
+ *
55
+ * @Entity()
56
+ * class Post {
57
+ * @Column({ type: "text", nullable: true, transformer: nullableIdTransformer(usr) })
58
+ * authorId!: Id<"usr"> | null;
59
+ * }
60
+ * ```
61
+ */
62
+ declare function nullableIdTransformer<Brand extends string>(codec: IdColumnCodec<Brand>): ValueTransformer;
39
63
  //#endregion
40
- export { type IdColumnCodec, IdsError, type IdsErrorCode, idTransformer, isIdsError };
64
+ export { type IdColumnCodec, IdsError, type IdsErrorCode, idTransformer, isIdsError, nullableIdTransformer };
41
65
  //# sourceMappingURL=typeorm.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"typeorm.d.mts","names":[],"sources":["../src/adapters/typeorm.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;AAAkF;;;;;;;;;;;;;;;;;;AAAlF,iBAAgB,aAAA,uBAAoC,KAAA,EAAO,aAAA,CAAc,KAAA,IAAS,gBAAA"}
1
+ {"version":3,"file":"typeorm.d.mts","names":[],"sources":["../src/adapters/typeorm.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;AAAkF;AAkClF;;;;;;;;;;;;;;AAEG;;;AApCH,iBAAgB,aAAA,uBAAoC,KAAA,EAAO,aAAA,CAAc,KAAA,IAAS,gBAAA;;;;;;;;;;;;;;;;;;;;;;;;iBAkClE,qBAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,IACpB,gBAAA"}
package/dist/typeorm.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
2
- import { t as readIdColumn } from "./adapter-types-7wWdELSh.mjs";
2
+ import { n as readIdColumnNullable, t as readIdColumn } from "./adapter-types-CjzFNDcJ.mjs";
3
3
  //#region src/adapters/typeorm.ts
4
4
  /**
5
5
  * TypeORM column transformer for `Id<Brand>`.
@@ -43,7 +43,40 @@ function idTransformer(codec) {
43
43
  }
44
44
  };
45
45
  }
46
+ /**
47
+ * TypeORM column transformer for a **nullable** `Id<Brand>` column.
48
+ *
49
+ * Behaves like {@link idTransformer} but `from` returns `null` for `null` /
50
+ * `undefined` database values and `to` passes `null` / `undefined` through
51
+ * unchanged. Use for optional foreign keys.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { nullableIdTransformer } from "@smonn/ids/typeorm";
56
+ * import { createTimestampId } from "@smonn/ids";
57
+ * import type { Id } from "@smonn/ids";
58
+ * import { Column, Entity } from "typeorm";
59
+ *
60
+ * const usr = createTimestampId("usr");
61
+ *
62
+ * @Entity()
63
+ * class Post {
64
+ * @Column({ type: "text", nullable: true, transformer: nullableIdTransformer(usr) })
65
+ * authorId!: Id<"usr"> | null;
66
+ * }
67
+ * ```
68
+ */
69
+ function nullableIdTransformer(codec) {
70
+ return {
71
+ to(value) {
72
+ return value;
73
+ },
74
+ from(value) {
75
+ return readIdColumnNullable(codec, value);
76
+ }
77
+ };
78
+ }
46
79
  //#endregion
47
- export { IdsError, idTransformer, isIdsError };
80
+ export { IdsError, idTransformer, isIdsError, nullableIdTransformer };
48
81
 
49
82
  //# sourceMappingURL=typeorm.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"typeorm.mjs","names":[],"sources":["../src/adapters/typeorm.ts"],"sourcesContent":["import type { ValueTransformer } from \"typeorm\";\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 } from \"../error.js\";\n\nexport type { IdColumnCodec };\n\n/**\n * TypeORM column transformer for `Id<Brand>`.\n *\n * Returns a `ValueTransformer` object suitable for use in a TypeORM `@Column`\n * decorator's `transformer` option.\n *\n * **Write path (`to`):** passes the `Id<Brand>` directly to the database — it is\n * already the canonical string form.\n *\n * **Read path (`from`):** normalises the raw database value via `codec.safeParse()`.\n * Throws `IdsError` with code `\"invalid_id\"` if the value does not parse as a valid\n * `Id<Brand>`.\n *\n * **TypeORM branding caveat:** TypeORM cannot brand a generated entity field type at\n * the schema level. Annotate the entity field explicitly: `id!: Id<\"usr\">`.\n *\n * @example\n * ```ts\n * import { idTransformer } from \"@smonn/ids/typeorm\";\n * import { createTimestampId } from \"@smonn/ids\";\n * import type { Id } from \"@smonn/ids\";\n * import { Column, Entity } from \"typeorm\";\n *\n * const usr = createTimestampId(\"usr\");\n *\n * @Entity()\n * class User {\n * @Column({ type: \"text\", transformer: idTransformer(usr) })\n * id!: Id<\"usr\">;\n * }\n * ```\n */\nexport function idTransformer<Brand extends string>(codec: IdColumnCodec<Brand>): ValueTransformer {\n return {\n to(value: Id<Brand>): string {\n return value;\n },\n from(value: unknown): Id<Brand> {\n return readIdColumn(codec, value);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,cAAoC,OAA+C;CACjG,OAAO;EACL,GAAG,OAA0B;GAC3B,OAAO;EACT;EACA,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;CACF;AACF"}
1
+ {"version":3,"file":"typeorm.mjs","names":[],"sources":["../src/adapters/typeorm.ts"],"sourcesContent":["import type { ValueTransformer } from \"typeorm\";\nimport { readIdColumn, readIdColumnNullable, 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 } from \"../error.js\";\n\nexport type { IdColumnCodec };\n\n/**\n * TypeORM column transformer for `Id<Brand>`.\n *\n * Returns a `ValueTransformer` object suitable for use in a TypeORM `@Column`\n * decorator's `transformer` option.\n *\n * **Write path (`to`):** passes the `Id<Brand>` directly to the database — it is\n * already the canonical string form.\n *\n * **Read path (`from`):** normalises the raw database value via `codec.safeParse()`.\n * Throws `IdsError` with code `\"invalid_id\"` if the value does not parse as a valid\n * `Id<Brand>`.\n *\n * **TypeORM branding caveat:** TypeORM cannot brand a generated entity field type at\n * the schema level. Annotate the entity field explicitly: `id!: Id<\"usr\">`.\n *\n * @example\n * ```ts\n * import { idTransformer } from \"@smonn/ids/typeorm\";\n * import { createTimestampId } from \"@smonn/ids\";\n * import type { Id } from \"@smonn/ids\";\n * import { Column, Entity } from \"typeorm\";\n *\n * const usr = createTimestampId(\"usr\");\n *\n * @Entity()\n * class User {\n * @Column({ type: \"text\", transformer: idTransformer(usr) })\n * id!: Id<\"usr\">;\n * }\n * ```\n */\nexport function idTransformer<Brand extends string>(codec: IdColumnCodec<Brand>): ValueTransformer {\n return {\n to(value: Id<Brand>): string {\n return value;\n },\n from(value: unknown): Id<Brand> {\n return readIdColumn(codec, value);\n },\n };\n}\n\n/**\n * TypeORM column transformer for a **nullable** `Id<Brand>` column.\n *\n * Behaves like {@link idTransformer} but `from` returns `null` for `null` /\n * `undefined` database values and `to` passes `null` / `undefined` through\n * unchanged. Use for optional foreign keys.\n *\n * @example\n * ```ts\n * import { nullableIdTransformer } from \"@smonn/ids/typeorm\";\n * import { createTimestampId } from \"@smonn/ids\";\n * import type { Id } from \"@smonn/ids\";\n * import { Column, Entity } from \"typeorm\";\n *\n * const usr = createTimestampId(\"usr\");\n *\n * @Entity()\n * class Post {\n * @Column({ type: \"text\", nullable: true, transformer: nullableIdTransformer(usr) })\n * authorId!: Id<\"usr\"> | null;\n * }\n * ```\n */\nexport function nullableIdTransformer<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n): ValueTransformer {\n return {\n to(value: Id<Brand> | null | undefined): string | null | undefined {\n return value;\n },\n from(value: unknown): Id<Brand> | null {\n return readIdColumnNullable(codec, value);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,cAAoC,OAA+C;CACjG,OAAO;EACL,GAAG,OAA0B;GAC3B,OAAO;EACT;EACA,KAAK,OAA2B;GAC9B,OAAO,aAAa,OAAO,KAAK;EAClC;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,sBACd,OACkB;CAClB,OAAO;EACL,GAAG,OAAgE;GACjE,OAAO;EACT;EACA,KAAK,OAAkC;GACrC,OAAO,qBAAqB,OAAO,KAAK;EAC1C;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smonn/ids",
3
- "version": "0.15.0",
3
+ "version": "1.0.0-rc.0",
4
4
  "license": "MIT",
5
5
  "author": "Simon Ingeson (https://github.com/smonn)",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter-types-7wWdELSh.mjs","names":[],"sources":["../src/adapters/adapter-types.ts"],"sourcesContent":["import { IdsError } from \"../error.js\";\nimport type { Id, ParseError, ParseResult } from \"../types.js\";\n\n/** Discriminated failure value passed to `onError` and emitted to the framework's error handler. */\nexport type IdParamFailure =\n | { readonly reason: \"brand_mismatch\"; readonly status: number }\n | { readonly reason: \"malformed\"; readonly status: number };\n\n/** Minimum structural type required by web and ORM adapters. Any codec variant satisfies this — all expose `safeParse`. Adapters only ever call `safeParse` — never key-dependent methods like `extractTimestamp`, `wrap`, or `unwrap`. */\nexport type IdCodec<Brand extends string> = {\n safeParse(value: unknown): ParseResult<Brand>;\n};\n\n/** Re-exported from ORM adapter subpaths (`@smonn/ids/drizzle`, `@smonn/ids/prisma`, `@smonn/ids/kysely`) under the public name; structurally identical to {@link IdCodec}. */\nexport type IdColumnCodec<Brand extends string> = IdCodec<Brand>;\n\n/** Parses `value` as `Id<Brand>` via `codec.safeParse`; throws `IdsError(\"invalid_id\")` on failure. Shared read helper for ORM adapters. */\nexport function readIdColumn<Brand extends string>(\n codec: IdCodec<Brand>,\n value: unknown,\n): Id<Brand> {\n const result = codec.safeParse(value);\n if (!result.ok) {\n throw new IdsError(\"invalid_id\", `invalid ID from database: ${result.error}`, {\n cause: result.error,\n });\n }\n return result.id;\n}\n\n/**\n * Maps a `ParseError` to `{ reason, status }` for web adapter failure handling.\n *\n * - `invalid_prefix` → `brand_mismatch` / default 404\n * - anything else → `malformed` / default 400\n * - `options.status[reason]` overrides the default for that reason\n */\nexport function resolveIdParamFailure(\n error: ParseError,\n options?: { status?: { brand_mismatch?: number; malformed?: number } },\n): IdParamFailure {\n const reason = error === \"invalid_prefix\" ? (\"brand_mismatch\" as const) : (\"malformed\" as const);\n const defaultStatus = reason === \"brand_mismatch\" ? 404 : 400;\n const status = options?.status?.[reason] ?? defaultStatus;\n return { reason, status };\n}\n"],"mappings":";;;AAiBA,SAAgB,aACd,OACA,OACW;CACX,MAAM,SAAS,MAAM,UAAU,KAAK;CACpC,IAAI,CAAC,OAAO,IACV,MAAM,IAAI,SAAS,cAAc,6BAA6B,OAAO,SAAS,EAC5E,OAAO,OAAO,MAChB,CAAC;CAEH,OAAO,OAAO;AAChB;;;;;;;;AASA,SAAgB,sBACd,OACA,SACgB;CAChB,MAAM,SAAS,UAAU,mBAAoB,mBAA8B;CAC3E,MAAM,gBAAgB,WAAW,mBAAmB,MAAM;CAE1D,OAAO;EAAE;EAAQ,QADF,SAAS,SAAS,WAAW;CACpB;AAC1B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"opaque-COAcIIY4.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, LayoutOps, Prefix } from \"../../types.js\";\nimport { decryptPayload, encryptPayload } from \"../_kernel/crypto.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\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 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): LayoutOps<Brand> & {\n generateAt(ms: number): Promise<Id<Brand>>;\n extractTimestamp(id: Id<Brand>): Promise<Date>;\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: (_ms?: number): 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 * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.\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 * @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 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 * @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 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 {\n Id,\n JsonSchema,\n ParseResult,\n Prefix,\n StandardSchemaProps,\n ValidBrand,\n} 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 *\n * @remarks\n * **Security properties (unauthenticated, deterministic, and malleable by design):**\n *\n * - The payload is AES-CBC encrypted but **unauthenticated** — there is no\n * integrity tag. A tampered or wrong-key payload decrypts to garbage bytes\n * without throwing.\n * - Opaque IDs must be treated as **opaque handles**, not as trusted or\n * authenticated tokens.\n * - `extractTimestamp` is best-effort on untrusted input: a wrong or tampered\n * key returns a plausible-looking `Date` without error, not a verification\n * failure. Do not treat the returned timestamp as proof of origin.\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 /**\n * JSON Schema for the canonical wire form. The `pattern` matches the canonical stored\n * form only and is deliberately stricter than `parse()`/`safeParse()`, which accept\n * uppercase letters and Crockford aliases (`o`/`i`/`l`) before normalising. See ADR-0003.\n * The `example` is a structural placeholder (generated at construction time).\n */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n /**\n * Converts a trusted `Id<Brand>` to an RFC 9562 canonical (lowercase, hyphenated)\n * UUID string by reinterpreting the 16-byte payload verbatim. The payload is the\n * encrypted ciphertext — `toUUID` does not decrypt it. Total — cannot fail.\n * Returns a plain `string` (brand is shed). See ADR-0024.\n */\n toUUID(id: Id<Brand>): string;\n /**\n * Parses a UUID string into an `Id<Brand>`. Accepts case-insensitive `8-4-4-4-12`\n * hyphenated form only. Throws `IdsError` with `code: \"invalid_id\"` on bad input.\n * See ADR-0024.\n */\n fromUUID(value: string): Id<Brand>;\n /**\n * Non-throwing UUID parse. Returns `{ ok: true, id }` or\n * `{ ok: false, error: \"not_string\" | \"invalid_uuid\" }`. See ADR-0024.\n */\n safeFromUUID(value: unknown): ParseResult<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 & ValidBrand<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 toUUID: wire.toUUID,\n fromUUID: wire.fromUUID,\n safeFromUUID: wire.safeFromUUID,\n };\n}\n"],"mappings":";;;;AAWA,SAAS,eAAe,IAAY,KAA+C;CACjF,MAAM,4BAAY,IAAI,WAAA,EAA4B;CAClD,eAAe,IAAI,SAAS;CAC5B,IAAI,UAAU,SAAA,GAAA,EAA+C,CAAC;CAC9D,OAAO;AACT;AAEA,eAAe,uBACb,QACA,KACA,IACe;CACf,MAAM,YAAY,MAAM,eAAe,KAAK,mBAAmB,QAAQ,EAAE,CAAC;CAC1E,OAAO,IAAI,KAAK,gBAAgB,SAAS,CAAC;AAC5C;;;AAIA,eAAe,eACb,QACA,KACA,KACA,IACoB;CAGpB,OAAO,SAAS,QAAQ,MADA,eAAe,KADrB,eAAe,IAAI,GACe,CAAC,CACpB;AACnC;;AAGA,SAAS,cAAoC,QAA+B;CAC1E,OAAO,SAAS,IAAI,OAAO,mBAAmB;AAChD;;AAGA,SAAgB,sBACd,QACA,KACA,KAIA;CACA,OAAO;EACL,aAAa,OAAmC,eAAe,QAAQ,KAAK,KAAK,EAAE;EACnF,mBAAmB,OAAiC,uBAAuB,QAAQ,KAAK,EAAE;EAC1F,gBAAgB,QAA4B,cAAc,MAAM;CAClE;AACF;;;ACjCA,MAAM,qCAAqB,IAAI,QAAwC;;;;;;;;;;;;AAavE,eAAsB,gBAAgB,OAAuC;CAC3E,iCAAiC,MAAM,QAAQ,KAAK;CACpD,MAAM,YAAY,MAAM,OAAO,OAAO,UACpC,OACA,OACA,WACA,OACA,CAAC,WAAW,SAAS,CACvB;CACA,MAAM,MAAM,OAAO,OAAO,CAAC,CAAC;CAC5B,mBAAmB,IAAI,KAAK,SAAS;CACrC,OAAO;AACT;AAEA,SAAgB,sBAAsB,KAAqC;CACzE,MAAM,YAAY,mBAAmB,IAAI,GAAG;CAC5C,IAAI,cAAc,KAAA,GAChB,MAAM,IAAI,MAAM,oBAAoB;CAEtC,OAAO;AACT;;;;;;;;;AAUA,SAAgB,gBAAgB,OAAmB,QAAiC;CAClF,OAAO,kBAAkB,OAAO,QAAQ,UAAU,KAAK;AACzD;;;;;;;;;;AAWA,SAAgB,gBAAgB,SAAiB,QAAqC;CACpF,OAAO,kBAAkB,SAAS,QAAQ,UAAU,KAAK;AAC3D;;;;;;;;;;AC4CA,SAAgB,wBACd,OACA,MAC6B;CAC7B,cAAc,KAAK;CACnB,cAAc,OAAO,KAAK,mBAAmB;CAE7C,MAAM,YAAY,sBAAsB,KAAK,GAAG;CAChD,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,MAAM,MAAM,KAAK,OAAO;CACxB,MAAM,SAAwB,GAAG,MAAM;CACvC,MAAM,OAAO,YAAY,MAAM;CAC/B,MAAM,SAAS,sBAAsB,QAAQ,WAAW,GAAG;CAE3D,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,oBAAoB,KAAK,aAAa,OAAO,OAAO,cAAc,CAAC;EACnE,aAAa,KAAK;EAClB,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,cAAc,KAAK;CACrB;AACF"}