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