@prisma-next/mongo-codec 0.5.0-dev.28 → 0.5.0-dev.29

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 CHANGED
@@ -5,7 +5,7 @@ Codec interface and registry for MongoDB value serialization.
5
5
  ## Responsibilities
6
6
 
7
7
  - **Codec interface**: `MongoCodec<Id, TTraits, TWire, TInput>` — declares how a JS value translates to and from the BSON-shaped wire format the Mongo driver exchanges, plus the JSON-safe form stored in contract artifacts. Carries trait annotations (`equality`, `order`, `boolean`, `numeric`, `textual`, `vector`) for operator gating. Same four generics as the framework `Codec` base.
8
- - **Codec factory**: `mongoCodec()` — creates frozen codec instances from a config object. `encode` is optional (identity default when omitted); `encode`/`decode` may be authored as sync or async functions and are lifted to Promise-returning query-time methods automatically. Build-time methods (`encodeJson`, `decodeJson`) are synchronous and default to identity when omitted.
8
+ - **Codec factory**: `mongoCodec()` — creates frozen codec instances from a config object. Both `encode` and `decode` are required so `TInput` and `TWire` are always covered by an explicit author function — the factory installs no identity fallback. `encode` and `decode` may be authored as sync or async functions and are lifted to Promise-returning query-time methods automatically. Build-time methods (`encodeJson`, `decodeJson`) are synchronous and default to identity when omitted.
9
9
  - **Codec registry**: `MongoCodecRegistry` and `createMongoCodecRegistry()` — a map-based container that stores and retrieves codecs by ID, with duplicate-ID protection
10
10
  - **Type-level helpers**: `MongoCodecInput<T>` and `MongoCodecTraits<T>` for extracting the input JS type and traits from codec types
11
11
 
@@ -33,7 +33,29 @@ const secretCodec = mongoCodec({
33
33
  });
