@prisma-next/mongo-runtime 0.5.0-dev.4 → 0.5.0-dev.42

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.
@@ -1,115 +1,156 @@
1
- import type {
2
- RuntimeMiddleware,
3
- RuntimeMiddlewareContext,
4
- } from '@prisma-next/framework-components/runtime';
1
+ import type { CodecCallContext } from '@prisma-next/framework-components/codec';
5
2
  import {
6
3
  AsyncIterableResult,
4
+ checkAborted,
7
5
  checkMiddlewareCompatibility,
6
+ RuntimeCore,
7
+ type RuntimeExecuteOptions,
8
+ runWithMiddleware,
8
9
  } from '@prisma-next/framework-components/runtime';
9
10
  import type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';
10
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';
14
+ import type { MongoExecutionPlan } from './mongo-execution-plan';
15
+ import type { MongoCodecLookup, MongoExecutionContext } from './mongo-execution-stack';
16
+ import type { MongoMiddleware, MongoMiddlewareContext } from './mongo-middleware';
11
17
 
12
18
  function noop() {}
13
- function now() {
14
- return Date.now();
15
- }
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
- readonly middleware?: readonly RuntimeMiddleware[];
34
+ readonly middleware?: readonly MongoMiddleware[];
23
35
  readonly mode?: 'strict' | 'permissive';
24
36
  }
25
37
 
26
38
  export interface MongoRuntime {
27
- execute<Row>(plan: MongoQueryPlan<Row>): AsyncIterableResult<Row>;
39
+ /**
40
+ * Execute a `MongoQueryPlan` and return an async iterable of rows.
41
+ *
42
+ * The optional `options.signal` is threaded through
43
+ * `lower → adapter.lower → resolveValue → codec.encode` so codec authors
44
+ * who forward the signal to their underlying SDK get true cancellation
45
+ * of in-flight network calls. The runtime additionally observes the
46
+ * signal at two boundaries:
47
+ *
48
+ * - **Already-aborted at entry** — first `next()` throws
49
+ * `RUNTIME.ABORTED { phase: 'stream' }` before any work is done.
50
+ * (Inherited from `RuntimeCore.execute`.)
51
+ * - **Mid-encode abort** — surfaces as
52
+ * `RUNTIME.ABORTED { phase: 'encode' }` from inside `resolveValue`'s
53
+ * per-level `Promise.all` race.
54
+ *
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.
59
+ */
60
+ execute<Row>(
61
+ plan: MongoQueryPlan<Row>,
62
+ options?: RuntimeExecuteOptions,
63
+ ): AsyncIterableResult<Row>;
28
64
  close(): Promise<void>;
29
65
  }
30
66
 
31
- class MongoRuntimeImpl implements MongoRuntime {
67
+ class MongoRuntimeImpl
68
+ extends RuntimeCore<MongoQueryPlan, MongoExecutionPlan, MongoMiddleware>
69
+ implements MongoRuntime
70
+ {
32
71
  readonly #adapter: MongoAdapter;
33
72
  readonly #driver: MongoDriver;
34
- readonly #middleware: readonly RuntimeMiddleware[];
35
- readonly #middlewareContext: RuntimeMiddlewareContext;
73
+ readonly #codecs: MongoCodecLookup;
36
74
 
37
75
  constructor(options: MongoRuntimeOptions) {
38
- this.#adapter = options.adapter;
39
- this.#driver = options.driver;
40
-
41
76
  const middleware = options.middleware ? [...options.middleware] : [];
77
+ const targetId = options.context.stack.target.targetId;
42
78
  for (const mw of middleware) {
43
- checkMiddlewareCompatibility(mw, 'mongo', options.targetId);
79
+ checkMiddlewareCompatibility(mw, 'mongo', targetId);
44
80
  }
45
- this.#middleware = middleware;
46
81
 
47
- this.#middlewareContext = {
48
- contract: options.contract,
82
+ const ctx: MongoMiddlewareContext = {
83
+ contract: options.context.contract,
49
84
  mode: options.mode ?? 'strict',
50
- now,
85
+ now: () => Date.now(),
51
86
  log: { info: noop, warn: noop, error: noop },
52
87
  };
53
- }
54
-
55
- execute<Row>(plan: MongoQueryPlan<Row>): AsyncIterableResult<Row> {
56
- const adapter = this.#adapter;
57
- const driver = this.#driver;
58
- const middleware = this.#middleware;
59
- const ctx = this.#middlewareContext;
60
-
61
- const iterator = async function* (): AsyncGenerator<Row, void, unknown> {
62
- const startedAt = ctx.now();
63
- let rowCount = 0;
64
- let completed = false;
65
- let failed = false;
66
-
67
- try {
68
- for (const mw of middleware) {
69
- if (mw.beforeExecute) {
70
- await mw.beforeExecute(plan, ctx);
71
- }
72
- }
73
88
 
74
- const wireCommand = adapter.lower(plan);
89
+ super({ middleware, ctx });
75
90
 
76
- for await (const row of driver.execute<Row>(wireCommand)) {
77
- for (const mw of middleware) {
78
- if (mw.onRow) {
79
- await mw.onRow(row as Record<string, unknown>, plan, ctx);
80
- }
81
- }
82
- rowCount++;
83
- yield row;
84
- }
91
+ const adapterDescriptor = options.context.stack.adapter;
92
+ const adapterInstance = adapterDescriptor.create(options.context.stack);
93
+ this.#adapter = adapterInstance;
94
+ this.#driver = options.driver;
95
+ this.#codecs = options.context.codecs;
96
+ }
85
97
 
86
- completed = true;
87
- } catch (error) {
88
- failed = true;
89
- throw error;
90
- } finally {
91
- const latencyMs = ctx.now() - startedAt;
92
- for (const mw of middleware) {
93
- if (!mw.afterExecute) continue;
98
+ protected override async lower(
99
+ plan: MongoQueryPlan,
100
+ ctx: CodecCallContext,
101
+ ): Promise<MongoExecutionPlan> {
102
+ return {
103
+ command: await this.#adapter.lower(plan, ctx),
104
+ meta: plan.meta,
105
+ ...ifDefined('resultShape', plan.resultShape),
106
+ };
107
+ }
94
108
 
95
- if (failed) {
96
- try {
97
- await mw.afterExecute(plan, { rowCount, latencyMs, completed }, ctx);
98
- } catch {
99
- // Ignore errors from afterExecute during error handling
100
- }
101
- continue;
102
- }
109
+ protected override runDriver(exec: MongoExecutionPlan): AsyncIterable<Record<string, unknown>> {
110
+ return this.#driver.execute<Record<string, unknown>>(exec.command);
111
+ }
103
112
 
104
- await mw.afterExecute(plan, { rowCount, latencyMs, completed }, ctx);
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;
105
147
  }
106
148
  }
107
149
  };
108
-
109
- return new AsyncIterableResult(iterator());
150
+ return new AsyncIterableResult(generator());
110
151
  }
111
152
 
112
- async close(): Promise<void> {
153
+ override async close(): Promise<void> {
113
154
  await this.#driver.close();
114
155
  }
115
156
  }