@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.
- package/README.md +76 -5
- package/dist/index.d.mts +154 -13
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +178 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -16
- package/src/codecs/decoding.ts +170 -0
- package/src/exports/index.ts +17 -0
- package/src/mongo-execution-plan.ts +28 -0
- package/src/mongo-execution-stack.ts +171 -0
- package/src/mongo-middleware.ts +17 -6
- package/src/mongo-runtime.ts +114 -73
package/src/mongo-runtime.ts
CHANGED
|
@@ -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
|
|
32
|
+
readonly context: MongoExecutionContext;
|
|
19
33
|
readonly driver: MongoDriver;
|
|
20
|
-
readonly
|
|
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
|
-
|
|
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
|
|
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 #
|
|
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',
|
|
79
|
+
checkMiddlewareCompatibility(mw, 'mongo', targetId);
|
|
44
80
|
}
|
|
45
|
-
this.#middleware = middleware;
|
|
46
81
|
|
|
47
|
-
|
|
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
|
-
|
|
89
|
+
super({ middleware, ctx });
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
}
|