@smonn/ids 0.14.1 → 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.
- package/README.md +3 -3
- package/dist/{adapter-types-7wWdELSh.mjs → adapter-types-CjzFNDcJ.mjs} +7 -2
- package/dist/adapter-types-CjzFNDcJ.mjs.map +1 -0
- package/dist/cli.mjs +30 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/drizzle.d.mts +87 -3
- package/dist/drizzle.d.mts.map +1 -1
- package/dist/drizzle.mjs +115 -5
- package/dist/drizzle.mjs.map +1 -1
- package/dist/express.d.mts +44 -2
- package/dist/express.d.mts.map +1 -1
- package/dist/express.mjs +60 -2
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.d.mts +49 -2
- package/dist/fastify.d.mts.map +1 -1
- package/dist/fastify.mjs +61 -2
- package/dist/fastify.mjs.map +1 -1
- package/dist/graphql.d.mts +2 -1
- package/dist/graphql.d.mts.map +1 -1
- package/dist/graphql.mjs +2 -1
- package/dist/graphql.mjs.map +1 -1
- package/dist/hono.d.mts +44 -2
- package/dist/hono.d.mts.map +1 -1
- package/dist/hono.mjs +54 -2
- package/dist/hono.mjs.map +1 -1
- package/dist/kysely.d.mts +73 -2
- package/dist/kysely.d.mts.map +1 -1
- package/dist/kysely.mjs +84 -2
- package/dist/kysely.mjs.map +1 -1
- package/dist/mikro-orm.d.mts +42 -3
- package/dist/mikro-orm.d.mts.map +1 -1
- package/dist/mikro-orm.mjs +54 -4
- package/dist/mikro-orm.mjs.map +1 -1
- package/dist/nestjs.mjs +1 -1
- package/dist/{opaque-COAcIIY4.mjs → opaque-Dle3CmSE.mjs} +18 -10
- package/dist/opaque-Dle3CmSE.mjs.map +1 -0
- package/dist/opaque.d.mts +16 -10
- package/dist/opaque.d.mts.map +1 -1
- package/dist/opaque.mjs +1 -1
- package/dist/prisma.d.mts +112 -9
- package/dist/prisma.d.mts.map +1 -1
- package/dist/prisma.mjs +101 -3
- package/dist/prisma.mjs.map +1 -1
- package/dist/typeorm.d.mts +25 -1
- package/dist/typeorm.d.mts.map +1 -1
- package/dist/typeorm.mjs +35 -2
- package/dist/typeorm.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/adapter-types-7wWdELSh.mjs.map +0 -1
- package/dist/opaque-COAcIIY4.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-BRZkuQeP.mjs";
|
|
2
2
|
import { a as writeTimestamp, r as readTimestampMs, t as defaultRng } from "./rng-6GyNT4zS.mjs";
|
|
3
|
-
import { a as decryptPayload, i as encodeKeyMaterial, r as decodeKeyMaterial, s as encryptPayload, t as assertValidKeyMaterialByteLength } from "./key-material-1wOKJ1o-.mjs";
|
|
3
|
+
import { a as decryptPayload, i as encodeKeyMaterial, o as deriveKey, r as decodeKeyMaterial, s as encryptPayload, t as assertValidKeyMaterialByteLength } from "./key-material-1wOKJ1o-.mjs";
|
|
4
4
|
//#region src/codecs/opaque/layout.ts
|
|
5
5
|
function buildPlaintext(ms, rng) {
|
|
6
6
|
const plaintext = /* @__PURE__ */ new Uint8Array(16);
|
|
@@ -31,21 +31,29 @@ function createOpaqueLayoutOps(prefix, key, rng) {
|
|
|
31
31
|
}
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/codecs/opaque/key.ts
|
|
34
|
+
const aesInfo = new TextEncoder().encode("@smonn/ids/opaque/aes");
|
|
34
35
|
const opaqueKeyInternals = /* @__PURE__ */ new WeakMap();
|
|
35
36
|
/**
|
|
36
|
-
* Imports
|
|
37
|
+
* Imports operator key material into an {@link OpaqueKey} handle for the Opaque
|
|
37
38
|
* Timestamp codec.
|
|
38
39
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* The bytes are HKDF **input keying material**, not the AES key itself: the
|
|
41
|
+
* codec derives an **AES-256** key from them via HKDF under the label
|
|
42
|
+
* `@smonn/ids/opaque/aes` (ADR-0027). Accepts 16, 24, or 32 bytes; the input
|
|
43
|
+
* size sets the entropy floor only — a 16-byte handle still yields AES-256 with
|
|
44
|
+
* a 128-bit entropy floor. To store or transport key material, use
|
|
45
|
+
* {@link encodeOpaqueKey} / {@link decodeOpaqueKey} (`"hex"` or `"base64url"` —
|
|
46
|
+
* not Crockford base32).
|
|
42
47
|
*
|
|
43
|
-
* @param bytes - 16, 24, or 32 raw key
|
|
48
|
+
* @param bytes - 16, 24, or 32 bytes of raw key material.
|
|
44
49
|
* @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.
|
|
45
50
|
*/
|
|
46
51
|
async function importOpaqueKey(bytes) {
|
|
47
52
|
assertValidKeyMaterialByteLength(bytes.length, "AES");
|
|
48
|
-
const cryptoKey = await
|
|
53
|
+
const cryptoKey = await deriveKey(bytes, aesInfo, {
|
|
54
|
+
name: "AES-CBC",
|
|
55
|
+
length: 256
|
|
56
|
+
}, ["encrypt", "decrypt"]);
|
|
49
57
|
const key = Object.freeze({});
|
|
50
58
|
opaqueKeyInternals.set(key, cryptoKey);
|
|
51
59
|
return key;
|
|
@@ -56,9 +64,9 @@ function getOpaqueKeyCryptoKey(key) {
|
|
|
56
64
|
return cryptoKey;
|
|
57
65
|
}
|
|
58
66
|
/**
|
|
59
|
-
* Encodes raw
|
|
67
|
+
* Encodes raw Opaque key material bytes for storage in env vars or secret managers.
|
|
60
68
|
*
|
|
61
|
-
* @param bytes - 16, 24, or 32 raw key bytes
|
|
69
|
+
* @param bytes - 16, 24, or 32 raw Opaque key material bytes.
|
|
62
70
|
* @param format - `hex` (lowercase) or `base64url`.
|
|
63
71
|
* @throws {IdsError} `invalid_key_format` if `format` is not `"hex"` or `"base64url"`.
|
|
64
72
|
* @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.
|
|
@@ -113,4 +121,4 @@ function createOpaqueTimestampId(brand, opts) {
|
|
|
113
121
|
//#endregion
|
|
114
122
|
export { importOpaqueKey as i, decodeOpaqueKey as n, encodeOpaqueKey as r, createOpaqueTimestampId as t };
|
|
115
123
|
|
|
116
|
-
//# sourceMappingURL=opaque-
|
|
124
|
+
//# sourceMappingURL=opaque-Dle3CmSE.mjs.map
|
|
@@ -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
|
|
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
|
-
*
|
|
16
|
-
*
|
|
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
|
|
24
|
+
* Imports operator key material into an {@link OpaqueKey} handle for the Opaque
|
|
23
25
|
* Timestamp codec.
|
|
24
26
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/dist/opaque.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/codecs/opaque/key.ts","../src/codecs/opaque/index.ts"],"mappings":";;;;;
|
|
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-
|
|
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,48 @@
|
|
|
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
|
+
/**
|
|
27
|
+
* Typed `$extends` result-component field definition produced by
|
|
28
|
+
* {@link IdTransform.computeField} — a `{ needs, compute }` pair whose `compute`
|
|
29
|
+
* return type is statically `Id<Brand>`, so the extended-client model field is
|
|
30
|
+
* typed correctly without a per-call-site cast.
|
|
31
|
+
*/
|
|
32
|
+
type IdComputeField<Brand extends string> = {
|
|
33
|
+
needs: Record<string, boolean>;
|
|
34
|
+
compute: (model: Record<string, unknown>) => Id<Brand>;
|
|
35
|
+
};
|
|
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
|
+
/**
|
|
7
46
|
* Read/write transform pair and `$extends` result-component factory for
|
|
8
47
|
* integrating `Id<Brand>` with Prisma extensions.
|
|
9
48
|
*/
|
|
@@ -15,6 +54,11 @@ type IdTransform<Brand extends string> = {
|
|
|
15
54
|
*/
|
|
16
55
|
read(value: unknown): Id<Brand>;
|
|
17
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
|
+
/**
|
|
18
62
|
* Write transform: passes `Id<Brand>` through as its canonical string form.
|
|
19
63
|
* `Id<Brand>` is already the canonical string, so this is an identity function
|
|
20
64
|
* at runtime.
|
|
@@ -27,8 +71,8 @@ type IdTransform<Brand extends string> = {
|
|
|
27
71
|
* `Id<Brand>` through Prisma's type machinery without a per-call-site cast.
|
|
28
72
|
*
|
|
29
73
|
* @param fieldName - The model field to read from (e.g. `"id"`).
|
|
30
|
-
* @returns
|
|
31
|
-
*
|
|
74
|
+
* @returns An {@link IdComputeField} whose `compute` return type is statically
|
|
75
|
+
* `Id<Brand>`, so the extended-client model field is typed correctly.
|
|
32
76
|
*
|
|
33
77
|
* @example
|
|
34
78
|
* ```ts
|
|
@@ -40,20 +84,51 @@ type IdTransform<Brand extends string> = {
|
|
|
40
84
|
* // xprisma.user.findUnique(…).id is typed as Id<"usr"> — no cast required
|
|
41
85
|
* ```
|
|
42
86
|
*/
|
|
43
|
-
computeField(fieldName: string):
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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>;
|
|
47
118
|
};
|
|
48
119
|
/**
|
|
49
120
|
* Creates a read/write transform pair for use with Prisma's `$extends` extension model.
|
|
50
121
|
*
|
|
51
|
-
*
|
|
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()`.
|
|
52
123
|
*
|
|
53
124
|
* Use `computeField(fieldName)` to produce a typed `$extends` result-component
|
|
54
125
|
* field definition — the brand is carried through Prisma's type machinery
|
|
55
126
|
* automatically and no per-call-site cast is required.
|
|
56
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
|
+
*
|
|
57
132
|
* @example
|
|
58
133
|
* ```ts
|
|
59
134
|
* import { idField } from "@smonn/ids/prisma";
|
|
@@ -70,7 +145,35 @@ type IdTransform<Brand extends string> = {
|
|
|
70
145
|
* // xprisma.user.findUnique(…).id is typed as Id<"usr"> — no cast required
|
|
71
146
|
* ```
|
|
72
147
|
*/
|
|
73
|
-
declare function idField<Brand extends string>(codec:
|
|
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">;
|
|
74
177
|
//#endregion
|
|
75
|
-
export { type IdColumnCodec, IdTransform, IdsError, type IdsErrorCode, idField, isIdsError };
|
|
178
|
+
export { type IdColumnCodec, IdComputeField, IdGeneratingCodec, IdQueryField, IdTransform, IdsError, type IdsErrorCode, NullableIdComputeField, idField, idFieldReadOnly, isIdsError };
|
|
76
179
|
//# sourceMappingURL=prisma.d.mts.map
|
package/dist/prisma.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.d.mts","names":[],"sources":["../src/adapters/prisma.ts"],"mappings":"
|
|
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-
|
|
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
|
-
*
|
|
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
|
package/dist/prisma.mjs.map
CHANGED
|
@@ -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 * 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 A `{ needs, compute }` object whose `compute` return type is\n * statically `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): {\n needs: Record<string, boolean>;\n compute: (model: Record<string, unknown>) => Id<Brand>;\n };\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,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"}
|
package/dist/typeorm.d.mts
CHANGED
|
@@ -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
|
package/dist/typeorm.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeorm.d.mts","names":[],"sources":["../src/adapters/typeorm.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;AAAkF
|
|
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-
|
|
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
|
package/dist/typeorm.mjs.map
CHANGED
|
@@ -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"}
|