@prisma-next/mongo-runtime 0.5.0-dev.8 → 0.5.0-dev.80

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,160 @@
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 { computeMongoContentHash } from './content-hash';
15
+ import type { MongoExecutionPlan } from './mongo-execution-plan';
16
+ import type { MongoCodecLookup, MongoExecutionContext } from './mongo-execution-stack';
17
+ import type { MongoMiddleware, MongoMiddlewareContext } from './mongo-middleware';
11
18
 
12
19
  function noop() {}
13
- function now() {
14
- return Date.now();
15
- }
16
20
 
21
+ /**
22
+ * Mongo runtime options.
23
+ *
24
+ * The runtime takes a {@link MongoExecutionContext} (built via
25
+ * `createMongoExecutionContext`) and a driver. Codec resolution flows from
26
+ * the context — there is no `codecs` field on this options bag. The adapter
27
+ * is reached via `context.stack.adapter` (instantiated lazily through the
28
+ * stack's `create(stack)` factory). See ADR — Mongo result-shape as a
29
+ * structural plan field, § Codec registry: stack aggregation, not user
30
+ * threading.
31
+ */
17
32
  export interface MongoRuntimeOptions {
18
- readonly adapter: MongoAdapter;
33
+ readonly context: MongoExecutionContext;
19
34
  readonly driver: MongoDriver;
20
- readonly contract: unknown;
21
- readonly targetId: string;
22
- readonly middleware?: readonly RuntimeMiddleware[];
35
+ readonly middleware?: readonly MongoMiddleware[];
23
36
  readonly mode?: 'strict' | 'permissive';
24
37
  }
25
38
 
26
39
  export interface MongoRuntime {
27
- execute<Row>(plan: MongoQueryPlan<Row>): AsyncIterableResult<Row>;
40
+ /**
41
+ * Execute a `MongoQueryPlan` and return an async iterable of rows.
42
+ *
43
+ * The optional `options.signal` is threaded through
44
+ * `lower → adapter.lower → resolveValue → codec.encode` so codec authors
45
+ * who forward the signal to their underlying SDK get true cancellation
46
+ * of in-flight network calls. The runtime additionally observes the
47
+ * signal at two boundaries:
48
+ *
49
+ * - **Already-aborted at entry** — first `next()` throws
50
+ * `RUNTIME.ABORTED { phase: 'stream' }` before any work is done.
51
+ * (Inherited from `RuntimeCore.execute`.)
52
+ * - **Mid-encode abort** — surfaces as
53
+ * `RUNTIME.ABORTED { phase: 'encode' }` from inside `resolveValue`'s
54
+ * per-level `Promise.all` race.
55
+ *
56
+ * Mongo's read path decodes rows via `resultShape` (per ADR 209). The
57
+ * same `CodecCallContext` is forwarded into each `codec.decode(wire, ctx)`
58
+ * call, so async decoders that respect the signal get cancellation; the
59
+ * runtime itself does not currently emit a `phase: 'decode'` envelope.
60
+ */
61
+ execute<Row>(
62
+ plan: MongoQueryPlan<Row>,
63
+ options?: RuntimeExecuteOptions,
64
+ ): AsyncIterableResult<Row>;
28
65
  close(): Promise<void>;
29
66
  }
30
67
 
