@prisma-next/mongo-runtime 0.5.0-dev.30 → 0.5.0-dev.41

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
@@ -12,9 +12,42 @@ MongoDB runtime executor for Prisma Next.
12
12
 
13
13
  The Mongo runtime package implements the Mongo family runtime by extending the abstract `RuntimeCore` base class from `@prisma-next/framework-components/runtime` with Mongo-specific lowering and driver dispatch. It provides the public runtime API for MongoDB, layering Mongo concerns (adapter lowering and wire-command dispatch) on top of the shared middleware lifecycle.
14
14
 
15
+ ## Usage
16
+
17
+ The runtime takes a **`MongoExecutionContext`** built from a **`MongoExecutionStack`** (target + adapter + optional driver + extension packs). The context aggregates codec contributions from each stack component into a single registry — users do not construct or thread a `MongoCodecRegistry` themselves. This mirrors the SQL pattern (see `packages/2-sql/5-runtime/src/sql-context.ts`).
18
+
19
+ Typed reads that attach a **`resultShape`** on the query plan are decoded after the driver yields each row: scalars and scalar arrays run through their `codecId` entries; `kind: 'unknown'` subtrees are passed through unchanged; plans without `resultShape` (for example raw commands) leave rows as the driver returned them.
20
+
21
+ Example:
22
+
23
+ ```ts
24
+ import mongoRuntimeAdapter from '@prisma-next/adapter-mongo/runtime';
25
+ import { createMongoDriver } from '@prisma-next/driver-mongo';
26
+ import {
27
+ createMongoExecutionContext,
28
+ createMongoExecutionStack,
29
+ createMongoRuntime,
30
+ } from '@prisma-next/mongo-runtime';
31
+ import mongoRuntimeTarget from '@prisma-next/target-mongo/runtime';
32
+
33
+ const stack = createMongoExecutionStack({
34
+ target: mongoRuntimeTarget,
35
+ adapter: mongoRuntimeAdapter,
36
+ });
37
+ const context = createMongoExecutionContext({ contract, stack });
38
+
39
+ const runtime = createMongoRuntime({
40
+ context,
41
+ driver: await createMongoDriver(url, dbName),
42
+ });
43
+ ```
44
+
45
+ Custom or third-party codecs (encryption, vendor scalars) are contributed via an extension-pack descriptor whose `codecInstances` list includes them; `createMongoExecutionContext` folds them into the same registry. Duplicate codec ids across contributors throw `RUNTIME.DUPLICATE_CODEC` at composition time.
46
+
15
47
  ## Responsibilities
16
48
 
