@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/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/mongo-runtime",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "MongoDB runtime implementation for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@prisma-next/
|
|
9
|
-
"@prisma-next/
|
|
10
|
-
"@prisma-next/mongo-lowering": "0.5.0-dev.
|
|
11
|
-
"@prisma-next/mongo-query-ast": "0.5.0-dev.
|
|
8
|
+
"@prisma-next/framework-components": "0.5.0-dev.42",
|
|
9
|
+
"@prisma-next/contract": "0.5.0-dev.42",
|
|
10
|
+
"@prisma-next/mongo-lowering": "0.5.0-dev.42",
|
|
11
|
+
"@prisma-next/mongo-query-ast": "0.5.0-dev.42",
|
|
12
|
+
"@prisma-next/mongo-wire": "0.5.0-dev.42",
|
|
13
|
+
"@prisma-next/mongo-codec": "0.5.0-dev.42",
|
|
14
|
+
"@prisma-next/utils": "0.5.0-dev.42"
|
|
12
15
|
},
|
|
13
16
|
"devDependencies": {
|
|
14
17
|
"mongodb": "^6.16.0",
|
|
@@ -16,19 +19,18 @@
|
|
|
16
19
|
"tsdown": "0.18.4",
|
|
17
20
|
"typescript": "5.9.3",
|
|
18
21
|
"vitest": "4.0.17",
|
|
19
|
-
"@prisma-next/
|
|
20
|
-
"@prisma-next/
|
|
21
|
-
"@prisma-next/
|
|
22
|
-
"@prisma-next/mongo
|
|
23
|
-
"@prisma-next/
|
|
24
|
-
"@prisma-next/mongo-
|
|
25
|
-
"@prisma-next/mongo
|
|
26
|
-
"@prisma-next/
|
|
22
|
+
"@prisma-next/adapter-mongo": "0.5.0-dev.42",
|
|
23
|
+
"@prisma-next/middleware-telemetry": "0.5.0-dev.42",
|
|
24
|
+
"@prisma-next/mongo-contract-ts": "0.5.0-dev.42",
|
|
25
|
+
"@prisma-next/family-mongo": "0.5.0-dev.42",
|
|
26
|
+
"@prisma-next/mongo-query-builder": "0.5.0-dev.42",
|
|
27
|
+
"@prisma-next/mongo-value": "0.5.0-dev.42",
|
|
28
|
+
"@prisma-next/driver-mongo": "0.5.0-dev.42",
|
|
29
|
+
"@prisma-next/target-mongo": "0.5.0-dev.42",
|
|
30
|
+
"@prisma-next/mongo-contract": "0.5.0-dev.42",
|
|
27
31
|
"@prisma-next/test-utils": "0.0.1",
|
|
28
|
-
"@prisma-next/target-mongo": "0.5.0-dev.4",
|
|
29
32
|
"@prisma-next/tsconfig": "0.0.0",
|
|
30
|
-
"@prisma-next/tsdown": "0.0.0"
|
|
31
|
-
"@prisma-next/mongo-contract": "0.5.0-dev.4"
|
|
33
|
+
"@prisma-next/tsdown": "0.0.0"
|
|
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
|
+
}
|
package/src/exports/index.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
export type { RuntimeTargetInstance } from '@prisma-next/framework-components/execution';
|
|
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';
|
|
1
18
|
export type { MongoMiddleware, MongoMiddlewareContext } from '../mongo-middleware';
|
|
2
19
|
export type { MongoRuntime, MongoRuntimeOptions } from '../mongo-runtime';
|
|
3
20
|
export { createMongoRuntime } from '../mongo-runtime';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExecutionPlan } from '@prisma-next/framework-components/runtime';
|
|
2
|
+
import type { MongoResultShape } from '@prisma-next/mongo-query-ast/execution';
|
|
3
|
+
import type { AnyMongoWireCommand } from '@prisma-next/mongo-wire';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mongo-domain execution plan: a query lowered to the wire-command shape
|
|
7
|
+
* that a Mongo driver can run.
|
|
8
|
+
*
|
|
9
|
+
* The plan carries:
|
|
10
|
+
* - `command` — the wire command (e.g. `InsertOneWireCommand`,
|
|
11
|
+
* `AggregateWireCommand`) produced by `MongoAdapter.lower(plan)`
|
|
12
|
+
* - `meta` — family-agnostic plan metadata (target, lane, hashes, ...)
|
|
13
|
+
* - `_row` — phantom row type, propagated from the originating
|
|
14
|
+
* `MongoQueryPlan`
|
|
15
|
+
*
|
|
16
|
+
* Extends the framework-level `ExecutionPlan<Row>` marker so generic SPIs
|
|
17
|
+
* (`RuntimeExecutor<MongoExecutionPlan>`,
|
|
18
|
+
* `RuntimeMiddleware<MongoExecutionPlan>`) can be parameterized over it.
|
|
19
|
+
*
|
|
20
|
+
* Lives in the runtime layer (alongside `MongoRuntime`) because the wire
|
|
21
|
+
* command shape lives in the transport layer (`@prisma-next/mongo-wire`),
|
|
22
|
+
* which the lanes layer (`mongo-query-ast`, where `MongoQueryPlan` lives)
|
|
23
|
+
* cannot depend on.
|
|
24
|
+
*/
|
|
25
|
+
export interface MongoExecutionPlan<Row = unknown> extends ExecutionPlan<Row> {
|
|
26
|
+
readonly command: AnyMongoWireCommand;
|
|
27
|
+
readonly resultShape?: MongoResultShape;
|
|
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
|
+
}
|
package/src/mongo-middleware.ts
CHANGED
|
@@ -3,20 +3,31 @@ import type {
|
|
|
3
3
|
RuntimeMiddleware,
|
|
4
4
|
RuntimeMiddlewareContext,
|
|
5
5
|
} from '@prisma-next/framework-components/runtime';
|
|
6
|
-
import type {
|
|
6
|
+
import type { MongoExecutionPlan } from './mongo-execution-plan';
|
|
7
7
|
|
|
8
8
|
export interface MongoMiddlewareContext extends RuntimeMiddlewareContext {}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Mongo-domain middleware. Extends the framework `RuntimeMiddleware`
|
|
12
|
+
* parameterized over `MongoExecutionPlan` because `runWithMiddleware`
|
|
13
|
+
* (driven by `RuntimeCore`) invokes the lifecycle hooks with the
|
|
14
|
+
* post-lowering plan.
|
|
15
|
+
*
|
|
16
|
+
* `familyId` is optional so generic cross-family middleware (e.g.
|
|
17
|
+
* telemetry) — which carry no `familyId` — remain assignable. When
|
|
18
|
+
* present, it must be `'mongo'`; the runtime rejects mismatches at
|
|
19
|
+
* construction time via `checkMiddlewareCompatibility`.
|
|
20
|
+
*/
|
|
21
|
+
export interface MongoMiddleware extends RuntimeMiddleware<MongoExecutionPlan> {
|
|
22
|
+
readonly familyId?: 'mongo';
|
|
23
|
+
beforeExecute?(plan: MongoExecutionPlan, ctx: MongoMiddlewareContext): Promise<void>;
|
|
13
24
|
onRow?(
|
|
14
25
|
row: Record<string, unknown>,
|
|
15
|
-
plan:
|
|
26
|
+
plan: MongoExecutionPlan,
|
|
16
27
|
ctx: MongoMiddlewareContext,
|
|
17
28
|
): Promise<void>;
|
|
18
29
|
afterExecute?(
|
|
19
|
-
plan:
|
|
30
|
+
plan: MongoExecutionPlan,
|
|
20
31
|
result: AfterExecuteResult,
|
|
21
32
|
ctx: MongoMiddlewareContext,
|
|
22
33
|
): Promise<void>;
|