31
- class MongoRuntimeImpl implements MongoRuntime {
68
+ class MongoRuntimeImpl
69
+ extends RuntimeCore<MongoQueryPlan, MongoExecutionPlan, MongoMiddleware>
70
+ implements MongoRuntime
71
+ {
32
72
  readonly #adapter: MongoAdapter;
33
73
  readonly #driver: MongoDriver;
34
- readonly #middleware: readonly RuntimeMiddleware[];
35
- readonly #middlewareContext: RuntimeMiddlewareContext;
74
+ readonly #codecs: MongoCodecLookup;
36
75
 
37
76
  constructor(options: MongoRuntimeOptions) {
38
- this.#adapter = options.adapter;
39
- this.#driver = options.driver;
40
-
41
77
  const middleware = options.middleware ? [...options.middleware] : [];
78
+ const targetId = options.context.stack.target.targetId;
42
79
  for (const mw of middleware) {
43
- checkMiddlewareCompatibility(mw, 'mongo', options.targetId);
80
+ checkMiddlewareCompatibility(mw, 'mongo', targetId);
44
81
  }
45
- this.#middleware = middleware;
46
82
 
47
- this.#middlewareContext = {
48
- contract: options.contract,
83
+ const ctx: MongoMiddlewareContext = {
84
+ contract: options.context.contract,
49
85
  mode: options.mode ?? 'strict',
50
- now,
86
+ now: () => Date.now(),
51
87
  log: { info: noop, warn: noop, error: noop },
88
+ // ctx is only invoked by runWithMiddleware with execs this runtime lowered;
89
+ // the framework parameter type is the cross-family base.
90
+ contentHash: (exec) => computeMongoContentHash(exec as MongoExecutionPlan),
52
91
  };
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
92
 
74
- const wireCommand = await adapter.lower(plan);
93
+ super({ middleware, ctx });
75
94
 
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
- }
95
+ const adapterDescriptor = options.context.stack.adapter;
96
+ const adapterInstance = adapterDescriptor.create(options.context.stack);
97
+ this.#adapter = adapterInstance;
98
+ this.#driver = options.driver;
99
+ this.#codecs = options.context.codecs;
100
+ }
85
101
 
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;
102
+ protected override async lower(
103
+ plan: MongoQueryPlan,
104
+ ctx: CodecCallContext,
105
+ ): Promise<MongoExecutionPlan> {
106
+ return {
107
+ command: await this.#adapter.lower(plan, ctx),
108
+ meta: plan.meta,
109
+ ...ifDefined('resultShape', plan.resultShape),
110
+ };
111
+ }
94
112
 
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
- }
113
+ protected override runDriver(exec: MongoExecutionPlan): AsyncIterable<Record<string, unknown>> {
114
+ return this.#driver.execute<Record<string, unknown>>(exec.command);
115
+ }
103
116
 
104
- await mw.afterExecute(plan, { rowCount, latencyMs, completed }, ctx);
117
+ override execute<Row>(
118
+ plan: MongoQueryPlan & { readonly _row?: Row },
119
+ options?: RuntimeExecuteOptions,
120
+ ): AsyncIterableResult<Row> {
121
+ const self = this;
122
+ const signal = options?.signal;
123
+ const codecCtx: CodecCallContext = signal === undefined ? {} : { signal };
124
+ const generator = async function* (): AsyncGenerator<Row, void, unknown> {
125
+ checkAborted(codecCtx, 'stream');
126
+ const compiled = await self.runBeforeCompile(plan);
127
+ const exec = await self.lower(compiled, codecCtx);
128
+ const stream = runWithMiddleware<MongoExecutionPlan, Record<string, unknown>>(
129
+ exec,
130
+ self.middleware,
131
+ self.ctx,
132
+ () => self.runDriver(exec),
133
+ );
134
+ for await (const rawRow of stream) {
135
+ if (exec.resultShape === undefined) {
136
+ yield rawRow as Row;
137
+ } else {
138
+ // Source the collection from the lowered exec rather than the
139
+ // pre-lowering plan: a `runBeforeCompile` middleware is allowed to
140
+ // rewrite collection names during compilation, and the wire
141
+ // command carried by `exec` is always authoritative for what just
142
+ // ran.
143
+ const decoded = await decodeMongoRow(
144
+ rawRow,
145
+ exec.resultShape,
146
+ self.#codecs,
147
+ exec.command.collection,
148
+ codecCtx,
149
+ );
150
+ yield decoded as Row;
105
151
  }
106
152
  }
107
153
  };
108
-
109
- return new AsyncIterableResult(iterator());
154
+ return new AsyncIterableResult(generator());
110
155
  }
111
156
 
112
- async close(): Promise<void> {
157
+ override async close(): Promise<void> {
113
158
  await this.#driver.close();
114
159
  }
115
160
  }