17
- - **Runtime executor**: `createMongoRuntime()` composes adapter and driver into a `MongoRuntime` with a single `execute(plan)` entry point accepting `MongoQueryPlan<Row>` from `@prisma-next/mongo-query-ast`. Execution is one path for reads and writes: `adapter.lower(plan)` produces a wire command, then the driver runs it.
49
+ - **Stack/context composition**: `createMongoExecutionStack` and `createMongoExecutionContext` mirror SQL's `createSqlExecutionStack` / `createExecutionContext`. The context aggregates codec contributions from `[stack.target, stack.adapter, ...stack.extensionPacks]` into a single `MongoCodecRegistry`.
50
+ - **Runtime executor**: `createMongoRuntime({ context, driver, ... })` composes context and driver into a `MongoRuntime` with a single `execute(plan)` entry point accepting `MongoQueryPlan<Row>` from `@prisma-next/mongo-query-ast`. The adapter is reached via `context.stack.adapter` (instantiated lazily through the stack's `create(stack)` factory). Execution lowers the plan through the adapter, runs the wire command on the driver, then **optionally decodes** each row when `plan.resultShape` is present.
18
51
  - **Unified flow**: There is no separate `execute` vs `executeCommand`; all operations use `execute(plan)`.
19
52
  - **Lowering**: Happens in the adapter (`lower(plan)`), wrapped by the runtime's `lower` override into a `MongoExecutionPlan`.
20
53
  - **Middleware lifecycle inheritance**: `MongoRuntime` extends `RuntimeCore<MongoQueryPlan, MongoExecutionPlan, MongoMiddleware>` and inherits the `beforeExecute` / `onRow` / `afterExecute` lifecycle from the framework via `runWithMiddleware`. Mongo does **not** override `runBeforeCompile` (Mongo middleware has no `beforeCompile` hook today).
@@ -23,9 +56,10 @@ The Mongo runtime package implements the Mongo family runtime by extending the a
23
56
  ## Dependencies
24
57
 
25
58
  - **Depends on**:
59
+ - `@prisma-next/mongo-codec` (`MongoCodecRegistry` for decode)
26
60
  - `@prisma-next/mongo-lowering` (`MongoAdapter`, `MongoDriver` interfaces)
27
61
  - `@prisma-next/mongo-query-ast` (`MongoQueryPlan`, `AnyMongoCommand` — the typed plan shape)
28
- - `@prisma-next/framework-components` (`RuntimeCore` base class, `runWithMiddleware` helper, `RuntimeMiddleware` SPI, `AsyncIterableResult` return type)
62
+ - `@prisma-next/framework-components` (`RuntimeCore` base class, `runWithMiddleware` helper, `RuntimeMiddleware` SPI, `AsyncIterableResult` return type, `RuntimeAdapterDescriptor` / `ExecutionStack` for the stack composition model)
29
63
  - **Depended on by**:
30
64
  - Integration tests (`test/integration/test/mongo/` and `test/integration/test/cross-package/cross-family-middleware.test.ts`)
31
65
 
@@ -37,7 +71,9 @@ The Mongo runtime package implements the Mongo family runtime by extending the a
37
71
  - `runDriver(exec)` — dispatches the wire command to the Mongo driver via `driver.execute(exec.command)`.
38
72
  - `close()` — closes the underlying driver.
39
73
 
40
- The execution template (`execute(plan)` `lower` `runWithMiddleware` `runDriver`) is inherited from `RuntimeCore`. The four inline middleware lifecycle loops (`beforeExecute`, `onRow`, `afterExecute`, plus the error-path `afterExecute`) that previously lived in `MongoRuntimeImpl.execute` are now delegated to the shared `runWithMiddleware` helper.
74
+ `MongoRuntimeImpl` extends `RuntimeCore` but **overrides `execute`** so that after `runWithMiddleware` yields a raw driver row, the runtime can **`decodeMongoRow`** when the lowered plan carries `resultShape`, then yield the decoded row. `lower(plan)` copies `resultShape` from the query plan onto `MongoExecutionPlan`. Middleware `onRow` still sees the raw driver row (decode happens after the middleware loop for that row, before the consumer receives the value).
75
+
76
+ The execution template is: `lower` → `runWithMiddleware` (driver loop + middleware) → **per-row decode when `exec.resultShape` is set** → yield to consumer.
41
77
 
42
78
  ```mermaid
43
79
  flowchart LR
package/dist/index.d.mts CHANGED
@@ -1,7 +1,9 @@
1
+ import { RuntimeAdapterDescriptor, RuntimeAdapterInstance, RuntimeDriverDescriptor, RuntimeDriverInstance, RuntimeExtensionDescriptor, RuntimeExtensionInstance, RuntimeTargetDescriptor, RuntimeTargetInstance, RuntimeTargetInstance as RuntimeTargetInstance$1 } from "@prisma-next/framework-components/execution";
1
2
  import { AfterExecuteResult, AsyncIterableResult, ExecutionPlan, RuntimeExecuteOptions, RuntimeMiddleware, RuntimeMiddlewareContext } from "@prisma-next/framework-components/runtime";
3
+ import { MongoCodec, MongoCodecRegistry } from "@prisma-next/mongo-codec";
4
+ import { MongoQueryPlan, MongoResultShape } from "@prisma-next/mongo-query-ast/execution";
2
5
  import { AnyMongoWireCommand } from "@prisma-next/mongo-wire";
3
6
  import { MongoAdapter, MongoDriver } from "@prisma-next/mongo-lowering";
4
- import { MongoQueryPlan } from "@prisma-next/mongo-query-ast/execution";
5
7
 
6
8
  //#region src/mongo-execution-plan.d.ts
7
9
 
@@ -23,13 +25,83 @@ import { MongoQueryPlan } from "@prisma-next/mongo-query-ast/execution";
23
25
  * Lives in the runtime layer (alongside `MongoRuntime`) because the wire
24
26
  * command shape lives in the transport layer (`@prisma-next/mongo-wire`),
25
27
  * which the lanes layer (`mongo-query-ast`, where `MongoQueryPlan` lives)
26
- * cannot depend on. M1 establishes this type; `MongoRuntime.execute` does
27
- * not yet accept it as input — that adoption lands in M4.
28
+ * cannot depend on.
28
29
  */
29
30
  interface MongoExecutionPlan<Row = unknown> extends ExecutionPlan<Row> {
30
31
  readonly command: AnyMongoWireCommand;
32
+ readonly resultShape?: MongoResultShape;
31
33
  }
32
34
  //#endregion
35
+ //#region src/mongo-execution-stack.d.ts
36
+ /**
37
+ * Mongo-specific static contributions a runtime descriptor declares.
38
+ *
39
+ * Mirrors `SqlStaticContributions` in shape: a `codecs()` getter that yields
40
+ * a `MongoCodecRegistry` populated with this contributor's codecs. The
41
+ * registry is then walked by `createMongoExecutionContext` and folded into
42
+ * the single per-execution registry the runtime reads from at decode time.
43
+ */
44
+ interface MongoStaticContributions {
45
+ readonly codecs: () => MongoCodecRegistry;
46
+ }
47
+ interface MongoRuntimeTargetDescriptor<TTargetId extends string = 'mongo', TTargetInstance extends RuntimeTargetInstance$1<'mongo', TTargetId> = RuntimeTargetInstance$1<'mongo', TTargetId>> extends RuntimeTargetDescriptor<'mongo', TTargetId, TTargetInstance>, MongoStaticContributions {}
48
+ interface MongoRuntimeAdapterInstance<TTargetId extends string = 'mongo'> extends RuntimeAdapterInstance<'mongo', TTargetId>, MongoAdapter {}
49
+ interface MongoRuntimeAdapterDescriptor<TTargetId extends string = 'mongo', TAdapterInstance extends RuntimeAdapterInstance<'mongo', TTargetId> = MongoRuntimeAdapterInstance<TTargetId>> extends RuntimeAdapterDescriptor<'mongo', TTargetId, TAdapterInstance>, MongoStaticContributions {}
50
+ interface MongoRuntimeExtensionInstance<TTargetId extends string = 'mongo'> extends RuntimeExtensionInstance<'mongo', TTargetId> {}
51
+ interface MongoRuntimeExtensionDescriptor<TTargetId extends string = 'mongo'> extends RuntimeExtensionDescriptor<'mongo', TTargetId, MongoRuntimeExtensionInstance<TTargetId>>, MongoStaticContributions {
52
+ create(): MongoRuntimeExtensionInstance<TTargetId>;
53
+ }
54
+ /**
55
+ * The Mongo execution stack: target + adapter + optional driver + extension
56
+ * packs. Mirrors `SqlExecutionStack`. Constructed via
57
+ * `createMongoExecutionStack`.
58
+ */
59
+ interface MongoExecutionStack<TTargetId extends string = 'mongo'> {
60
+ readonly target: MongoRuntimeTargetDescriptor<TTargetId>;
61
+ readonly adapter: MongoRuntimeAdapterDescriptor<TTargetId>;
62
+ readonly driver: RuntimeDriverDescriptor<'mongo', TTargetId, unknown, RuntimeDriverInstance<'mongo', TTargetId>> | undefined;
63
+ readonly extensionPacks: readonly MongoRuntimeExtensionDescriptor<TTargetId>[];
64
+ }
65
+ declare function createMongoExecutionStack<TTargetId extends string = 'mongo'>(options: {
66
+ readonly target: MongoRuntimeTargetDescriptor<TTargetId>;
67
+ readonly adapter: MongoRuntimeAdapterDescriptor<TTargetId>;
68
+ readonly driver?: RuntimeDriverDescriptor<'mongo', TTargetId, unknown, RuntimeDriverInstance<'mongo', TTargetId>> | undefined;
69
+ readonly extensionPacks?: readonly MongoRuntimeExtensionDescriptor<TTargetId>[] | undefined;
70
+ }): MongoExecutionStack<TTargetId>;
71
+ /**
72
+ * Read-only view of the codec registry exposed on `MongoExecutionContext`.
73
+ *
74
+ * Hides `register()` and the iterator from public surface — users do not
75
+ * mutate the per-execution codec registry. Internal aggregation in
76
+ * `createMongoExecutionContext` keeps using the full `MongoCodecRegistry`
77
+ * (it needs `register()`).
78
+ */
79
+ interface MongoCodecLookup {
80
+ get(id: string): MongoCodec<string> | undefined;
81
+ has(id: string): boolean;
82
+ }
83
+ /**
84
+ * Per-execution context aggregated from a `MongoExecutionStack`.
85
+ *
86
+ * Carries the user's contract, a read-only lookup over the codec registry
87
+ * composed from every stack contributor, and a back-reference to the stack
88
+ * itself so the runtime can reach the adapter without users threading it
89
+ * explicitly.
90
+ *
91
+ * Mirrors SQL's `ExecutionContext` in role; Mongo's flavour is leaner
92
+ * because there are no parameterised codecs, JSON-schema validators, or
93
+ * mutation-default generators in scope yet.
94
+ */
95
+ interface MongoExecutionContext<TTargetId extends string = 'mongo'> {
96
+ readonly contract: unknown;
97
+ readonly codecs: MongoCodecLookup;
98
+ readonly stack: MongoExecutionStack<TTargetId>;
99
+ }
100
+ declare function createMongoExecutionContext<TTargetId extends string = 'mongo'>(options: {
101
+ readonly contract: unknown;
102
+ readonly stack: MongoExecutionStack<TTargetId>;
103
+ }): MongoExecutionContext<TTargetId>;
104
+ //#endregion
33
105
  //#region src/mongo-middleware.d.ts
34
106
  interface MongoMiddlewareContext extends RuntimeMiddlewareContext {}
35
107
  /**
@@ -51,11 +123,20 @@ interface MongoMiddleware extends RuntimeMiddleware<MongoExecutionPlan> {
51
123
  }
52
124
  //#endregion
53
125
  //#region src/mongo-runtime.d.ts
126
+ /**
127
+ * Mongo runtime options.
128
+ *
129
+ * The runtime takes a {@link MongoExecutionContext} (built via
130
+ * `createMongoExecutionContext`) and a driver. Codec resolution flows from
131
+ * the context — there is no `codecs` field on this options bag. The adapter
132
+ * is reached via `context.stack.adapter` (instantiated lazily through the
133
+ * stack's `create(stack)` factory). See ADR — Mongo result-shape as a
134
+ * structural plan field, § Codec registry: stack aggregation, not user
135
+ * threading.
136
+ */
54
137
  interface MongoRuntimeOptions {
55
- readonly adapter: MongoAdapter;
138
+ readonly context: MongoExecutionContext;
56
139
  readonly driver: MongoDriver;
57
- readonly contract: unknown;
58
- readonly targetId: string;
59
140
  readonly middleware?: readonly MongoMiddleware[];
60
141
  readonly mode?: 'strict' | 'permissive';
61
142
  }
@@ -76,13 +157,15 @@ interface MongoRuntime {
76
157
  * `RUNTIME.ABORTED { phase: 'encode' }` from inside `resolveValue`'s
77
158
  * per-level `Promise.all` race.
78
159
  *
79
- * Mongo's read path does not go through codecs (per ADR 204), so there
80
- * is no `phase: 'decode'` boundary on the Mongo side.
160
+ * Mongo's read path decodes rows via `resultShape` (per ADR 209). The
161
+ * same `CodecCallContext` is forwarded into each `codec.decode(wire, ctx)`
162
+ * call, so async decoders that respect the signal get cancellation; the
163
+ * runtime itself does not currently emit a `phase: 'decode'` envelope.
81
164
  */
82
165
  execute<Row>(plan: MongoQueryPlan<Row>, options?: RuntimeExecuteOptions): AsyncIterableResult<Row>;
83
166
  close(): Promise<void>;
84
167
  }
85
168
  declare function createMongoRuntime(options: MongoRuntimeOptions): MongoRuntime;
86
169
  //#endregion
87
- export { type MongoExecutionPlan, type MongoMiddleware, type MongoMiddlewareContext, type MongoRuntime, type MongoRuntimeOptions, createMongoRuntime };
170
+ export { type MongoCodecLookup, type MongoExecutionContext, type MongoExecutionPlan, type MongoExecutionStack, type MongoMiddleware, type MongoMiddlewareContext, type MongoRuntime, type MongoRuntimeAdapterDescriptor, type MongoRuntimeAdapterInstance, type MongoRuntimeExtensionDescriptor, type MongoRuntimeExtensionInstance, type MongoRuntimeOptions, type MongoRuntimeTargetDescriptor, type MongoStaticContributions, type RuntimeTargetInstance, createMongoExecutionContext, createMongoExecutionStack, createMongoRuntime };
88
171
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/mongo-execution-plan.ts","../src/mongo-middleware.ts","../src/mongo-runtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAwBA;;;;;;;;ACjBA;AAaA;;;;;;;;AAOK,UDHY,kBCGZ,CAAA,MAAA,OAAA,CAAA,SDHsD,aCGtD,CDHoE,GCGpE,CAAA,CAAA;EAEK,SAAA,OAAA,EDJU,mBCIV;;;;UAtBO,sBAAA,SAA+B;;;ADiBhD;;;;;;;;ACjBA;AAaiB,UAAA,eAAA,SAAwB,iBAAR,CAA0B,kBAA1B,CAAA,CAAA;EAA0B,SAAA,QAAA,CAAA,EAAA,OAAA;EAEpC,aAAA,EAAA,IAAA,EAAA,kBAAA,EAAA,GAAA,EAAyB,sBAAzB,CAAA,EAAkD,OAAlD,CAAA,IAAA,CAAA;EAAyB,KAAA,EAAA,GAAA,EAEvC,MAFuC,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,IAAA,EAGtC,kBAHsC,EAAA,GAAA,EAIvC,sBAJuC,CAAA,EAK3C,OAL2C,CAAA,IAAA,CAAA;EAAyB,YAAA,EAAA,IAAA,EAO/D,kBAP+D,EAAA,MAAA,EAQ7D,kBAR6D,EAAA,GAAA,EAShE,sBATgE,CAAA,EAUpE,OAVoE,CAAA,IAAA,CAAA;;;;UCNxD,mBAAA;oBACG;EFOH,SAAA,MAAA,EENE,WFMgB;EAAsC,SAAA,QAAA,EAAA,OAAA;EACrD,SAAA,QAAA,EAAA,MAAA;EADuC,SAAA,UAAA,CAAA,EAAA,SEH1B,eFG0B,EAAA;EAAa,SAAA,IAAA,CAAA,EAAA,QAAA,GAAA,YAAA;;UECvD,YAAA;;ADlBjB;AAaA;;;;;;;;;;;;;;;;;qBC0BU,eAAe,gBACX,wBACT,oBAAoB;EAhCR,KAAA,EAAA,EAiCN,OAjCM,CAAA,IAAA,CAAmB;;AAEjB,iBA+EH,kBAAA,CA/EG,OAAA,EA+EyB,mBA/EzB,CAAA,EA+E+C,YA/E/C"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/mongo-execution-plan.ts","../src/mongo-execution-stack.ts","../src/mongo-middleware.ts","../src/mongo-runtime.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,15 +1,157 @@
1
- import { RuntimeCore, checkMiddlewareCompatibility } from "@prisma-next/framework-components/runtime";
1
+ import { createExecutionStack } from "@prisma-next/framework-components/execution";
2
+ import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatibility, runWithMiddleware, runtimeError } from "@prisma-next/framework-components/runtime";
3
+ import { createMongoCodecRegistry } from "@prisma-next/mongo-codec";
4
+ import { ifDefined } from "@prisma-next/utils/defined";
2
5
 
6
+ //#region src/mongo-execution-stack.ts
7
+ function createMongoExecutionStack(options) {
8
+ return createExecutionStack({
9
+ target: options.target,
10
+ adapter: options.adapter,
11
+ driver: options.driver,
12
+ extensionPacks: options.extensionPacks
13
+ });
14
+ }
15
+ function createMongoExecutionContext(options) {
16
+ const registry = createMongoCodecRegistry();
17
+ const owners = /* @__PURE__ */ new Map();
18
+ const contributors = [
19
+ options.stack.target,
20
+ options.stack.adapter,
21
+ ...options.stack.extensionPacks
22
+ ];
23
+ for (const contributor of contributors) {
24
+ const contributed = contributor.codecs();
25
+ for (const codec of iterateCodecs(contributed)) {
26
+ const existingOwner = owners.get(codec.id);
27
+ if (existingOwner !== void 0) throw runtimeError("RUNTIME.DUPLICATE_CODEC", `Duplicate Mongo codec id '${codec.id}' contributed by '${contributor.id}' (already registered by '${existingOwner}').`, {
28
+ codecId: codec.id,
29
+ existingOwner,
30
+ incomingOwner: contributor.id
31
+ });
32
+ registry.register(codec);
33
+ owners.set(codec.id, contributor.id);
34
+ }
35
+ }
36
+ return Object.freeze({
37
+ contract: options.contract,
38
+ codecs: registry,
39
+ stack: options.stack
40
+ });
41
+ }
42
+ function* iterateCodecs(registry) {
43
+ yield* registry.values();
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/codecs/decoding.ts
48
+ const WIRE_PREVIEW_LIMIT = 100;
49
+ function previewWireValue(wireValue) {
50
+ if (typeof wireValue === "string") return wireValue.length > WIRE_PREVIEW_LIMIT ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...` : wireValue;
51
+ return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);
52
+ }
53
+ function wrapDecodeFailure(error, collection, path, codecId, wireValue) {
54
+ const wrapped = runtimeError("RUNTIME.DECODE_FAILED", `Failed to decode field ${path} in collection '${collection}' with codec '${codecId}': ${error instanceof Error ? error.message : String(error)}`, {
55
+ collection,
56
+ path,
57
+ codec: codecId,
58
+ wirePreview: previewWireValue(wireValue)
59
+ });
60
+ wrapped.cause = error;
61
+ throw wrapped;
62
+ }
63
+ async function decodeMongoRow(row, shape, registry, collection, ctx = {}) {
64
+ if (shape.kind === "unknown") return row;
65
+ if (typeof row !== "object" || row === null) return row;
66
+ const rowObj = row;
67
+ const out = {};
68
+ const tasks = [];
69
+ function scheduleLeaf(path, codecId, wire, assign) {
70
+ const codec = registry.get(codecId);
71
+ if (!codec) {
72
+ assign(wire);
73
+ return;
74
+ }
75
+ tasks.push((async () => {
76
+ try {
77
+ assign(await codec.decode(wire, ctx));
78
+ } catch (error) {
79
+ wrapDecodeFailure(error, collection, path, codecId, wire);
80
+ }
81
+ })());
82
+ }
83
+ function walkField(value, fieldShape, path, assign) {
84
+ switch (fieldShape.kind) {
85
+ case "unknown":
86
+ assign(value);
87
+ return;
88
+ case "leaf":
89
+ if (value === null || value === void 0) {
90
+ assign(value);
91
+ return;
92
+ }
93
+ scheduleLeaf(path, fieldShape.codecId, value, assign);
94
+ return;
95
+ case "document": {
96
+ if (value === null || value === void 0) {
97
+ assign(value);
98
+ return;
99
+ }
100
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
101
+ assign(value);
102
+ return;
103
+ }
104
+ const vObj = value;
105
+ const nested = { ...vObj };
106
+ assign(nested);
107
+ for (const [fk, fShape] of Object.entries(fieldShape.fields)) walkField(vObj[fk], fShape, `${path}.${fk}`, (v) => {
108
+ nested[fk] = v;
109
+ });
110
+ return;
111
+ }
112
+ case "array": {
113
+ if (value === null || value === void 0) {
114
+ assign(value);
115
+ return;
116
+ }
117
+ if (!Array.isArray(value)) {
118
+ assign(value);
119
+ return;
120
+ }
121
+ const arr = [];
122
+ assign(arr);
123
+ for (let i = 0; i < value.length; i++) {
124
+ const el = value[i];
125
+ walkField(el, fieldShape.element, `${path}.${i}`, (v) => {
126
+ arr[i] = v;
127
+ });
128
+ }
129
+ return;
130
+ }
131
+ }
132
+ /* v8 ignore stop */
133
+ }
134
+ for (const [k, fShape] of Object.entries(shape.fields)) walkField(rowObj[k], fShape, k, (v) => {
135
+ out[k] = v;
136
+ });
137
+ for (const k of Object.keys(rowObj)) if (!Object.hasOwn(shape.fields, k)) out[k] = rowObj[k];
138
+ await Promise.all(tasks);
139
+ return out;
140
+ }
141
+
142
+ //#endregion
3
143
  //#region src/mongo-runtime.ts