34
34
  ```
35
35
 
36
- See [ADR 204 Single-Path Async Codec Runtime](../../../../docs/architecture%20docs/adrs/ADR%20204%20-%20Single-Path%20Async%20Codec%20Runtime.md) for the codec runtime's async boundary contract.
36
+ ### Codec call context (`ctx`)
37
+
38
+ Codecs receive a second `ctx` options argument; you may ignore it. The Mongo runtime allocates one `CodecCallContext` per `mongoRuntime.execute(plan, { signal })` call and threads the same reference to every codec dispatch site as a non-optional argument — when no `signal` is supplied the runtime still threads an empty `{}`, never `undefined`. Mongo uses the framework `CodecCallContext` directly (signal-only); column metadata is SQL-family-specific and isn't part of Mongo's per-call shape today. The internal `MongoCodec` interface declares the parameter as required (`encode(value, ctx: CodecCallContext)` / `decode(wire, ctx: CodecCallContext)`); single-arg author functions `(value) => …` continue to compile via TypeScript's bivariance for trailing parameters, so codec ergonomics are unchanged. The `signal` field on the ctx may be `undefined` when the caller didn't supply one.
39
+
40
+ ```ts
41
+ // Forward ctx.signal to a network SDK so aborted queries stop the round-trip.
42
+ const kmsSecretCodec = mongoCodec({
43
+ typeId: 'mongo/kms-secret@1',
44
+ targetTypes: ['string'],
45
+ encode: async (v: string, ctx) =>
46
+ kms.encrypt({ plaintext: v }, { signal: ctx?.signal }),
47
+ decode: async (w: string, ctx) =>
48
+ kms.decrypt({ ciphertext: w }, { signal: ctx?.signal }),
49
+ encodeJson: (v: string) => v,
50
+ decodeJson: (j: string) => j,
51
+ });
52
+ ```
53
+
54
+ > **Note.** Mongo's read path doesn't go through `codec.decode` (per ADR 204 cross-family scope notes), so the `decode` signature above accepts `ctx` for parity with the codec interface but the runtime doesn't currently invoke `decode` on the Mongo read side. Encode-side `ctx.signal` is observed at every recursion level of `resolveValue` so a mid-encode abort surfaces as `RUNTIME.ABORTED { phase: 'encode' }`.
55
+
56
+ Codec bodies that ignore `ctx.signal` complete in the background (cooperative cancellation); aborts still surface to the caller as `RUNTIME.ABORTED`.
57
+
58
+ See [ADR 204 — Single-Path Async Codec Runtime](../../../../docs/architecture%20docs/adrs/ADR%20204%20-%20Single-Path%20Async%20Codec%20Runtime.md) for the codec runtime's async boundary contract, and [ADR 207 — Codec call context: per-query `AbortSignal` and column metadata](../../../../docs/architecture%20docs/adrs/ADR%20207%20-%20Codec%20call%20context%20per-query%20AbortSignal%20and%20column%20metadata.md) for the per-call context shape.
37
59
 
38
60
  ## Dependencies
39
61
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { JsonValue } from "@prisma-next/contract/types";
2
- import { Codec, CodecTrait } from "@prisma-next/framework-components/codec";
2
+ import { Codec, CodecCallContext, CodecTrait } from "@prisma-next/framework-components/codec";
3
3
 
4
4
  //#region src/codecs.d.ts
5
5
  type MongoCodecTrait = CodecTrait;
@@ -34,20 +34,21 @@ type JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue] ? {
34
34
  * Author `encode` and `decode` as sync or async functions; the factory
35
35
  * produces a {@link MongoCodec} whose query-time methods follow the
36
36
  * boundary contract documented on the framework {@link BaseCodec}.
37
+ * Authors receive a second `ctx` options argument carrying the per-call
38
+ * context; ignore it if you don't need it.
37
39
  *
38
- * `encode` is optional when omitted, an identity default is installed
39
- * (declaring "the input value already is the wire value", so `TInput` and
40
- * `TWire` are interchangeable for that codec). `decode` is always
41
- * required. `encodeJson` and `decodeJson` default to identity **only when
42
- * `TInput` is assignable to `JsonValue`**; otherwise both are required so
43
- * the contract artifact stays JSON-safe.
40
+ * Both `encode` and `decode` are required so `TInput` and `TWire` are
41
+ * always covered by an explicit author function the factory installs
42
+ * no identity fallback. `encodeJson` and `decodeJson` default to identity
43
+ * **only when `TInput` is assignable to `JsonValue`**; otherwise both are
44
+ * required so the contract artifact stays JSON-safe.
44
45
  */
45
46
  declare function mongoCodec<Id extends string, const TTraits$1 extends readonly MongoCodecTrait[] = readonly [], TWire = unknown, TInput = unknown>(config: {
46
47
  typeId: Id;
47
48
  targetTypes: readonly string[];
48
49
  traits?: TTraits$1;
49
- encode?: (value: TInput) => TWire | Promise<TWire>;
50
- decode: (wire: TWire) => TInput | Promise<TInput>;
50
+ encode: (value: TInput, ctx: CodecCallContext) => TWire | Promise<TWire>;
51
+ decode: (wire: TWire, ctx: CodecCallContext) => TInput | Promise<TInput>;
51
52
  renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;
52
53
  } & JsonRoundTripConfig<TInput>): MongoCodec<Id, TTraits$1, TWire, TInput>;
53
54
  /** Extract the JS application type carried by a Mongo codec — used both as `encode` input and as `decode` output. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/codecs.ts","../src/codec-registry.ts"],"sourcesContent":[],"mappings":";;;;KAIY,eAAA,GAAkB;;AAA9B;AAYA;;;;;;;;AAKa,KALD,UAKC,CAAA,WAAA,MAAA,GAAA,MAAA,EAAA,kBAAA,SAHc,eAGd,EAAA,GAAA,SAH2C,eAG3C,EAAA,EAAA,QAAA,OAAA,EAAA,SAAA,OAAA,CAAA,GAAT,KAAS,CAAC,EAAD,EAAK,SAAL,EAAc,KAAd,EAAqB,MAArB,CAAA;AAA6B;;;;;;;KASrC,mBAMqB,CAAA,MAAA,CAAA,GAAA,CANU,MAMV,CAAA,SAAA,CAN2B,SAM3B,CAAA,GAAA;EAAW,UAAA,CAAA,EAAA,CAAA,KAAA,EAJV,MAIU,EAAA,GAJC,SAID;EACZ,UAAA,CAAA,EAAA,CAAA,IAAA,EAJC,SAID,EAAA,GAJe,MAIf;CAAc,GAAA;EAAM,UAAA,EAAA,CAAA,KAAA,EADnB,MACmB,EAAA,GADR,SACQ;EAiB7B,UAAA,EAAA,CAAU,IAAA,EAjBD,SAiBC,EAAA,GAjBa,MAiBb;CAEO;;;;;;;;;;;;;;;AAYf,iBAdF,UAcE,CAAA,WAAA,MAAA,EAAA,wBAAA,SAZe,eAYf,EAAA,GAAA,SAAA,EAAA,EAAA,QAAA,OAAA,EAAA,SAAA,OAAA,CAAA,CAAA,MAAA,EAAA;EAAS,MAAA,EAPf,EAOe;EAAO,WAAA,EAAA,SAAA,MAAA,EAAA;EAA/B,MAAA,CAAA,EALU,SAKV;EAAU,MAAA,CAAA,EAAA,CAAA,KAAA,EAJQ,MAIR,EAAA,GAJmB,KAInB,GAJ2B,OAI3B,CAJmC,KAInC,CAAA;EAuCD,MAAA,EAAA,CAAA,IAAA,EA1CO,KA0CQ,EAAA,GA1CE,MA0CF,GA1CW,OA0CX,CA1CmB,MA0CnB,CAAA;EACzB,gBAAA,CAAA,EAAA,CAAA,UAAA,EA1CkC,MA0ClC,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,GAAA,MAAA,GAAA,SAAA;CAAsC,GAzClC,mBAyCkC,CAzCd,MAyCc,CAAA,CAAA,EAxCrC,UAwCqC,CAxC1B,EAwC0B,EAxCtB,SAwCsB,EAxCb,KAwCa,EAxCN,MAwCM,CAAA;;AAAlB,KADV,eACU,CAAA,CAAA,CAAA,GAApB,CAAoB,SAAV,UAAU,CAAA,MAAA,EAAA,SAAkB,eAAlB,EAAA,EAAA,OAAA,EAAA,KAAA,OAAA,CAAA,GAAA,MAAA,GAAA,KAAA;AAEV,KAAA,gBAAgB,CAAA,CAAA,CAAA,GAC1B,CAD0B,SAChB,UADgB,CAAA,MAAA,EAAA,KAAA,QAAA,CAAA,GACoB,OADpB,CAAA,MAAA,CAAA,GACsC,eADtC,GAAA,KAAA;;;UC5GX,kBAAA;mBACE;;EDCP,QAAA,CAAA,KAAA,ECCM,UDDS,CAAA,MAAG,CAAA,CAAA,EAAA,IAAU;EAY5B,CAAA,MAAA,CAAA,QAAU,GAAA,ECVC,QDUD,CCVU,UDUV,CAAA,MAAA,CAAA,CAAA;EAEK,MAAA,EAAA,ECXf,gBDWe,CCXE,UDWF,CAAA,MAAA,CAAA,CAAA;;AAGb,iBCgBE,wBAAA,CAAA,CDhBF,ECgB8B,kBDhB9B"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/codecs.ts","../src/codec-registry.ts"],"sourcesContent":[],"mappings":";;;;KAQY,eAAA,GAAkB;;AAA9B;AAYA;;;;;;;;AAKa,KALD,UAKC,CAAA,WAAA,MAAA,GAAA,MAAA,EAAA,kBAAA,SAHc,eAGd,EAAA,GAAA,SAH2C,eAG3C,EAAA,EAAA,QAAA,OAAA,EAAA,SAAA,OAAA,CAAA,GAAT,KAAS,CAAC,EAAD,EAAK,SAAL,EAAc,KAAd,EAAqB,MAArB,CAAA;AAA6B;;;;;;;KASrC,mBAMqB,CAAA,MAAA,CAAA,GAAA,CANU,MAMV,CAAA,SAAA,CAN2B,SAM3B,CAAA,GAAA;EAAW,UAAA,CAAA,EAAA,CAAA,KAAA,EAJV,MAIU,EAAA,GAJC,SAID;EACZ,UAAA,CAAA,EAAA,CAAA,IAAA,EAJC,SAID,EAAA,GAJe,MAIf;CAAc,GAAA;EAAM,UAAA,EAAA,CAAA,KAAA,EADnB,MACmB,EAAA,GADR,SACQ;EAkB7B,UAAA,EAAA,CAAU,IAAA,EAlBD,SAkBC,EAAA,GAlBa,MAkBb;CAEO;;;;;;;;;;;;;;;;AAYnB,iBAdE,UAcF,CAAA,WAAA,MAAA,EAAA,wBAAA,SAZmB,eAYnB,EAAA,GAAA,SAAA,EAAA,EAAA,QAAA,OAAA,EAAA,SAAA,OAAA,CAAA,CAAA,MAAA,EAAA;EAAI,MAAA,EAPN,EAOM;EAAS,WAAA,EAAA,SAAA,MAAA,EAAA;EAAO,MAAA,CAAA,EALrB,SAKqB;EAA/B,MAAA,EAAA,CAAA,KAAA,EAJiB,MAIjB,EAAA,GAAA,EAJ8B,gBAI9B,EAAA,GAJmD,KAInD,GAJ2D,OAI3D,CAJmE,KAInE,CAAA;EAAU,MAAA,EAAA,CAAA,IAAA,EAHM,KAGN,EAAA,GAAA,EAHkB,gBAGlB,EAAA,GAHuC,MAGvC,GAHgD,OAGhD,CAHwD,MAGxD,CAAA;EA0CD,gBAAA,CAAA,EAAe,CAAA,UAAA,EA5CS,MA4CT,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,GAAA,MAAA,GAAA,SAAA;CACzB,GA5CI,mBA4CJ,CA5CwB,MA4CxB,CAAA,CAAA,EA3CC,UA2CD,CA3CY,EA2CZ,EA3CgB,SA2ChB,EA3CyB,KA2CzB,EA3CgC,MA2ChC,CAAA;;AAAU,KADA,eACA,CAAA,CAAA,CAAA,GAAV,CAAU,SAAA,UAAA,CAAA,MAAA,EAAA,SAA4B,eAA5B,EAAA,EAAA,OAAA,EAAA,KAAA,OAAA,CAAA,GAAA,MAAA,GAAA,KAAA;AAAU,KAEV,gBAFU,CAAA,CAAA,CAAA,GAGpB,CAHoB,SAGV,UAHU,CAAA,MAAA,EAAA,KAAA,QAAA,CAAA,GAG0B,OAH1B,CAAA,MAAA,CAAA,GAG4C,eAH5C,GAAA,KAAA;;;UClHL,kBAAA;mBACE;;EDKP,QAAA,CAAA,KAAA,ECHM,UDGS,CAAA,MAAG,CAAA,CAAA,EAAA,IAAU;EAY5B,CAAA,MAAA,CAAA,QAAU,GAAA,ECdC,QDcD,CCdU,UDcV,CAAA,MAAA,CAAA,CAAA;EAEK,MAAA,EAAA,ECff,gBDee,CCfE,UDeF,CAAA,MAAA,CAAA,CAAA;;AAGb,iBCYE,wBAAA,CAAA,CDZF,ECY8B,kBDZ9B"}
package/dist/index.mjs CHANGED
@@ -32,17 +32,18 @@ function createMongoCodecRegistry() {
32
32
  * Author `encode` and `decode` as sync or async functions; the factory
33
33
  * produces a {@link MongoCodec} whose query-time methods follow the
34
34
  * boundary contract documented on the framework {@link BaseCodec}.
35
+ * Authors receive a second `ctx` options argument carrying the per-call
36
+ * context; ignore it if you don't need it.
35
37
  *
36
- * `encode` is optional when omitted, an identity default is installed
37
- * (declaring "the input value already is the wire value", so `TInput` and
38
- * `TWire` are interchangeable for that codec). `decode` is always
39
- * required. `encodeJson` and `decodeJson` default to identity **only when
40
- * `TInput` is assignable to `JsonValue`**; otherwise both are required so
41
- * the contract artifact stays JSON-safe.
38
+ * Both `encode` and `decode` are required so `TInput` and `TWire` are
39
+ * always covered by an explicit author function the factory installs
40
+ * no identity fallback. `encodeJson` and `decodeJson` default to identity
41
+ * **only when `TInput` is assignable to `JsonValue`**; otherwise both are
42
+ * required so the contract artifact stays JSON-safe.
42
43
  */
43
44
  function mongoCodec(config) {
44
45
  const identity = (v) => v;
45
- const userEncode = config.encode ?? ((value) => value);
46
+ const userEncode = config.encode;
46
47
  const userDecode = config.decode;
47
48
  const widenedConfig = config;
48
49
  return {
@@ -50,16 +51,16 @@ function mongoCodec(config) {
50
51
  targetTypes: config.targetTypes,
51
52
  ...ifDefined("traits", config.traits ? Object.freeze([...config.traits]) : void 0),
52
53
  ...ifDefined("renderOutputType", config.renderOutputType),
53
- encode: (value) => {
54
+ encode: (value, ctx) => {
54
55
  try {
55
- return Promise.resolve(userEncode(value));
56
+ return Promise.resolve(userEncode(value, ctx));
56
57
  } catch (error) {
57
58
  return Promise.reject(error);
58
59
  }
59
60
  },
60
- decode: (wire) => {
61
+ decode: (wire, ctx) => {
61
62
  try {
62
- return Promise.resolve(userDecode(wire));
63
+ return Promise.resolve(userDecode(wire, ctx));
63
64
  } catch (error) {
64
65
  return Promise.reject(error);
65
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["#byId"],"sources":["../src/codec-registry.ts","../src/codecs.ts"],"sourcesContent":["import type { MongoCodec } from './codecs';\n\nexport interface MongoCodecRegistry {\n get(id: string): MongoCodec<string> | undefined;\n has(id: string): boolean;\n register(codec: MongoCodec<string>): void;\n [Symbol.iterator](): Iterator<MongoCodec<string>>;\n values(): IterableIterator<MongoCodec<string>>;\n}\n\nclass MongoCodecRegistryImpl implements MongoCodecRegistry {\n readonly #byId = new Map<string, MongoCodec<string>>();\n\n get(id: string): MongoCodec<string> | undefined {\n return this.#byId.get(id);\n }\n\n has(id: string): boolean {\n return this.#byId.has(id);\n }\n\n register(codec: MongoCodec<string>): void {\n if (this.#byId.has(codec.id)) {\n throw new Error(`Codec with ID '${codec.id}' is already registered`);\n }\n this.#byId.set(codec.id, codec);\n }\n\n *[Symbol.iterator](): Iterator<MongoCodec<string>> {\n yield* this.#byId.values();\n }\n\n values(): IterableIterator<MongoCodec<string>> {\n return this.#byId.values();\n }\n}\n\nexport function createMongoCodecRegistry(): MongoCodecRegistry {\n return new MongoCodecRegistryImpl();\n}\n","import type { JsonValue } from '@prisma-next/contract/types';\nimport type { Codec as BaseCodec, CodecTrait } from '@prisma-next/framework-components/codec';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport type MongoCodecTrait = CodecTrait;\n\n/**\n * A codec for the Mongo target. Translates between an application value\n * and the BSON-shaped wire form the Mongo driver exchanges, and between\n * an application value and the JSON form stored in contract artifacts.\n *\n * Same shape as the framework codec base — see `Codec` in\n * `@prisma-next/framework-components/codec` for the contract. The alias\n * exists so Mongo-specific metadata can be added here in future without\n * touching the framework base.\n */\nexport type MongoCodec<\n Id extends string = string,\n TTraits extends readonly MongoCodecTrait[] = readonly MongoCodecTrait[],\n TWire = unknown,\n TInput = unknown,\n> = BaseCodec<Id, TTraits, TWire, TInput>;\n\n/**\n * Conditional bundle for `encodeJson`/`decodeJson`: when `TInput` is\n * structurally assignable to `JsonValue` the identity defaults are\n * sound and both fields are optional; otherwise both fields are\n * required so an author cannot silently produce a non-JSON-safe\n * contract artifact.\n */\ntype JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue]\n ? {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n }\n : {\n encodeJson: (value: TInput) => JsonValue;\n decodeJson: (json: JsonValue) => TInput;\n };\n\n/**\n * Construct a Mongo codec from author functions.\n *\n * Author `encode` and `decode` as sync or async functions; the factory\n * produces a {@link MongoCodec} whose query-time methods follow the\n * boundary contract documented on the framework {@link BaseCodec}.\n *\n * `encode` is optional when omitted, an identity default is installed\n * (declaring \"the input value already is the wire value\", so `TInput` and\n * `TWire` are interchangeable for that codec). `decode` is always\n * required. `encodeJson` and `decodeJson` default to identity **only when\n * `TInput` is assignable to `JsonValue`**; otherwise both are required so\n * the contract artifact stays JSON-safe.\n */\nexport function mongoCodec<\n Id extends string,\n const TTraits extends readonly MongoCodecTrait[] = readonly [],\n TWire = unknown,\n TInput = unknown,\n>(\n config: {\n typeId: Id;\n targetTypes: readonly string[];\n traits?: TTraits;\n encode?: (value: TInput) => TWire | Promise<TWire>;\n decode: (wire: TWire) => TInput | Promise<TInput>;\n renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;\n } & JsonRoundTripConfig<TInput>,\n): MongoCodec<Id, TTraits, TWire, TInput> {\n const identity = (v: unknown) => v;\n // The synchronous identity default is only safe when the author has\n // declared \"the input is already the wire value\" (i.e. TInput == TWire);\n // it returns the value directly, never a Promise.\n const userEncode = config.encode ?? ((value: TInput) => value as unknown as TWire);\n const userDecode = config.decode;\n const widenedConfig = config as {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n };\n return {\n id: config.typeId,\n targetTypes: config.targetTypes,\n ...ifDefined(\n 'traits',\n config.traits ? (Object.freeze([...config.traits]) as TTraits) : undefined,\n ),\n ...ifDefined('renderOutputType', config.renderOutputType),\n encode: (value) => {\n try {\n return Promise.resolve(userEncode(value));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n decode: (wire) => {\n try {\n return Promise.resolve(userDecode(wire));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n encodeJson: (widenedConfig.encodeJson ?? identity) as (value: TInput) => JsonValue,\n decodeJson: (widenedConfig.decodeJson ?? identity) as (json: JsonValue) => TInput,\n };\n}\n\n/** Extract the JS application type carried by a Mongo codec — used both as `encode` input and as `decode` output. */\nexport type MongoCodecInput<T> =\n T extends MongoCodec<string, readonly MongoCodecTrait[], unknown, infer TInput> ? TInput : never;\n\nexport type MongoCodecTraits<T> =\n T extends MongoCodec<string, infer TTraits> ? TTraits[number] & MongoCodecTrait : never;\n"],"mappings":";;;AAUA,IAAM,yBAAN,MAA2D;CACzD,CAASA,uBAAQ,IAAI,KAAiC;CAEtD,IAAI,IAA4C;AAC9C,SAAO,MAAKA,KAAM,IAAI,GAAG;;CAG3B,IAAI,IAAqB;AACvB,SAAO,MAAKA,KAAM,IAAI,GAAG;;CAG3B,SAAS,OAAiC;AACxC,MAAI,MAAKA,KAAM,IAAI,MAAM,GAAG,CAC1B,OAAM,IAAI,MAAM,kBAAkB,MAAM,GAAG,yBAAyB;AAEtE,QAAKA,KAAM,IAAI,MAAM,IAAI,MAAM;;CAGjC,EAAE,OAAO,YAA0C;AACjD,SAAO,MAAKA,KAAM,QAAQ;;CAG5B,SAA+C;AAC7C,SAAO,MAAKA,KAAM,QAAQ;;;AAI9B,SAAgB,2BAA+C;AAC7D,QAAO,IAAI,wBAAwB;;;;;;;;;;;;;;;;;;;ACgBrC,SAAgB,WAMd,QAQwC;CACxC,MAAM,YAAY,MAAe;CAIjC,MAAM,aAAa,OAAO,YAAY,UAAkB;CACxD,MAAM,aAAa,OAAO;CAC1B,MAAM,gBAAgB;AAItB,QAAO;EACL,IAAI,OAAO;EACX,aAAa,OAAO;EACpB,GAAG,UACD,UACA,OAAO,SAAU,OAAO,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,GAAe,OAClE;EACD,GAAG,UAAU,oBAAoB,OAAO,iBAAiB;EACzD,SAAS,UAAU;AACjB,OAAI;AACF,WAAO,QAAQ,QAAQ,WAAW,MAAM,CAAC;YAClC,OAAO;AACd,WAAO,QAAQ,OAAO,MAAM;;;EAGhC,SAAS,SAAS;AAChB,OAAI;AACF,WAAO,QAAQ,QAAQ,WAAW,KAAK,CAAC;YACjC,OAAO;AACd,WAAO,QAAQ,OAAO,MAAM;;;EAGhC,YAAa,cAAc,cAAc;EACzC,YAAa,cAAc,cAAc;EAC1C"}
1
+ {"version":3,"file":"index.mjs","names":["#byId"],"sources":["../src/codec-registry.ts","../src/codecs.ts"],"sourcesContent":["import type { MongoCodec } from './codecs';\n\nexport interface MongoCodecRegistry {\n get(id: string): MongoCodec<string> | undefined;\n has(id: string): boolean;\n register(codec: MongoCodec<string>): void;\n [Symbol.iterator](): Iterator<MongoCodec<string>>;\n values(): IterableIterator<MongoCodec<string>>;\n}\n\nclass MongoCodecRegistryImpl implements MongoCodecRegistry {\n readonly #byId = new Map<string, MongoCodec<string>>();\n\n get(id: string): MongoCodec<string> | undefined {\n return this.#byId.get(id);\n }\n\n has(id: string): boolean {\n return this.#byId.has(id);\n }\n\n register(codec: MongoCodec<string>): void {\n if (this.#byId.has(codec.id)) {\n throw new Error(`Codec with ID '${codec.id}' is already registered`);\n }\n this.#byId.set(codec.id, codec);\n }\n\n *[Symbol.iterator](): Iterator<MongoCodec<string>> {\n yield* this.#byId.values();\n }\n\n values(): IterableIterator<MongoCodec<string>> {\n return this.#byId.values();\n }\n}\n\nexport function createMongoCodecRegistry(): MongoCodecRegistry {\n return new MongoCodecRegistryImpl();\n}\n","import type { JsonValue } from '@prisma-next/contract/types';\nimport type {\n Codec as BaseCodec,\n CodecCallContext,\n CodecTrait,\n} from '@prisma-next/framework-components/codec';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport type MongoCodecTrait = CodecTrait;\n\n/**\n * A codec for the Mongo target. Translates between an application value\n * and the BSON-shaped wire form the Mongo driver exchanges, and between\n * an application value and the JSON form stored in contract artifacts.\n *\n * Same shape as the framework codec base — see `Codec` in\n * `@prisma-next/framework-components/codec` for the contract. The alias\n * exists so Mongo-specific metadata can be added here in future without\n * touching the framework base.\n */\nexport type MongoCodec<\n Id extends string = string,\n TTraits extends readonly MongoCodecTrait[] = readonly MongoCodecTrait[],\n TWire = unknown,\n TInput = unknown,\n> = BaseCodec<Id, TTraits, TWire, TInput>;\n\n/**\n * Conditional bundle for `encodeJson`/`decodeJson`: when `TInput` is\n * structurally assignable to `JsonValue` the identity defaults are\n * sound and both fields are optional; otherwise both fields are\n * required so an author cannot silently produce a non-JSON-safe\n * contract artifact.\n */\ntype JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue]\n ? {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n }\n : {\n encodeJson: (value: TInput) => JsonValue;\n decodeJson: (json: JsonValue) => TInput;\n };\n\n/**\n * Construct a Mongo codec from author functions.\n *\n * Author `encode` and `decode` as sync or async functions; the factory\n * produces a {@link MongoCodec} whose query-time methods follow the\n * boundary contract documented on the framework {@link BaseCodec}.\n * Authors receive a second `ctx` options argument carrying the per-call\n * context; ignore it if you don't need it.\n *\n * Both `encode` and `decode` are required so `TInput` and `TWire` are\n * always covered by an explicit author function — the factory installs\n * no identity fallback. `encodeJson` and `decodeJson` default to identity\n * **only when `TInput` is assignable to `JsonValue`**; otherwise both are\n * required so the contract artifact stays JSON-safe.\n */\nexport function mongoCodec<\n Id extends string,\n const TTraits extends readonly MongoCodecTrait[] = readonly [],\n TWire = unknown,\n TInput = unknown,\n>(\n config: {\n typeId: Id;\n targetTypes: readonly string[];\n traits?: TTraits;\n encode: (value: TInput, ctx: CodecCallContext) => TWire | Promise<TWire>;\n decode: (wire: TWire, ctx: CodecCallContext) => TInput | Promise<TInput>;\n renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;\n } & JsonRoundTripConfig<TInput>,\n): MongoCodec<Id, TTraits, TWire, TInput> {\n const identity = (v: unknown) => v;\n // The runtime allocates one `CodecCallContext` per `runtime.execute()`\n // call (no caller-supplied `signal` produces `{}` instead of `undefined`)\n // and threads it as a non-optional reference to every codec call. The\n // author surface keeps the second parameter optional so single-arg\n // `(value) => …` authors continue to satisfy the signature via\n // TypeScript's bivariance for trailing parameters.\n const userEncode = config.encode;\n const userDecode = config.decode;\n const widenedConfig = config as {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n };\n return {\n id: config.typeId,\n targetTypes: config.targetTypes,\n ...ifDefined(\n 'traits',\n config.traits ? (Object.freeze([...config.traits]) as TTraits) : undefined,\n ),\n ...ifDefined('renderOutputType', config.renderOutputType),\n encode: (value, ctx) => {\n try {\n return Promise.resolve(userEncode(value, ctx));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n decode: (wire, ctx) => {\n try {\n return Promise.resolve(userDecode(wire, ctx));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n encodeJson: (widenedConfig.encodeJson ?? identity) as (value: TInput) => JsonValue,\n decodeJson: (widenedConfig.decodeJson ?? identity) as (json: JsonValue) => TInput,\n };\n}\n\n/** Extract the JS application type carried by a Mongo codec — used both as `encode` input and as `decode` output. */\nexport type MongoCodecInput<T> =\n T extends MongoCodec<string, readonly MongoCodecTrait[], unknown, infer TInput> ? TInput : never;\n\nexport type MongoCodecTraits<T> =\n T extends MongoCodec<string, infer TTraits> ? TTraits[number] & MongoCodecTrait : never;\n"],"mappings":";;;AAUA,IAAM,yBAAN,MAA2D;CACzD,CAASA,uBAAQ,IAAI,KAAiC;CAEtD,IAAI,IAA4C;AAC9C,SAAO,MAAKA,KAAM,IAAI,GAAG;;CAG3B,IAAI,IAAqB;AACvB,SAAO,MAAKA,KAAM,IAAI,GAAG;;CAG3B,SAAS,OAAiC;AACxC,MAAI,MAAKA,KAAM,IAAI,MAAM,GAAG,CAC1B,OAAM,IAAI,MAAM,kBAAkB,MAAM,GAAG,yBAAyB;AAEtE,QAAKA,KAAM,IAAI,MAAM,IAAI,MAAM;;CAGjC,EAAE,OAAO,YAA0C;AACjD,SAAO,MAAKA,KAAM,QAAQ;;CAG5B,SAA+C;AAC7C,SAAO,MAAKA,KAAM,QAAQ;;;AAI9B,SAAgB,2BAA+C;AAC7D,QAAO,IAAI,wBAAwB;;;;;;;;;;;;;;;;;;;;ACqBrC,SAAgB,WAMd,QAQwC;CACxC,MAAM,YAAY,MAAe;CAOjC,MAAM,aAAa,OAAO;CAC1B,MAAM,aAAa,OAAO;CAC1B,MAAM,gBAAgB;AAItB,QAAO;EACL,IAAI,OAAO;EACX,aAAa,OAAO;EACpB,GAAG,UACD,UACA,OAAO,SAAU,OAAO,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,GAAe,OAClE;EACD,GAAG,UAAU,oBAAoB,OAAO,iBAAiB;EACzD,SAAS,OAAO,QAAQ;AACtB,OAAI;AACF,WAAO,QAAQ,QAAQ,WAAW,OAAO,IAAI,CAAC;YACvC,OAAO;AACd,WAAO,QAAQ,OAAO,MAAM;;;EAGhC,SAAS,MAAM,QAAQ;AACrB,OAAI;AACF,WAAO,QAAQ,QAAQ,WAAW,MAAM,IAAI,CAAC;YACtC,OAAO;AACd,WAAO,QAAQ,OAAO,MAAM;;;EAGhC,YAAa,cAAc,cAAc;EACzC,YAAa,cAAc,cAAc;EAC1C"}
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@prisma-next/mongo-codec",
3
- "version": "0.5.0-dev.28",
3
+ "version": "0.5.0-dev.29",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Codec interface and registry for Prisma Next MongoDB support",
7
7
  "dependencies": {
8
- "@prisma-next/contract": "0.5.0-dev.28",
9
- "@prisma-next/framework-components": "0.5.0-dev.28",
10
- "@prisma-next/utils": "0.5.0-dev.28"
8
+ "@prisma-next/contract": "0.5.0-dev.29",
9
+ "@prisma-next/framework-components": "0.5.0-dev.29",
10
+ "@prisma-next/utils": "0.5.0-dev.29"
11
11
  },
12
12
  "devDependencies": {
13
13
  "tsdown": "0.18.4",
14
14
  "typescript": "5.9.3",
15
15
  "vitest": "4.0.17",
16
16
  "@prisma-next/test-utils": "0.0.1",
17
- "@prisma-next/tsconfig": "0.0.0",
18
- "@prisma-next/tsdown": "0.0.0"
17
+ "@prisma-next/tsdown": "0.0.0",
18
+ "@prisma-next/tsconfig": "0.0.0"
19
19
  },
20
20
  "files": [
21
21
  "dist",
package/src/codecs.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { JsonValue } from '@prisma-next/contract/types';
2
- import type { Codec as BaseCodec, CodecTrait } from '@prisma-next/framework-components/codec';
2
+ import type {
3
+ Codec as BaseCodec,
4
+ CodecCallContext,
5
+ CodecTrait,
6
+ } from '@prisma-next/framework-components/codec';
3
7
  import { ifDefined } from '@prisma-next/utils/defined';
4
8
 
5
9
  export type MongoCodecTrait = CodecTrait;
@@ -44,13 +48,14 @@ type JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue]
44
48
  * Author `encode` and `decode` as sync or async functions; the factory
45
49
  * produces a {@link MongoCodec} whose query-time methods follow the
46
50
  * boundary contract documented on the framework {@link BaseCodec}.
51
+ * Authors receive a second `ctx` options argument carrying the per-call
52
+ * context; ignore it if you don't need it.
47
53
  *
48
- * `encode` is optional when omitted, an identity default is installed
49
- * (declaring "the input value already is the wire value", so `TInput` and
50
- * `TWire` are interchangeable for that codec). `decode` is always
51
- * required. `encodeJson` and `decodeJson` default to identity **only when
52
- * `TInput` is assignable to `JsonValue`**; otherwise both are required so
53
- * the contract artifact stays JSON-safe.
54
+ * Both `encode` and `decode` are required so `TInput` and `TWire` are
55
+ * always covered by an explicit author function the factory installs
56
+ * no identity fallback. `encodeJson` and `decodeJson` default to identity
57
+ * **only when `TInput` is assignable to `JsonValue`**; otherwise both are
58
+ * required so the contract artifact stays JSON-safe.
54
59
  */
55
60
  export function mongoCodec<
56
61
  Id extends string,
@@ -62,16 +67,19 @@ export function mongoCodec<
62
67
  typeId: Id;
63
68
  targetTypes: readonly string[];
64
69
  traits?: TTraits;
65
- encode?: (value: TInput) => TWire | Promise<TWire>;
66
- decode: (wire: TWire) => TInput | Promise<TInput>;
70
+ encode: (value: TInput, ctx: CodecCallContext) => TWire | Promise<TWire>;
71
+ decode: (wire: TWire, ctx: CodecCallContext) => TInput | Promise<TInput>;
67
72
  renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;
68
73
  } & JsonRoundTripConfig<TInput>,
69
74
  ): MongoCodec<Id, TTraits, TWire, TInput> {
70
75
  const identity = (v: unknown) => v;
71
- // The synchronous identity default is only safe when the author has
72
- // declared "the input is already the wire value" (i.e. TInput == TWire);
73
- // it returns the value directly, never a Promise.
74
- const userEncode = config.encode ?? ((value: TInput) => value as unknown as TWire);
76
+ // The runtime allocates one `CodecCallContext` per `runtime.execute()`
77
+ // call (no caller-supplied `signal` produces `{}` instead of `undefined`)
78
+ // and threads it as a non-optional reference to every codec call. The
79
+ // author surface keeps the second parameter optional so single-arg
80
+ // `(value) => …` authors continue to satisfy the signature via
81
+ // TypeScript's bivariance for trailing parameters.
82
+ const userEncode = config.encode;
75
83
  const userDecode = config.decode;
76
84
  const widenedConfig = config as {
77
85
  encodeJson?: (value: TInput) => JsonValue;
@@ -85,16 +93,16 @@ export function mongoCodec<
85
93
  config.traits ? (Object.freeze([...config.traits]) as TTraits) : undefined,
86
94
  ),
87
95
  ...ifDefined('renderOutputType', config.renderOutputType),
88
- encode: (value) => {
96
+ encode: (value, ctx) => {
89
97
  try {
90
- return Promise.resolve(userEncode(value));
98
+ return Promise.resolve(userEncode(value, ctx));
91
99
  } catch (error) {
92
100
  return Promise.reject(error);
93
101
  }
94
102
  },
95
- decode: (wire) => {
103
+ decode: (wire, ctx) => {
96
104
  try {
97
- return Promise.resolve(userDecode(wire));
105
+ return Promise.resolve(userDecode(wire, ctx));
98
106
  } catch (error) {
99
107
  return Promise.reject(error);
100
108
  }