4
144
  function noop() {}
5
145
  var MongoRuntimeImpl = class extends RuntimeCore {
6
146
  #adapter;
7
147
  #driver;
148
+ #codecs;
8
149
  constructor(options) {
9
150
  const middleware = options.middleware ? [...options.middleware] : [];
10
- for (const mw of middleware) checkMiddlewareCompatibility(mw, "mongo", options.targetId);
151
+ const targetId = options.context.stack.target.targetId;
152
+ for (const mw of middleware) checkMiddlewareCompatibility(mw, "mongo", targetId);
11
153
  const ctx = {
12
- contract: options.contract,
154
+ contract: options.context.contract,
13
155
  mode: options.mode ?? "strict",
14
156
  now: () => Date.now(),
15
157
  log: {
@@ -22,18 +164,34 @@ var MongoRuntimeImpl = class extends RuntimeCore {
22
164
  middleware,
23
165
  ctx
24
166
  });
25
- this.#adapter = options.adapter;
167
+ this.#adapter = options.context.stack.adapter.create(options.context.stack);
26
168
  this.#driver = options.driver;
169
+ this.#codecs = options.context.codecs;
27
170
  }
28
171
  async lower(plan, ctx) {
29
172
  return {
30
173
  command: await this.#adapter.lower(plan, ctx),
31
- meta: plan.meta
174
+ meta: plan.meta,
175
+ ...ifDefined("resultShape", plan.resultShape)
32
176
  };
33
177
  }
34
178
  runDriver(exec) {
35
179
  return this.#driver.execute(exec.command);
36
180
  }
181
+ execute(plan, options) {
182
+ const self = this;
183
+ const signal = options?.signal;
184
+ const codecCtx = signal === void 0 ? {} : { signal };
185
+ const generator = async function* () {
186
+ checkAborted(codecCtx, "stream");
187
+ const compiled = await self.runBeforeCompile(plan);
188
+ const exec = await self.lower(compiled, codecCtx);
189
+ const stream = runWithMiddleware(exec, self.middleware, self.ctx, () => self.runDriver(exec));
190
+ for await (const rawRow of stream) if (exec.resultShape === void 0) yield rawRow;
191
+ else yield await decodeMongoRow(rawRow, exec.resultShape, self.#codecs, exec.command.collection, codecCtx);
192
+ };
193
+ return new AsyncIterableResult(generator());
194
+ }
37
195
  async close() {
38
196
  await this.#driver.close();
39
197
  }
@@ -43,5 +201,5 @@ function createMongoRuntime(options) {
43
201
  }
44
202
 
45
203
  //#endregion
46
- export { createMongoRuntime };
204
+ export { createMongoExecutionContext, createMongoExecutionStack, createMongoRuntime };
47
205
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["#adapter","#driver","ctx: MongoMiddlewareContext"],"sources":["../src/mongo-runtime.ts"],"sourcesContent":["import type { CodecCallContext } from '@prisma-next/framework-components/codec';\nimport type {\n AsyncIterableResult,\n RuntimeExecuteOptions,\n} from '@prisma-next/framework-components/runtime';\nimport {\n checkMiddlewareCompatibility,\n RuntimeCore,\n} from '@prisma-next/framework-components/runtime';\nimport type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';\nimport type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';\nimport type { MongoExecutionPlan } from './mongo-execution-plan';\nimport type { MongoMiddleware, MongoMiddlewareContext } from './mongo-middleware';\n\nfunction noop() {}\n\nexport interface MongoRuntimeOptions {\n readonly adapter: MongoAdapter;\n readonly driver: MongoDriver;\n readonly contract: unknown;\n readonly targetId: string;\n readonly middleware?: readonly MongoMiddleware[];\n readonly mode?: 'strict' | 'permissive';\n}\n\nexport interface MongoRuntime {\n /**\n * Execute a `MongoQueryPlan` and return an async iterable of rows.\n *\n * The optional `options.signal` is threaded through\n * `lower → adapter.lower → resolveValue → codec.encode` so codec authors\n * who forward the signal to their underlying SDK get true cancellation\n * of in-flight network calls. The runtime additionally observes the\n * signal at two boundaries:\n *\n * - **Already-aborted at entry** — first `next()` throws\n * `RUNTIME.ABORTED { phase: 'stream' }` before any work is done.\n * (Inherited from `RuntimeCore.execute`.)\n * - **Mid-encode abort** — surfaces as\n * `RUNTIME.ABORTED { phase: 'encode' }` from inside `resolveValue`'s\n * per-level `Promise.all` race.\n *\n * Mongo's read path does not go through codecs (per ADR 204), so there\n * is no `phase: 'decode'` boundary on the Mongo side.\n */\n execute<Row>(\n plan: MongoQueryPlan<Row>,\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row>;\n close(): Promise<void>;\n}\n\nclass MongoRuntimeImpl\n extends RuntimeCore<MongoQueryPlan, MongoExecutionPlan, MongoMiddleware>\n implements MongoRuntime\n{\n readonly #adapter: MongoAdapter;\n readonly #driver: MongoDriver;\n\n constructor(options: MongoRuntimeOptions) {\n const middleware = options.middleware ? [...options.middleware] : [];\n for (const mw of middleware) {\n checkMiddlewareCompatibility(mw, 'mongo', options.targetId);\n }\n\n const ctx: MongoMiddlewareContext = {\n contract: options.contract,\n mode: options.mode ?? 'strict',\n now: () => Date.now(),\n log: { info: noop, warn: noop, error: noop },\n };\n\n super({ middleware, ctx });\n\n this.#adapter = options.adapter;\n this.#driver = options.driver;\n }\n\n protected override async lower(\n plan: MongoQueryPlan,\n ctx: CodecCallContext,\n ): Promise<MongoExecutionPlan> {\n return {\n command: await this.#adapter.lower(plan, ctx),\n meta: plan.meta,\n };\n }\n\n protected override runDriver(exec: MongoExecutionPlan): AsyncIterable<Record<string, unknown>> {\n return this.#driver.execute<Record<string, unknown>>(exec.command);\n }\n\n override async close(): Promise<void> {\n await this.#driver.close();\n }\n}\n\nexport function createMongoRuntime(options: MongoRuntimeOptions): MongoRuntime {\n return new MongoRuntimeImpl(options);\n}\n"],"mappings":";;;AAcA,SAAS,OAAO;AAsChB,IAAM,mBAAN,cACU,YAEV;CACE,CAASA;CACT,CAASC;CAET,YAAY,SAA8B;EACxC,MAAM,aAAa,QAAQ,aAAa,CAAC,GAAG,QAAQ,WAAW,GAAG,EAAE;AACpE,OAAK,MAAM,MAAM,WACf,8BAA6B,IAAI,SAAS,QAAQ,SAAS;EAG7D,MAAMC,MAA8B;GAClC,UAAU,QAAQ;GAClB,MAAM,QAAQ,QAAQ;GACtB,WAAW,KAAK,KAAK;GACrB,KAAK;IAAE,MAAM;IAAM,MAAM;IAAM,OAAO;IAAM;GAC7C;AAED,QAAM;GAAE;GAAY;GAAK,CAAC;AAE1B,QAAKF,UAAW,QAAQ;AACxB,QAAKC,SAAU,QAAQ;;CAGzB,MAAyB,MACvB,MACA,KAC6B;AAC7B,SAAO;GACL,SAAS,MAAM,MAAKD,QAAS,MAAM,MAAM,IAAI;GAC7C,MAAM,KAAK;GACZ;;CAGH,AAAmB,UAAU,MAAkE;AAC7F,SAAO,MAAKC,OAAQ,QAAiC,KAAK,QAAQ;;CAGpE,MAAe,QAAuB;AACpC,QAAM,MAAKA,OAAQ,OAAO;;;AAI9B,SAAgB,mBAAmB,SAA4C;AAC7E,QAAO,IAAI,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"index.mjs","names":["contributors: ReadonlyArray<MongoStaticContributions & { readonly id: string }>","out: Record<string, unknown>","tasks: Array<Promise<void>>","nested: Record<string, unknown>","arr: unknown[]","#adapter","#driver","#codecs","ctx: MongoMiddlewareContext","codecCtx: CodecCallContext"],"sources":["../src/mongo-execution-stack.ts","../src/codecs/decoding.ts","../src/mongo-runtime.ts"],"sourcesContent":["import {\n createExecutionStack,\n type ExecutionStack,\n type RuntimeAdapterDescriptor,\n type RuntimeAdapterInstance,\n type RuntimeDriverDescriptor,\n type RuntimeDriverInstance,\n type RuntimeExtensionDescriptor,\n type RuntimeExtensionInstance,\n type RuntimeTargetDescriptor,\n type RuntimeTargetInstance,\n} from '@prisma-next/framework-components/execution';\nimport { runtimeError } from '@prisma-next/framework-components/runtime';\nimport type { MongoCodec } from '@prisma-next/mongo-codec';\nimport { createMongoCodecRegistry, type MongoCodecRegistry } from '@prisma-next/mongo-codec';\nimport type { MongoAdapter } from '@prisma-next/mongo-lowering';\n\n/**\n * Mongo-specific static contributions a runtime descriptor declares.\n *\n * Mirrors `SqlStaticContributions` in shape: a `codecs()` getter that yields\n * a `MongoCodecRegistry` populated with this contributor's codecs. The\n * registry is then walked by `createMongoExecutionContext` and folded into\n * the single per-execution registry the runtime reads from at decode time.\n */\nexport interface MongoStaticContributions {\n readonly codecs: () => MongoCodecRegistry;\n}\n\nexport interface MongoRuntimeTargetDescriptor<\n TTargetId extends string = 'mongo',\n TTargetInstance extends RuntimeTargetInstance<'mongo', TTargetId> = RuntimeTargetInstance<\n 'mongo',\n TTargetId\n >,\n> extends RuntimeTargetDescriptor<'mongo', TTargetId, TTargetInstance>,\n MongoStaticContributions {}\n\nexport interface MongoRuntimeAdapterInstance<TTargetId extends string = 'mongo'>\n extends RuntimeAdapterInstance<'mongo', TTargetId>,\n MongoAdapter {}\n\nexport interface MongoRuntimeAdapterDescriptor<\n TTargetId extends string = 'mongo',\n TAdapterInstance extends RuntimeAdapterInstance<\n 'mongo',\n TTargetId\n > = MongoRuntimeAdapterInstance<TTargetId>,\n> extends RuntimeAdapterDescriptor<'mongo', TTargetId, TAdapterInstance>,\n MongoStaticContributions {}\n\nexport interface MongoRuntimeExtensionInstance<TTargetId extends string = 'mongo'>\n extends RuntimeExtensionInstance<'mongo', TTargetId> {}\n\nexport interface MongoRuntimeExtensionDescriptor<TTargetId extends string = 'mongo'>\n extends RuntimeExtensionDescriptor<'mongo', TTargetId, MongoRuntimeExtensionInstance<TTargetId>>,\n MongoStaticContributions {\n create(): MongoRuntimeExtensionInstance<TTargetId>;\n}\n\n/**\n * The Mongo execution stack: target + adapter + optional driver + extension\n * packs. Mirrors `SqlExecutionStack`. Constructed via\n * `createMongoExecutionStack`.\n */\nexport interface MongoExecutionStack<TTargetId extends string = 'mongo'> {\n readonly target: MongoRuntimeTargetDescriptor<TTargetId>;\n readonly adapter: MongoRuntimeAdapterDescriptor<TTargetId>;\n readonly driver:\n | RuntimeDriverDescriptor<\n 'mongo',\n TTargetId,\n unknown,\n RuntimeDriverInstance<'mongo', TTargetId>\n >\n | undefined;\n readonly extensionPacks: readonly MongoRuntimeExtensionDescriptor<TTargetId>[];\n}\n\nexport function createMongoExecutionStack<TTargetId extends string = 'mongo'>(options: {\n readonly target: MongoRuntimeTargetDescriptor<TTargetId>;\n readonly adapter: MongoRuntimeAdapterDescriptor<TTargetId>;\n readonly driver?:\n | RuntimeDriverDescriptor<\n 'mongo',\n TTargetId,\n unknown,\n RuntimeDriverInstance<'mongo', TTargetId>\n >\n | undefined;\n readonly extensionPacks?: readonly MongoRuntimeExtensionDescriptor<TTargetId>[] | undefined;\n}): MongoExecutionStack<TTargetId> {\n const stack = createExecutionStack({\n target: options.target,\n adapter: options.adapter,\n driver: options.driver,\n extensionPacks: options.extensionPacks,\n });\n return stack as ExecutionStack<'mongo', TTargetId> as MongoExecutionStack<TTargetId>;\n}\n\n/**\n * Read-only view of the codec registry exposed on `MongoExecutionContext`.\n *\n * Hides `register()` and the iterator from public surface — users do not\n * mutate the per-execution codec registry. Internal aggregation in\n * `createMongoExecutionContext` keeps using the full `MongoCodecRegistry`\n * (it needs `register()`).\n */\nexport interface MongoCodecLookup {\n get(id: string): MongoCodec<string> | undefined;\n has(id: string): boolean;\n}\n\n/**\n * Per-execution context aggregated from a `MongoExecutionStack`.\n *\n * Carries the user's contract, a read-only lookup over the codec registry\n * composed from every stack contributor, and a back-reference to the stack\n * itself so the runtime can reach the adapter without users threading it\n * explicitly.\n *\n * Mirrors SQL's `ExecutionContext` in role; Mongo's flavour is leaner\n * because there are no parameterised codecs, JSON-schema validators, or\n * mutation-default generators in scope yet.\n */\nexport interface MongoExecutionContext<TTargetId extends string = 'mongo'> {\n readonly contract: unknown;\n readonly codecs: MongoCodecLookup;\n readonly stack: MongoExecutionStack<TTargetId>;\n}\n\nexport function createMongoExecutionContext<TTargetId extends string = 'mongo'>(options: {\n readonly contract: unknown;\n readonly stack: MongoExecutionStack<TTargetId>;\n}): MongoExecutionContext<TTargetId> {\n const registry = createMongoCodecRegistry();\n const owners = new Map<string, string>();\n\n const contributors: ReadonlyArray<MongoStaticContributions & { readonly id: string }> = [\n options.stack.target,\n options.stack.adapter,\n ...options.stack.extensionPacks,\n ];\n\n for (const contributor of contributors) {\n const contributed = contributor.codecs();\n for (const codec of iterateCodecs(contributed)) {\n const existingOwner = owners.get(codec.id);\n if (existingOwner !== undefined) {\n throw runtimeError(\n 'RUNTIME.DUPLICATE_CODEC',\n `Duplicate Mongo codec id '${codec.id}' contributed by '${contributor.id}' (already registered by '${existingOwner}').`,\n { codecId: codec.id, existingOwner, incomingOwner: contributor.id },\n );\n }\n registry.register(codec);\n owners.set(codec.id, contributor.id);\n }\n }\n\n return Object.freeze({\n contract: options.contract,\n codecs: registry,\n stack: options.stack,\n });\n}\n\nfunction* iterateCodecs(registry: MongoCodecRegistry): Iterable<MongoCodec<string>> {\n yield* registry.values();\n}\n","import type { CodecCallContext } from '@prisma-next/framework-components/codec';\nimport { runtimeError } from '@prisma-next/framework-components/runtime';\nimport type { MongoFieldShape, MongoResultShape } from '@prisma-next/mongo-query-ast/execution';\nimport type { MongoCodecLookup } from '../mongo-execution-stack';\n\nconst WIRE_PREVIEW_LIMIT = 100;\n\nfunction previewWireValue(wireValue: unknown): string {\n if (typeof wireValue === 'string') {\n return wireValue.length > WIRE_PREVIEW_LIMIT\n ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...`\n : wireValue;\n }\n return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);\n}\n\nfunction wrapDecodeFailure(\n error: unknown,\n collection: string,\n path: string,\n codecId: string,\n wireValue: unknown,\n): never {\n const message = error instanceof Error ? error.message : String(error);\n const wrapped = runtimeError(\n 'RUNTIME.DECODE_FAILED',\n `Failed to decode field ${path} in collection '${collection}' with codec '${codecId}': ${message}`,\n {\n collection,\n path,\n codec: codecId,\n wirePreview: previewWireValue(wireValue),\n },\n );\n wrapped.cause = error;\n throw wrapped;\n}\n\nexport async function decodeMongoRow(\n row: unknown,\n shape: MongoResultShape,\n registry: MongoCodecLookup,\n collection: string,\n ctx: CodecCallContext = {},\n): Promise<unknown> {\n if (shape.kind === 'unknown') {\n return row;\n }\n if (typeof row !== 'object' || row === null) {\n return row;\n }\n const rowObj = row as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n const tasks: Array<Promise<void>> = [];\n\n function scheduleLeaf(\n path: string,\n codecId: string,\n wire: unknown,\n assign: (v: unknown) => void,\n ): void {\n const codec = registry.get(codecId);\n if (!codec) {\n assign(wire);\n return;\n }\n tasks.push(\n (async () => {\n try {\n assign(await codec.decode(wire, ctx));\n } catch (error) {\n wrapDecodeFailure(error, collection, path, codecId, wire);\n }\n })(),\n );\n }\n\n function walkField(\n value: unknown,\n fieldShape: MongoFieldShape,\n path: string,\n assign: (v: unknown) => void,\n ): void {\n // Exhaustive over `MongoFieldShape['kind']` by construction:\n // adding a new variant must add a corresponding arm or the\n // `satisfies never` below would error at type-check time.\n switch (fieldShape.kind) {\n case 'unknown':\n assign(value);\n return;\n case 'leaf':\n if (value === null || value === undefined) {\n assign(value);\n return;\n }\n scheduleLeaf(path, fieldShape.codecId, value, assign);\n return;\n case 'document': {\n if (value === null || value === undefined) {\n assign(value);\n return;\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n assign(value);\n return;\n }\n const vObj = value as Record<string, unknown>;\n // Pre-seed with a shallow copy so unshaped subdocument keys\n // round-trip verbatim. Subsequent walkField assignments overwrite\n // shaped keys with their decoded values. Mirrors the top-level\n // pass-through invariant — the decode path is structurally\n // additive at every nesting depth, not just the root.\n const nested: Record<string, unknown> = { ...vObj };\n assign(nested);\n for (const [fk, fShape] of Object.entries(fieldShape.fields)) {\n walkField(vObj[fk], fShape, `${path}.${fk}`, (v) => {\n nested[fk] = v;\n });\n }\n return;\n }\n case 'array': {\n if (value === null || value === undefined) {\n assign(value);\n return;\n }\n if (!Array.isArray(value)) {\n assign(value);\n return;\n }\n const arr: unknown[] = [];\n assign(arr);\n for (let i = 0; i < value.length; i++) {\n const el = value[i];\n walkField(el, fieldShape.element, `${path}.${i}`, (v) => {\n arr[i] = v;\n });\n }\n return;\n }\n }\n // The switch above is exhaustive over `MongoFieldShape['kind']`. The\n // `satisfies never` below is a compile-time guard that fails if a new\n // variant is added without a corresponding arm.\n /* v8 ignore start */\n fieldShape satisfies never;\n /* v8 ignore stop */\n }\n\n for (const [k, fShape] of Object.entries(shape.fields)) {\n walkField(rowObj[k], fShape, k, (v) => {\n out[k] = v;\n });\n }\n\n // Pass through any row fields the shape does not describe. The shape is a\n // partial, lane-vouched description of what the runtime knows how to decode;\n // fields outside that description (e.g. polymorphic variant fields the base\n // model's shape doesn't enumerate, sidecar fields a future schema migration\n // adds) round-trip verbatim. Drop semantics belongs to explicit projection\n // (`select` / `$project`), not to the structural decode path.\n for (const k of Object.keys(rowObj)) {\n if (!Object.hasOwn(shape.fields, k)) {\n out[k] = rowObj[k];\n }\n }\n\n await Promise.all(tasks);\n return out;\n}\n","import type { CodecCallContext } from '@prisma-next/framework-components/codec';\nimport {\n AsyncIterableResult,\n checkAborted,\n checkMiddlewareCompatibility,\n RuntimeCore,\n type RuntimeExecuteOptions,\n runWithMiddleware,\n} from '@prisma-next/framework-components/runtime';\nimport type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';\nimport type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { decodeMongoRow } from './codecs/decoding';\nimport type { MongoExecutionPlan } from './mongo-execution-plan';\nimport type { MongoCodecLookup, MongoExecutionContext } from './mongo-execution-stack';\nimport type { MongoMiddleware, MongoMiddlewareContext } from './mongo-middleware';\n\nfunction noop() {}\n\n/**\n * Mongo runtime options.\n *\n * The runtime takes a {@link MongoExecutionContext} (built via\n * `createMongoExecutionContext`) and a driver. Codec resolution flows from\n * the context — there is no `codecs` field on this options bag. The adapter\n * is reached via `context.stack.adapter` (instantiated lazily through the\n * stack's `create(stack)` factory). See ADR — Mongo result-shape as a\n * structural plan field, § Codec registry: stack aggregation, not user\n * threading.\n */\nexport interface MongoRuntimeOptions {\n readonly context: MongoExecutionContext;\n readonly driver: MongoDriver;\n readonly middleware?: readonly MongoMiddleware[];\n readonly mode?: 'strict' | 'permissive';\n}\n\nexport interface MongoRuntime {\n /**\n * Execute a `MongoQueryPlan` and return an async iterable of rows.\n *\n * The optional `options.signal` is threaded through\n * `lower → adapter.lower → resolveValue → codec.encode` so codec authors\n * who forward the signal to their underlying SDK get true cancellation\n * of in-flight network calls. The runtime additionally observes the\n * signal at two boundaries:\n *\n * - **Already-aborted at entry** — first `next()` throws\n * `RUNTIME.ABORTED { phase: 'stream' }` before any work is done.\n * (Inherited from `RuntimeCore.execute`.)\n * - **Mid-encode abort** — surfaces as\n * `RUNTIME.ABORTED { phase: 'encode' }` from inside `resolveValue`'s\n * per-level `Promise.all` race.\n *\n * Mongo's read path decodes rows via `resultShape` (per ADR 209). The\n * same `CodecCallContext` is forwarded into each `codec.decode(wire, ctx)`\n * call, so async decoders that respect the signal get cancellation; the\n * runtime itself does not currently emit a `phase: 'decode'` envelope.\n */\n execute<Row>(\n plan: MongoQueryPlan<Row>,\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row>;\n close(): Promise<void>;\n}\n\nclass MongoRuntimeImpl\n extends RuntimeCore<MongoQueryPlan, MongoExecutionPlan, MongoMiddleware>\n implements MongoRuntime\n{\n readonly #adapter: MongoAdapter;\n readonly #driver: MongoDriver;\n readonly #codecs: MongoCodecLookup;\n\n constructor(options: MongoRuntimeOptions) {\n const middleware = options.middleware ? [...options.middleware] : [];\n const targetId = options.context.stack.target.targetId;\n for (const mw of middleware) {\n checkMiddlewareCompatibility(mw, 'mongo', targetId);\n }\n\n const ctx: MongoMiddlewareContext = {\n contract: options.context.contract,\n mode: options.mode ?? 'strict',\n now: () => Date.now(),\n log: { info: noop, warn: noop, error: noop },\n };\n\n super({ middleware, ctx });\n\n const adapterDescriptor = options.context.stack.adapter;\n const adapterInstance = adapterDescriptor.create(options.context.stack);\n this.#adapter = adapterInstance;\n this.#driver = options.driver;\n this.#codecs = options.context.codecs;\n }\n\n protected override async lower(\n plan: MongoQueryPlan,\n ctx: CodecCallContext,\n ): Promise<MongoExecutionPlan> {\n return {\n command: await this.#adapter.lower(plan, ctx),\n meta: plan.meta,\n ...ifDefined('resultShape', plan.resultShape),\n };\n }\n\n protected override runDriver(exec: MongoExecutionPlan): AsyncIterable<Record<string, unknown>> {\n return this.#driver.execute<Record<string, unknown>>(exec.command);\n }\n\n override execute<Row>(\n plan: MongoQueryPlan & { readonly _row?: Row },\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n const self = this;\n const signal = options?.signal;\n const codecCtx: CodecCallContext = signal === undefined ? {} : { signal };\n const generator = async function* (): AsyncGenerator<Row, void, unknown> {\n checkAborted(codecCtx, 'stream');\n const compiled = await self.runBeforeCompile(plan);\n const exec = await self.lower(compiled, codecCtx);\n const stream = runWithMiddleware<MongoExecutionPlan, Record<string, unknown>>(\n exec,\n self.middleware,\n self.ctx,\n () => self.runDriver(exec),\n );\n for await (const rawRow of stream) {\n if (exec.resultShape === undefined) {\n yield rawRow as Row;\n } else {\n // Source the collection from the lowered exec rather than the\n // pre-lowering plan: a `runBeforeCompile` middleware is allowed to\n // rewrite collection names during compilation, and the wire\n // command carried by `exec` is always authoritative for what just\n // ran.\n const decoded = await decodeMongoRow(\n rawRow,\n exec.resultShape,\n self.#codecs,\n exec.command.collection,\n codecCtx,\n );\n yield decoded as Row;\n }\n }\n };\n return new AsyncIterableResult(generator());\n }\n\n override async close(): Promise<void> {\n await this.#driver.close();\n }\n}\n\nexport function createMongoRuntime(options: MongoRuntimeOptions): MongoRuntime {\n return new MongoRuntimeImpl(options);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@prisma-next/mongo-runtime",
3
- "version": "0.5.0-dev.30",
3
+ "version": "0.5.0-dev.41",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "MongoDB runtime implementation for Prisma Next",
7
7
  "dependencies": {
8
- "@prisma-next/contract": "0.5.0-dev.30",
9
- "@prisma-next/framework-components": "0.5.0-dev.30",
10
- "@prisma-next/mongo-lowering": "0.5.0-dev.30",
11
- "@prisma-next/mongo-query-ast": "0.5.0-dev.30",
12
- "@prisma-next/mongo-wire": "0.5.0-dev.30"
8
+ "@prisma-next/mongo-codec": "0.5.0-dev.41",
9
+ "@prisma-next/contract": "0.5.0-dev.41",
10
+ "@prisma-next/mongo-lowering": "0.5.0-dev.41",
11
+ "@prisma-next/mongo-query-ast": "0.5.0-dev.41",
12
+ "@prisma-next/mongo-wire": "0.5.0-dev.41",
13
+ "@prisma-next/framework-components": "0.5.0-dev.41",
14
+ "@prisma-next/utils": "0.5.0-dev.41"
13
15
  },
14
16
  "devDependencies": {
15
17
  "mongodb": "^6.16.0",
@@ -17,18 +19,18 @@
17
19
  "tsdown": "0.18.4",
18
20
  "typescript": "5.9.3",
19
21
  "vitest": "4.0.17",
20
- "@prisma-next/adapter-mongo": "0.5.0-dev.30",
21
- "@prisma-next/family-mongo": "0.5.0-dev.30",
22
- "@prisma-next/mongo-contract-ts": "0.5.0-dev.30",
23
- "@prisma-next/middleware-telemetry": "0.5.0-dev.30",
24
- "@prisma-next/mongo-value": "0.5.0-dev.30",
25
- "@prisma-next/mongo-query-builder": "0.5.0-dev.30",
26
- "@prisma-next/driver-mongo": "0.5.0-dev.30",
27
- "@prisma-next/mongo-contract": "0.5.0-dev.30",
28
- "@prisma-next/target-mongo": "0.5.0-dev.30",
29
- "@prisma-next/test-utils": "0.0.1",
22
+ "@prisma-next/adapter-mongo": "0.5.0-dev.41",
23
+ "@prisma-next/middleware-telemetry": "0.5.0-dev.41",
24
+ "@prisma-next/mongo-contract-ts": "0.5.0-dev.41",
25
+ "@prisma-next/family-mongo": "0.5.0-dev.41",
26
+ "@prisma-next/mongo-query-builder": "0.5.0-dev.41",
27
+ "@prisma-next/mongo-value": "0.5.0-dev.41",
28
+ "@prisma-next/driver-mongo": "0.5.0-dev.41",
29
+ "@prisma-next/mongo-contract": "0.5.0-dev.41",
30
+ "@prisma-next/target-mongo": "0.5.0-dev.41",
31
+ "@prisma-next/tsconfig": "0.0.0",
30
32
  "@prisma-next/tsdown": "0.0.0",
31
- "@prisma-next/tsconfig": "0.0.0"
33
+ "@prisma-next/test-utils": "0.0.1"
32
34
  },
33
35
  "files": [
34
36
  "dist",
@@ -0,0 +1,170 @@
1
+ import type { CodecCallContext } from '@prisma-next/framework-components/codec';
2
+ import { runtimeError } from '@prisma-next/framework-components/runtime';
3
+ import type { MongoFieldShape, MongoResultShape } from '@prisma-next/mongo-query-ast/execution';
4
+ import type { MongoCodecLookup } from '../mongo-execution-stack';
5
+
6
+ const WIRE_PREVIEW_LIMIT = 100;
7
+
8
+ function previewWireValue(wireValue: unknown): string {
9
+ if (typeof wireValue === 'string') {
10
+ return wireValue.length > WIRE_PREVIEW_LIMIT
11
+ ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...`
12
+ : wireValue;
13
+ }
14
+ return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);
15
+ }
16
+
17
+ function wrapDecodeFailure(
18
+ error: unknown,
19
+ collection: string,
20
+ path: string,
21
+ codecId: string,
22
+ wireValue: unknown,
23
+ ): never {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ const wrapped = runtimeError(
26
+ 'RUNTIME.DECODE_FAILED',
27
+ `Failed to decode field ${path} in collection '${collection}' with codec '${codecId}': ${message}`,
28
+ {
29
+ collection,
30
+ path,
31
+ codec: codecId,
32
+ wirePreview: previewWireValue(wireValue),
33
+ },
34
+ );
35
+ wrapped.cause = error;
36
+ throw wrapped;
37
+ }
38
+
39
+ export async function decodeMongoRow(
40
+ row: unknown,
41
+ shape: MongoResultShape,
42
+ registry: MongoCodecLookup,
43
+ collection: string,
44
+ ctx: CodecCallContext = {},
45
+ ): Promise<unknown> {
46
+ if (shape.kind === 'unknown') {
47
+ return row;
48
+ }
49
+ if (typeof row !== 'object' || row === null) {
50
+ return row;
51
+ }
52
+ const rowObj = row as Record<string, unknown>;
53
+ const out: Record<string, unknown> = {};
54
+ const tasks: Array<Promise<void>> = [];
55
+
56
+ function scheduleLeaf(
57
+ path: string,
58
+ codecId: string,
59
+ wire: unknown,
60
+ assign: (v: unknown) => void,
61
+ ): void {
62
+ const codec = registry.get(codecId);
63
+ if (!codec) {
64
+ assign(wire);
65
+ return;
66
+ }
67
+ tasks.push(
68
+ (async () => {
69
+ try {
70
+ assign(await codec.decode(wire, ctx));
71
+ } catch (error) {
72
+ wrapDecodeFailure(error, collection, path, codecId, wire);
73
+ }
74
+ })(),
75
+ );
76
+ }
77
+
78
+ function walkField(
79
+ value: unknown,
80
+ fieldShape: MongoFieldShape,
81
+ path: string,
82
+ assign: (v: unknown) => void,
83
+ ): void {
84
+ // Exhaustive over `MongoFieldShape['kind']` by construction:
85
+ // adding a new variant must add a corresponding arm or the
86
+ // `satisfies never` below would error at type-check time.
87
+ switch (fieldShape.kind) {
88
+ case 'unknown':
89
+ assign(value);
90
+ return;
91
+ case 'leaf':
92
+ if (value === null || value === undefined) {
93
+ assign(value);
94
+ return;
95
+ }
96
+ scheduleLeaf(path, fieldShape.codecId, value, assign);
97
+ return;
98
+ case 'document': {
99
+ if (value === null || value === undefined) {
100
+ assign(value);
101
+ return;
102
+ }
103
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
104
+ assign(value);
105
+ return;
106
+ }
107
+ const vObj = value as Record<string, unknown>;
108
+ // Pre-seed with a shallow copy so unshaped subdocument keys
109
+ // round-trip verbatim. Subsequent walkField assignments overwrite
110
+ // shaped keys with their decoded values. Mirrors the top-level
111
+ // pass-through invariant — the decode path is structurally
112
+ // additive at every nesting depth, not just the root.
113
+ const nested: Record<string, unknown> = { ...vObj };
114
+ assign(nested);
115
+ for (const [fk, fShape] of Object.entries(fieldShape.fields)) {
116
+ walkField(vObj[fk], fShape, `${path}.${fk}`, (v) => {
117
+ nested[fk] = v;
118
+ });
119
+ }
120
+ return;
121
+ }
122
+ case 'array': {
123
+ if (value === null || value === undefined) {
124
+ assign(value);
125
+ return;
126
+ }
127
+ if (!Array.isArray(value)) {
128
+ assign(value);
129
+ return;
130
+ }
131
+ const arr: unknown[] = [];
132
+ assign(arr);
133
+ for (let i = 0; i < value.length; i++) {
134
+ const el = value[i];
135
+ walkField(el, fieldShape.element, `${path}.${i}`, (v) => {
136
+ arr[i] = v;
137
+ });
138
+ }
139
+ return;
140
+ }
141
+ }
142
+ // The switch above is exhaustive over `MongoFieldShape['kind']`. The
143
+ // `satisfies never` below is a compile-time guard that fails if a new
144
+ // variant is added without a corresponding arm.
145
+ /* v8 ignore start */
146
+ fieldShape satisfies never;
147
+ /* v8 ignore stop */
148
+ }
149
+
150
+ for (const [k, fShape] of Object.entries(shape.fields)) {
151
+ walkField(rowObj[k], fShape, k, (v) => {
152
+ out[k] = v;
153
+ });
154
+ }
155
+
156
+ // Pass through any row fields the shape does not describe. The shape is a
157
+ // partial, lane-vouched description of what the runtime knows how to decode;
158
+ // fields outside that description (e.g. polymorphic variant fields the base
159
+ // model's shape doesn't enumerate, sidecar fields a future schema migration
160
+ // adds) round-trip verbatim. Drop semantics belongs to explicit projection
161
+ // (`select` / `$project`), not to the structural decode path.
162
+ for (const k of Object.keys(rowObj)) {
163
+ if (!Object.hasOwn(shape.fields, k)) {
164
+ out[k] = rowObj[k];
165
+ }
166
+ }
167
+
168
+ await Promise.all(tasks);
169
+ return out;
170
+ }
@@ -1,4 +1,20 @@
1
+ export type { RuntimeTargetInstance } from '@prisma-next/framework-components/execution';
1
2
  export type { MongoExecutionPlan } from '../mongo-execution-plan';
3
+ export type {
4
+ MongoCodecLookup,
5
+ MongoExecutionContext,
6
+ MongoExecutionStack,
7
+ MongoRuntimeAdapterDescriptor,
8
+ MongoRuntimeAdapterInstance,
9
+ MongoRuntimeExtensionDescriptor,
10
+ MongoRuntimeExtensionInstance,
11
+ MongoRuntimeTargetDescriptor,
12
+ MongoStaticContributions,
13
+ } from '../mongo-execution-stack';
14
+ export {
15
+ createMongoExecutionContext,
16
+ createMongoExecutionStack,
17
+ } from '../mongo-execution-stack';
2
18
  export type { MongoMiddleware, MongoMiddlewareContext } from '../mongo-middleware';
3
19
  export type { MongoRuntime, MongoRuntimeOptions } from '../mongo-runtime';
4
20
  export { createMongoRuntime } from '../mongo-runtime';
@@ -1,4 +1,5 @@
1
1
  import type { ExecutionPlan } from '@prisma-next/framework-components/runtime';
2
+ import type { MongoResultShape } from '@prisma-next/mongo-query-ast/execution';
2
3
  import type { AnyMongoWireCommand } from '@prisma-next/mongo-wire';
3
4
 
4
5
  /**
@@ -19,9 +20,9 @@ import type { AnyMongoWireCommand } from '@prisma-next/mongo-wire';
19
20
  * Lives in the runtime layer (alongside `MongoRuntime`) because the wire
20
21
  * command shape lives in the transport layer (`@prisma-next/mongo-wire`),
21
22
  * which the lanes layer (`mongo-query-ast`, where `MongoQueryPlan` lives)
22
- * cannot depend on. M1 establishes this type; `MongoRuntime.execute` does
23
- * not yet accept it as input — that adoption lands in M4.
23
+ * cannot depend on.
24
24
  */
25
25
  export interface MongoExecutionPlan<Row = unknown> extends ExecutionPlan<Row> {
26
26
  readonly command: AnyMongoWireCommand;
27
+ readonly resultShape?: MongoResultShape;
27
28
  }
@@ -0,0 +1,171 @@
1
+ import {
2
+ createExecutionStack,
3
+ type ExecutionStack,
4
+ type RuntimeAdapterDescriptor,
5
+ type RuntimeAdapterInstance,
6
+ type RuntimeDriverDescriptor,
7
+ type RuntimeDriverInstance,
8
+ type RuntimeExtensionDescriptor,
9
+ type RuntimeExtensionInstance,
10
+ type RuntimeTargetDescriptor,
11
+ type RuntimeTargetInstance,
12
+ } from '@prisma-next/framework-components/execution';
13
+ import { runtimeError } from '@prisma-next/framework-components/runtime';
14
+ import type { MongoCodec } from '@prisma-next/mongo-codec';
15
+ import { createMongoCodecRegistry, type MongoCodecRegistry } from '@prisma-next/mongo-codec';
16
+ import type { MongoAdapter } from '@prisma-next/mongo-lowering';
17
+
18
+ /**
19
+ * Mongo-specific static contributions a runtime descriptor declares.
20
+ *
21
+ * Mirrors `SqlStaticContributions` in shape: a `codecs()` getter that yields
22
+ * a `MongoCodecRegistry` populated with this contributor's codecs. The
23
+ * registry is then walked by `createMongoExecutionContext` and folded into
24
+ * the single per-execution registry the runtime reads from at decode time.
25
+ */
26
+ export interface MongoStaticContributions {
27
+ readonly codecs: () => MongoCodecRegistry;
28
+ }
29
+
30
+ export interface MongoRuntimeTargetDescriptor<
31
+ TTargetId extends string = 'mongo',
32
+ TTargetInstance extends RuntimeTargetInstance<'mongo', TTargetId> = RuntimeTargetInstance<
33
+ 'mongo',
34
+ TTargetId
35
+ >,
36
+ > extends RuntimeTargetDescriptor<'mongo', TTargetId, TTargetInstance>,
37
+ MongoStaticContributions {}
38
+
39
+ export interface MongoRuntimeAdapterInstance<TTargetId extends string = 'mongo'>
40
+ extends RuntimeAdapterInstance<'mongo', TTargetId>,
41
+ MongoAdapter {}
42
+
43
+ export interface MongoRuntimeAdapterDescriptor<
44
+ TTargetId extends string = 'mongo',
45
+ TAdapterInstance extends RuntimeAdapterInstance<
46
+ 'mongo',
47
+ TTargetId
48
+ > = MongoRuntimeAdapterInstance<TTargetId>,
49
+ > extends RuntimeAdapterDescriptor<'mongo', TTargetId, TAdapterInstance>,
50
+ MongoStaticContributions {}
51
+
52
+ export interface MongoRuntimeExtensionInstance<TTargetId extends string = 'mongo'>
53
+ extends RuntimeExtensionInstance<'mongo', TTargetId> {}
54
+
55
+ export interface MongoRuntimeExtensionDescriptor<TTargetId extends string = 'mongo'>
56
+ extends RuntimeExtensionDescriptor<'mongo', TTargetId, MongoRuntimeExtensionInstance<TTargetId>>,
57
+ MongoStaticContributions {
58
+ create(): MongoRuntimeExtensionInstance<TTargetId>;
59
+ }
60
+
61
+ /**
62
+ * The Mongo execution stack: target + adapter + optional driver + extension
63
+ * packs. Mirrors `SqlExecutionStack`. Constructed via
64
+ * `createMongoExecutionStack`.
65
+ */
66
+ export interface MongoExecutionStack<TTargetId extends string = 'mongo'> {
67
+ readonly target: MongoRuntimeTargetDescriptor<TTargetId>;
68
+ readonly adapter: MongoRuntimeAdapterDescriptor<TTargetId>;
69
+ readonly driver:
70
+ | RuntimeDriverDescriptor<
71
+ 'mongo',
72
+ TTargetId,
73
+ unknown,
74
+ RuntimeDriverInstance<'mongo', TTargetId>
75
+ >
76
+ | undefined;
77
+ readonly extensionPacks: readonly MongoRuntimeExtensionDescriptor<TTargetId>[];
78
+ }
79
+
80
+ export function createMongoExecutionStack<TTargetId extends string = 'mongo'>(options: {
81
+ readonly target: MongoRuntimeTargetDescriptor<TTargetId>;
82
+ readonly adapter: MongoRuntimeAdapterDescriptor<TTargetId>;
83
+ readonly driver?:
84
+ | RuntimeDriverDescriptor<
85
+ 'mongo',
86
+ TTargetId,
87
+ unknown,
88
+ RuntimeDriverInstance<'mongo', TTargetId>
89
+ >
90
+ | undefined;
91
+ readonly extensionPacks?: readonly MongoRuntimeExtensionDescriptor<TTargetId>[] | undefined;
92
+ }): MongoExecutionStack<TTargetId> {
93
+ const stack = createExecutionStack({
94
+ target: options.target,
95
+ adapter: options.adapter,
96
+ driver: options.driver,
97
+ extensionPacks: options.extensionPacks,
98
+ });
99
+ return stack as ExecutionStack<'mongo', TTargetId> as MongoExecutionStack<TTargetId>;
100
+ }
101
+
102
+ /**
103
+ * Read-only view of the codec registry exposed on `MongoExecutionContext`.
104
+ *
105
+ * Hides `register()` and the iterator from public surface — users do not
106
+ * mutate the per-execution codec registry. Internal aggregation in
107
+ * `createMongoExecutionContext` keeps using the full `MongoCodecRegistry`
108
+ * (it needs `register()`).
109
+ */
110
+ export interface MongoCodecLookup {
111
+ get(id: string): MongoCodec<string> | undefined;
112
+ has(id: string): boolean;
113
+ }
114
+
115
+ /**
116
+ * Per-execution context aggregated from a `MongoExecutionStack`.
117
+ *
118
+ * Carries the user's contract, a read-only lookup over the codec registry
119
+ * composed from every stack contributor, and a back-reference to the stack
120
+ * itself so the runtime can reach the adapter without users threading it
121
+ * explicitly.
122
+ *
123
+ * Mirrors SQL's `ExecutionContext` in role; Mongo's flavour is leaner
124
+ * because there are no parameterised codecs, JSON-schema validators, or
125
+ * mutation-default generators in scope yet.
126
+ */
127
+ export interface MongoExecutionContext<TTargetId extends string = 'mongo'> {
128
+ readonly contract: unknown;
129
+ readonly codecs: MongoCodecLookup;
130
+ readonly stack: MongoExecutionStack<TTargetId>;
131
+ }
132
+
133
+ export function createMongoExecutionContext<TTargetId extends string = 'mongo'>(options: {
134
+ readonly contract: unknown;
135
+ readonly stack: MongoExecutionStack<TTargetId>;
136
+ }): MongoExecutionContext<TTargetId> {
137
+ const registry = createMongoCodecRegistry();
138
+ const owners = new Map<string, string>();
139
+
140
+ const contributors: ReadonlyArray<MongoStaticContributions & { readonly id: string }> = [
141
+ options.stack.target,
142
+ options.stack.adapter,
143
+ ...options.stack.extensionPacks,
144
+ ];
145
+
146
+ for (const contributor of contributors) {
147
+ const contributed = contributor.codecs();
148
+ for (const codec of iterateCodecs(contributed)) {
149
+ const existingOwner = owners.get(codec.id);
150
+ if (existingOwner !== undefined) {
151
+ throw runtimeError(
152
+ 'RUNTIME.DUPLICATE_CODEC',
153
+ `Duplicate Mongo codec id '${codec.id}' contributed by '${contributor.id}' (already registered by '${existingOwner}').`,
154
+ { codecId: codec.id, existingOwner, incomingOwner: contributor.id },
155
+ );
156
+ }
157
+ registry.register(codec);
158
+ owners.set(codec.id, contributor.id);
159
+ }
160
+ }
161
+
162
+ return Object.freeze({
163
+ contract: options.contract,
164
+ codecs: registry,
165
+ stack: options.stack,
166
+ });
167
+ }
168
+
169
+ function* iterateCodecs(registry: MongoCodecRegistry): Iterable<MongoCodec<string>> {
170
+ yield* registry.values();
171
+ }
@@ -1,24 +1,36 @@
1
1
  import type { CodecCallContext } from '@prisma-next/framework-components/codec';
2
- import type {
3
- AsyncIterableResult,
4
- RuntimeExecuteOptions,
5
- } from '@prisma-next/framework-components/runtime';
6
2
  import {
3
+ AsyncIterableResult,
4
+ checkAborted,
7
5
  checkMiddlewareCompatibility,
8
6
  RuntimeCore,
7
+ type RuntimeExecuteOptions,
8
+ runWithMiddleware,
9
9
  } from '@prisma-next/framework-components/runtime';
10
10
  import type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';
11
11
  import type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';
12
+ import { ifDefined } from '@prisma-next/utils/defined';
13
+ import { decodeMongoRow } from './codecs/decoding';
12
14
  import type { MongoExecutionPlan } from './mongo-execution-plan';
15
+ import type { MongoCodecLookup, MongoExecutionContext } from './mongo-execution-stack';
13
16
  import type { MongoMiddleware, MongoMiddlewareContext } from './mongo-middleware';
14
17
 
15
18
  function noop() {}
16
19
 
20
+ /**
21
+ * Mongo runtime options.
22
+ *
23
+ * The runtime takes a {@link MongoExecutionContext} (built via
24
+ * `createMongoExecutionContext`) and a driver. Codec resolution flows from
25
+ * the context — there is no `codecs` field on this options bag. The adapter
26
+ * is reached via `context.stack.adapter` (instantiated lazily through the
27
+ * stack's `create(stack)` factory). See ADR — Mongo result-shape as a
28
+ * structural plan field, § Codec registry: stack aggregation, not user
29
+ * threading.
30
+ */
17
31
  export interface MongoRuntimeOptions {
18
- readonly adapter: MongoAdapter;
32
+ readonly context: MongoExecutionContext;
19
33
  readonly driver: MongoDriver;
20
- readonly contract: unknown;
21
- readonly targetId: string;
22
34
  readonly middleware?: readonly MongoMiddleware[];
23
35
  readonly mode?: 'strict' | 'permissive';
24
36
  }
@@ -40,8 +52,10 @@ export interface MongoRuntime {
40
52
  * `RUNTIME.ABORTED { phase: 'encode' }` from inside `resolveValue`'s
41
53
  * per-level `Promise.all` race.
42
54
  *
43
- * Mongo's read path does not go through codecs (per ADR 204), so there
44
- * is no `phase: 'decode'` boundary on the Mongo side.
55
+ * Mongo's read path decodes rows via `resultShape` (per ADR 209). The
56
+ * same `CodecCallContext` is forwarded into each `codec.decode(wire, ctx)`
57
+ * call, so async decoders that respect the signal get cancellation; the
58
+ * runtime itself does not currently emit a `phase: 'decode'` envelope.
45
59
  */
46
60
  execute<Row>(
47
61
  plan: MongoQueryPlan<Row>,
@@ -56,15 +70,17 @@ class MongoRuntimeImpl
56
70
  {
57
71
  readonly #adapter: MongoAdapter;
58
72
  readonly #driver: MongoDriver;
73
+ readonly #codecs: MongoCodecLookup;
59
74
 
60
75
  constructor(options: MongoRuntimeOptions) {
61
76
  const middleware = options.middleware ? [...options.middleware] : [];
77
+ const targetId = options.context.stack.target.targetId;
62
78
  for (const mw of middleware) {
63
- checkMiddlewareCompatibility(mw, 'mongo', options.targetId);
79
+ checkMiddlewareCompatibility(mw, 'mongo', targetId);
64
80
  }
65
81
 
66
82
  const ctx: MongoMiddlewareContext = {
67
- contract: options.contract,
83
+ contract: options.context.contract,
68
84
  mode: options.mode ?? 'strict',
69
85
  now: () => Date.now(),
70
86
  log: { info: noop, warn: noop, error: noop },
@@ -72,8 +88,11 @@ class MongoRuntimeImpl
72
88
 
73
89
  super({ middleware, ctx });
74
90
 
75
- this.#adapter = options.adapter;
91
+ const adapterDescriptor = options.context.stack.adapter;
92
+ const adapterInstance = adapterDescriptor.create(options.context.stack);
93
+ this.#adapter = adapterInstance;
76
94
  this.#driver = options.driver;
95
+ this.#codecs = options.context.codecs;
77
96
  }
78
97
 
79
98
  protected override async lower(
@@ -83,6 +102,7 @@ class MongoRuntimeImpl
83
102
  return {
84
103
  command: await this.#adapter.lower(plan, ctx),
85
104
  meta: plan.meta,
105
+ ...ifDefined('resultShape', plan.resultShape),
86
106
  };
87
107
  }
88
108
 
@@ -90,6 +110,46 @@ class MongoRuntimeImpl
90
110
  return this.#driver.execute<Record<string, unknown>>(exec.command);
91
111
  }
92
112
 
113
+ override execute<Row>(
114
+ plan: MongoQueryPlan & { readonly _row?: Row },
115
+ options?: RuntimeExecuteOptions,
116
+ ): AsyncIterableResult<Row> {
117
+ const self = this;
118
+ const signal = options?.signal;
119
+ const codecCtx: CodecCallContext = signal === undefined ? {} : { signal };
120
+ const generator = async function* (): AsyncGenerator<Row, void, unknown> {
121
+ checkAborted(codecCtx, 'stream');
122
+ const compiled = await self.runBeforeCompile(plan);
123
+ const exec = await self.lower(compiled, codecCtx);
124
+ const stream = runWithMiddleware<MongoExecutionPlan, Record<string, unknown>>(
125
+ exec,
126
+ self.middleware,
127
+ self.ctx,
128
+ () => self.runDriver(exec),
129
+ );
130
+ for await (const rawRow of stream) {
131
+ if (exec.resultShape === undefined) {
132
+ yield rawRow as Row;
133
+ } else {
134
+ // Source the collection from the lowered exec rather than the
135
+ // pre-lowering plan: a `runBeforeCompile` middleware is allowed to
136
+ // rewrite collection names during compilation, and the wire
137
+ // command carried by `exec` is always authoritative for what just
138
+ // ran.
139
+ const decoded = await decodeMongoRow(
140
+ rawRow,
141
+ exec.resultShape,
142
+ self.#codecs,
143
+ exec.command.collection,
144
+ codecCtx,
145
+ );
146
+ yield decoded as Row;
147
+ }
148
+ }
149
+ };
150
+ return new AsyncIterableResult(generator());
151
+ }
152
+
93
153
  override async close(): Promise<void> {
94
154
  await this.#driver.close();
95
155
  }