@prisma-next/adapter-mongo 0.5.0-dev.7 → 0.5.0-dev.71

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/exports/runtime.ts"],"sourcesContent":["import type {\n ExecutionStack,\n RuntimeAdapterDescriptor,\n RuntimeAdapterInstance,\n} from '@prisma-next/framework-components/execution';\nimport type { MongoCodecRegistry } from '@prisma-next/mongo-codec';\nimport type { MongoAdapter } from '@prisma-next/mongo-lowering';\nimport { buildStandardCodecRegistry, mongoCodecDescriptors } from '../core/codecs';\nimport { createMongoAdapter } from '../mongo-adapter';\n\n/**\n * adapter-mongo deliberately does NOT import the `MongoRuntimeAdapterDescriptor` type alias from `@prisma-next/mongo-runtime`. The adapter package is downstream of the Mongo runtime package only conceptually; introducing a hard import would create a workspace dependency cycle (`mongo-runtime` consumes the runtime descriptor's `create(stack)` factory; `adapter-mongo` would then need `mongo-runtime` to type the\n * descriptor). The descriptor is shaped to satisfy the framework's `RuntimeAdapterDescriptor` plus the structural `MongoStaticContributions` (`codecs()`) that `@prisma-next/mongo-runtime` narrows to at composition time. This mirrors the `target-postgres` ↔ `sql-runtime` decoupling pattern.\n */\n\ninterface MongoRuntimeAdapterInstance\n extends RuntimeAdapterInstance<'mongo', 'mongo'>,\n MongoAdapter {}\n\nconst mongoRuntimeAdapterDescriptor: RuntimeAdapterDescriptor<\n 'mongo',\n 'mongo',\n MongoRuntimeAdapterInstance\n> & {\n readonly codecs: () => MongoCodecRegistry;\n} = {\n kind: 'adapter',\n id: 'mongo',\n familyId: 'mongo',\n targetId: 'mongo',\n version: '0.0.1',\n types: {\n codecTypes: {\n codecDescriptors: mongoCodecDescriptors,\n },\n },\n codecs: buildStandardCodecRegistry,\n create(_stack: ExecutionStack<'mongo', 'mongo'>): MongoRuntimeAdapterInstance {\n const adapter = createMongoAdapter();\n return {\n familyId: 'mongo' as const,\n targetId: 'mongo' as const,\n lower: adapter.lower.bind(adapter),\n };\n },\n};\n\nexport default mongoRuntimeAdapterDescriptor;\n"],"mappings":";;AAmBA,MAAM,gCAMF;CACF,MAAM;CACN,IAAI;CACJ,UAAU;CACV,UAAU;CACV,SAAS;CACT,OAAO,EACL,YAAY,EACV,kBAAkB,uBACnB,EACF;CACD,QAAQ;CACR,OAAO,QAAuE;EAC5E,MAAM,UAAU,oBAAoB;EACpC,OAAO;GACL,UAAU;GACV,UAAU;GACV,OAAO,QAAQ,MAAM,KAAK,QAAQ;GACnC;;CAEJ"}
package/package.json CHANGED
@@ -1,32 +1,35 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-mongo",
3
- "version": "0.5.0-dev.7",
3
+ "version": "0.5.0-dev.71",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "sideEffects": false,
6
7
  "description": "MongoDB adapter for Prisma Next (lowers commands to wire format)",
7
8
  "dependencies": {
8
9
  "arktype": "^2.1.29",
9
10
  "mongodb": "^6.16.0",
10
- "@prisma-next/config": "0.5.0-dev.7",
11
- "@prisma-next/target-mongo": "0.5.0-dev.7",
12
- "@prisma-next/contract": "0.5.0-dev.7",
13
- "@prisma-next/mongo-codec": "0.5.0-dev.7",
14
- "@prisma-next/framework-components": "0.5.0-dev.7",
15
- "@prisma-next/mongo-contract": "0.5.0-dev.7",
16
- "@prisma-next/mongo-lowering": "0.5.0-dev.7",
17
- "@prisma-next/mongo-query-ast": "0.5.0-dev.7",
18
- "@prisma-next/mongo-value": "0.5.0-dev.7",
19
- "@prisma-next/mongo-schema-ir": "0.5.0-dev.7",
20
- "@prisma-next/operations": "0.5.0-dev.7",
21
- "@prisma-next/mongo-wire": "0.5.0-dev.7",
22
- "@prisma-next/utils": "0.5.0-dev.7"
11
+ "@prisma-next/config": "0.5.0-dev.71",
12
+ "@prisma-next/contract": "0.5.0-dev.71",
13
+ "@prisma-next/framework-components": "0.5.0-dev.71",
14
+ "@prisma-next/target-mongo": "0.5.0-dev.71",
15
+ "@prisma-next/mongo-contract": "0.5.0-dev.71",
16
+ "@prisma-next/mongo-query-ast": "0.5.0-dev.71",
17
+ "@prisma-next/mongo-lowering": "0.5.0-dev.71",
18
+ "@prisma-next/mongo-schema-ir": "0.5.0-dev.71",
19
+ "@prisma-next/mongo-value": "0.5.0-dev.71",
20
+ "@prisma-next/mongo-codec": "0.5.0-dev.71",
21
+ "@prisma-next/utils": "0.5.0-dev.71",
22
+ "@prisma-next/operations": "0.5.0-dev.71",
23
+ "@prisma-next/mongo-wire": "0.5.0-dev.71"
23
24
  },
24
25
  "devDependencies": {
25
26
  "mongodb-memory-server": "10.4.3",
26
- "tsdown": "0.18.4",
27
+ "tsdown": "0.22.0",
27
28
  "typescript": "5.9.3",
28
- "vitest": "4.0.17",
29
- "@prisma-next/driver-mongo": "0.5.0-dev.7",
29
+ "vitest": "4.1.5",
30
+ "@prisma-next/driver-mongo": "0.5.0-dev.71",
31
+ "@prisma-next/mongo-contract-psl": "0.5.0-dev.71",
32
+ "@prisma-next/psl-parser": "0.5.0-dev.71",
30
33
  "@prisma-next/test-utils": "0.0.1",
31
34
  "@prisma-next/tsconfig": "0.0.0",
32
35
  "@prisma-next/tsdown": "0.0.0"
@@ -39,6 +42,7 @@
39
42
  ".": "./dist/index.mjs",
40
43
  "./codec-types": "./dist/codec-types.mjs",
41
44
  "./control": "./dist/control.mjs",
45
+ "./runtime": "./dist/runtime.mjs",
42
46
  "./package.json": "./package.json"
43
47
  },
44
48
  "main": "./dist/index.mjs",
@@ -1,4 +1,11 @@
1
- import { mongoCodec } from '@prisma-next/mongo-codec';
1
+ import type { CodecDescriptor, CodecTrait } from '@prisma-next/framework-components/codec';
2
+ import { voidParamsSchema } from '@prisma-next/framework-components/codec';
3
+ import {
4
+ type MongoCodec,
5
+ type MongoCodecRegistry,
6
+ mongoCodec,
7
+ newMongoCodecRegistry,
8
+ } from '@prisma-next/mongo-codec';
2
9
  import { ObjectId } from 'mongodb';
3
10
  import {
4
11
  MONGO_BOOLEAN_CODEC_ID,
@@ -12,64 +19,150 @@ import {
12
19
 
13
20
  export const mongoObjectIdCodec = mongoCodec({
14
21
  typeId: MONGO_OBJECTID_CODEC_ID,
15
- targetTypes: ['objectId'],
16
- traits: ['equality'],
17
22
  decode: (wire: ObjectId) => wire.toHexString(),
18
23
  encode: (value: string) => new ObjectId(value),
19
24
  });
20
25
 
21
26
  export const mongoStringCodec = mongoCodec({
22
27
  typeId: MONGO_STRING_CODEC_ID,
23
- targetTypes: ['string'],
24
- traits: ['equality', 'order', 'textual'],
25
28
  decode: (wire: string) => wire,
26
29
  encode: (value: string) => value,
27
30
  });
28
31
 
29
32
  export const mongoDoubleCodec = mongoCodec({
30
33
  typeId: MONGO_DOUBLE_CODEC_ID,
31
- targetTypes: ['double'],
32
- traits: ['equality', 'order', 'numeric'],
33
34
  decode: (wire: number) => wire,
34
35
  encode: (value: number) => value,
35
36
  });
36
37
 
37
38
  export const mongoInt32Codec = mongoCodec({
38
39
  typeId: MONGO_INT32_CODEC_ID,
39
- targetTypes: ['int'],
40
- traits: ['equality', 'order', 'numeric'],
41
40
  decode: (wire: number) => wire,
42
41
  encode: (value: number) => value,
43
42
  });
44
43
 
45
44
  export const mongoBooleanCodec = mongoCodec({
46
45
  typeId: MONGO_BOOLEAN_CODEC_ID,
47
- targetTypes: ['bool'],
48
- traits: ['equality', 'boolean'],
49
46
  decode: (wire: boolean) => wire,
50
47
  encode: (value: boolean) => value,
51
48
  });
52
49
 
53
50
  export const mongoDateCodec = mongoCodec({
54
51
  typeId: MONGO_DATE_CODEC_ID,
55
- targetTypes: ['date'],
56
- traits: ['equality', 'order'],
57
52
  decode: (wire: Date) => wire,
58
53
  encode: (value: Date) => value,
54
+ encodeJson: (value: Date) => value.toISOString(),
55
+ decodeJson: (json) => {
56
+ if (typeof json !== 'string') throw new Error('expected ISO date string');
57
+ return new Date(json);
58
+ },
59
59
  });
60
60
 
61
61
  export const mongoVectorCodec = mongoCodec({
62
62
  typeId: MONGO_VECTOR_CODEC_ID,
63
- targetTypes: ['vector'],
64
- traits: ['equality'],
65
63
  decode: (wire: readonly number[]) => wire,
66
64
  encode: (value: readonly number[]) => value,
67
- renderOutputType: (typeParams) => {
68
- const length = typeParams['length'];
69
- if (length === undefined) return undefined;
70
- if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
71
- throw new Error('renderOutputType: expected positive integer "length" for Vector');
72
- }
73
- return `Vector<${length}>`;
74
- },
75
65
  });
66
+
67
+ /**
68
+ * The canonical set of Mongo wire-type codecs.
69
+ *
70
+ * Single source of truth for both control- and runtime-plane adapter descriptors. Don't duplicate this list — import it.
71
+ */
72
+ export const mongoStandardCodecs = [
73
+ mongoObjectIdCodec,
74
+ mongoStringCodec,
75
+ mongoDoubleCodec,
76
+ mongoInt32Codec,
77
+ mongoBooleanCodec,
78
+ mongoDateCodec,
79
+ mongoVectorCodec,
80
+ ] as const;
81
+
82
+ /**
83
+ * Build a {@link CodecDescriptor} for a Mongo wire-type codec.
84
+ *
85
+ * Wraps an existing {@link MongoCodec} instance into a descriptor whose factory hands out the same shared codec. Mongo's full migration to descriptor-first authoring is tracked under TML-2324; for now the descriptor view is composed from the existing `mongoCodec()` outputs.
86
+ */
87
+ function descriptorFor<Id extends string>(
88
+ codec: MongoCodec<Id, readonly CodecTrait[]>,
89
+ metadata: {
90
+ readonly traits: readonly CodecTrait[];
91
+ readonly targetTypes: readonly string[];
92
+ readonly renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;
93
+ },
94
+ ): CodecDescriptor {
95
+ // The descriptor's `P` is structurally `Record<string, unknown>` for codecs that take params (Mongo `vector`); non-parameterized codecs ignore the slot. Cast through `unknown` to fit the `CodecDescriptor` slot's `(params: P) => …` typing without leaking a per-codec `P` into the heterogeneous descriptor list.
96
+ const renderOutputType = metadata.renderOutputType as
97
+ | CodecDescriptor['renderOutputType']
98
+ | undefined;
99
+ return {
100
+ codecId: codec.id,
101
+ traits: metadata.traits,
102
+ targetTypes: metadata.targetTypes,
103
+ paramsSchema: voidParamsSchema as CodecDescriptor['paramsSchema'],
104
+ isParameterized: false,
105
+ factory: (() => () => codec) as CodecDescriptor['factory'],
106
+ ...(renderOutputType !== undefined ? { renderOutputType } : {}),
107
+ };
108
+ }
109
+
110
+ const renderVectorOutputType = (typeParams: Record<string, unknown>): string | undefined => {
111
+ const length = typeParams['length'];
112
+ if (length === undefined) return undefined;
113
+ if (
114
+ typeof length !== 'number' ||
115
+ !Number.isFinite(length) ||
116
+ !Number.isInteger(length) ||
117
+ length <= 0
118
+ ) {
119
+ throw new Error('renderOutputType: expected positive integer "length" for Vector');
120
+ }
121
+ return `Vector<${length}>`;
122
+ };
123
+
124
+ /**
125
+ * Mongo wire-type codec descriptors. Static metadata for `traits`, `targetTypes`, and `renderOutputType` lives here (the descriptor shape) — `MongoCodec` itself is narrow and only carries the four conversion methods (TML-2357).
126
+ */
127
+ export const mongoCodecDescriptors: ReadonlyArray<CodecDescriptor> = [
128
+ descriptorFor(mongoObjectIdCodec, { traits: ['equality'], targetTypes: ['objectId'] }),
129
+ descriptorFor(mongoStringCodec, {
130
+ traits: ['equality', 'order', 'textual'],
131
+ targetTypes: ['string'],
132
+ }),
133
+ descriptorFor(mongoDoubleCodec, {
134
+ traits: ['equality', 'order', 'numeric'],
135
+ targetTypes: ['double'],
136
+ }),
137
+ descriptorFor(mongoInt32Codec, {
138
+ traits: ['equality', 'order', 'numeric'],
139
+ targetTypes: ['int'],
140
+ }),
141
+ descriptorFor(mongoBooleanCodec, { traits: ['equality', 'boolean'], targetTypes: ['bool'] }),
142
+ descriptorFor(mongoDateCodec, { traits: ['equality', 'order'], targetTypes: ['date'] }),
143
+ descriptorFor(mongoVectorCodec, {
144
+ traits: ['equality'],
145
+ targetTypes: ['vector'],
146
+ renderOutputType: renderVectorOutputType,
147
+ }),
148
+ ];
149
+
150
+ /**
151
+ * Lookup descriptor metadata by codec id — used by tests and for descriptor-side reads of static metadata.
152
+ */
153
+ export function mongoDescriptorById(codecId: string): CodecDescriptor | undefined {
154
+ return mongoCodecDescriptors.find((d) => d.codecId === codecId);
155
+ }
156
+
157
+ /**
158
+ * Build a {@link MongoCodecRegistry} preloaded with the standard Mongo wire-type codecs.
159
+ *
160
+ * Single point of truth for adapter-side codec construction: used by the legacy synchronous `createMongoAdapter()` factory and by the runtime adapter descriptor's `codecs()` getter. Userland code obtains a registry via the framework's execution-stack composition (see `createMongoExecutionContext`) instead of calling this directly.
161
+ */
162
+ export function buildStandardCodecRegistry(): MongoCodecRegistry {
163
+ const registry = newMongoCodecRegistry();
164
+ for (const codec of mongoStandardCodecs) {
165
+ registry.register(codec);
166
+ }
167
+ return registry;
168
+ }
@@ -18,7 +18,12 @@ function parseIndexKeys(keySpec: Record<string, unknown>): MongoIndexKey[] {
18
18
  return keys;
19
19
  }
20
20
 
21
- function isDefaultIdIndex(doc: Document): boolean {
21
+ /**
22
+ * Exported for unit tests to exercise the defensive `!key` guard; not part of
23
+ * the public API. Callers in this package use it via the `introspectSchema`
24
+ * pipeline only.
25
+ */
26
+ export function isDefaultIdIndex(doc: Document): boolean {
22
27
  const key = doc['key'] as Record<string, unknown> | undefined;
23
28
  if (!key) return false;
24
29
  const entries = Object.entries(key);
@@ -1,10 +1,10 @@
1
1
  import type { OperationDescriptor } from '@prisma-next/operations';
2
- import { MONGO_INT32_CODEC_ID, MONGO_VECTOR_CODEC_ID } from './codec-ids';
2
+ import { MONGO_VECTOR_CODEC_ID } from './codec-ids';
3
3
 
4
4
  export const mongoVectorNearOperation = Object.freeze({
5
5
  method: 'near',
6
- args: [{ codecId: MONGO_VECTOR_CODEC_ID, nullable: false }],
7
- returns: { codecId: MONGO_INT32_CODEC_ID, nullable: false },
6
+ self: { codecId: MONGO_VECTOR_CODEC_ID },
7
+ impl: () => undefined as never,
8
8
  }) satisfies OperationDescriptor;
9
9
 
10
10
  export const mongoVectorOperationDescriptors: readonly OperationDescriptor[] = [
@@ -8,15 +8,7 @@ export {
8
8
  } from '../core/mongo-control-driver';
9
9
  export { createMongoRunnerDeps, extractDb } from '../core/runner-deps';
10
10
 
11
- import {
12
- mongoBooleanCodec,
13
- mongoDateCodec,
14
- mongoDoubleCodec,
15
- mongoInt32Codec,
16
- mongoObjectIdCodec,
17
- mongoStringCodec,
18
- mongoVectorCodec,
19
- } from '../core/codecs';
11
+ import { mongoCodecDescriptors } from '../core/codecs';
20
12
 
21
13
  const mongoAdapterDescriptor: ControlAdapterDescriptor<'mongo', 'mongo'> = {
22
14
  kind: 'adapter',
@@ -34,15 +26,7 @@ const mongoAdapterDescriptor: ControlAdapterDescriptor<'mongo', 'mongo'> = {
34
26
  ]),
35
27
  types: {
36
28
  codecTypes: {
37
- codecInstances: [
38
- mongoObjectIdCodec,
39
- mongoStringCodec,
40
- mongoDoubleCodec,
41
- mongoInt32Codec,
42
- mongoBooleanCodec,
43
- mongoDateCodec,
44
- mongoVectorCodec,
45
- ],
29
+ codecDescriptors: mongoCodecDescriptors,
46
30
  import: {
47
31
  package: '@prisma-next/adapter-mongo/codec-types',
48
32
  named: 'CodecTypes',
@@ -0,0 +1,48 @@
1
+ import type {
2
+ ExecutionStack,
3
+ RuntimeAdapterDescriptor,
4
+ RuntimeAdapterInstance,
5
+ } from '@prisma-next/framework-components/execution';
6
+ import type { MongoCodecRegistry } from '@prisma-next/mongo-codec';
7
+ import type { MongoAdapter } from '@prisma-next/mongo-lowering';
8
+ import { buildStandardCodecRegistry, mongoCodecDescriptors } from '../core/codecs';
9
+ import { createMongoAdapter } from '../mongo-adapter';
10
+
11
+ /**
12
+ * adapter-mongo deliberately does NOT import the `MongoRuntimeAdapterDescriptor` type alias from `@prisma-next/mongo-runtime`. The adapter package is downstream of the Mongo runtime package only conceptually; introducing a hard import would create a workspace dependency cycle (`mongo-runtime` consumes the runtime descriptor's `create(stack)` factory; `adapter-mongo` would then need `mongo-runtime` to type the
13
+ * descriptor). The descriptor is shaped to satisfy the framework's `RuntimeAdapterDescriptor` plus the structural `MongoStaticContributions` (`codecs()`) that `@prisma-next/mongo-runtime` narrows to at composition time. This mirrors the `target-postgres` ↔ `sql-runtime` decoupling pattern.
14
+ */
15
+
16
+ interface MongoRuntimeAdapterInstance
17
+ extends RuntimeAdapterInstance<'mongo', 'mongo'>,
18
+ MongoAdapter {}
19
+
20
+ const mongoRuntimeAdapterDescriptor: RuntimeAdapterDescriptor<
21
+ 'mongo',
22
+ 'mongo',
23
+ MongoRuntimeAdapterInstance
24
+ > & {
25
+ readonly codecs: () => MongoCodecRegistry;
26
+ } = {
27
+ kind: 'adapter',
28
+ id: 'mongo',
29
+ familyId: 'mongo',
30
+ targetId: 'mongo',
31
+ version: '0.0.1',
32
+ types: {
33
+ codecTypes: {
34
+ codecDescriptors: mongoCodecDescriptors,
35
+ },
36
+ },
37
+ codecs: buildStandardCodecRegistry,
38
+ create(_stack: ExecutionStack<'mongo', 'mongo'>): MongoRuntimeAdapterInstance {
39
+ const adapter = createMongoAdapter();
40
+ return {
41
+ familyId: 'mongo' as const,
42
+ targetId: 'mongo' as const,
43
+ lower: adapter.lower.bind(adapter),
44
+ };
45
+ },
46
+ };
47
+
48
+ export default mongoRuntimeAdapterDescriptor;
package/src/lowering.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { CodecCallContext } from '@prisma-next/framework-components/codec';
2
+ import type { MongoCodecRegistry } from '@prisma-next/mongo-codec';
1
3
  import type {
2
4
  MongoAggExpr,
3
5
  MongoAggExprVisitor,
@@ -136,16 +138,20 @@ export function lowerAggExpr(expr: MongoAggExpr): unknown {
136
138
  return expr.accept(aggExprLoweringVisitor);
137
139
  }
138
140
 
139
- export function lowerFilter(filter: MongoFilterExpr): Document {
141
+ export async function lowerFilter(
142
+ filter: MongoFilterExpr,
143
+ codecs: MongoCodecRegistry,
144
+ ctx: CodecCallContext,
145
+ ): Promise<Document> {
140
146
  switch (filter.kind) {
141
147
  case 'field':
142
- return { [filter.field]: { [filter.op]: resolveValue(filter.value) } };
148
+ return { [filter.field]: { [filter.op]: await resolveValue(filter.value, codecs, ctx) } };
143
149
  case 'and':
144
- return { $and: filter.exprs.map((e) => lowerFilter(e)) };
150
+ return { $and: await Promise.all(filter.exprs.map((e) => lowerFilter(e, codecs, ctx))) };
145
151
  case 'or':
146
- return { $or: filter.exprs.map((e) => lowerFilter(e)) };
152
+ return { $or: await Promise.all(filter.exprs.map((e) => lowerFilter(e, codecs, ctx))) };
147
153
  case 'not':
148
- return { $nor: [lowerFilter(filter.expr)] };
154
+ return { $nor: [await lowerFilter(filter.expr, codecs, ctx)] };
149
155
  case 'exists':
150
156
  return { [filter.field]: { $exists: filter.exists } };
151
157
  case 'expr':
@@ -198,10 +204,14 @@ function lowerWindowField(wf: MongoWindowField): Record<string, unknown> {
198
204
  return result;
199
205
  }
200
206
 
201
- export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
207
+ export async function lowerStage(
208
+ stage: MongoPipelineStage,
209
+ codecs: MongoCodecRegistry,
210
+ ctx: CodecCallContext,
211
+ ): Promise<Record<string, unknown>> {
202
212
  switch (stage.kind) {
203
213
  case 'match':
204
- return { $match: lowerFilter(stage.filter) };
214
+ return { $match: await lowerFilter(stage.filter, codecs, ctx) };
205
215
  case 'project': {
206
216
  const projection: Record<string, unknown> = {};
207
217
  for (const [key, val] of Object.entries(stage.projection)) {
@@ -223,7 +233,9 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
223
233
  if (stage.localField !== undefined) lookup['localField'] = stage.localField;
224
234
  if (stage.foreignField !== undefined) lookup['foreignField'] = stage.foreignField;
225
235
  if (stage.pipeline) {
226
- lookup['pipeline'] = stage.pipeline.map((s) => lowerStage(s));
236
+ lookup['pipeline'] = await Promise.all(
237
+ stage.pipeline.map((s) => lowerStage(s, codecs, ctx)),
238
+ );
227
239
  }
228
240
  if (stage.let_) {
229
241
  lookup['let'] = lowerExprRecord(stage.let_);
@@ -264,7 +276,9 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
264
276
  case 'unionWith': {
265
277
  const unionWith: Record<string, unknown> = { coll: stage.collection };
266
278
  if (stage.pipeline) {
267
- unionWith['pipeline'] = stage.pipeline.map((s) => lowerStage(s));
279
+ unionWith['pipeline'] = await Promise.all(
280
+ stage.pipeline.map((s) => lowerStage(s, codecs, ctx)),
281
+ );
268
282
  }
269
283
  return { $unionWith: unionWith };
270
284
  }
@@ -294,7 +308,7 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
294
308
  if (stage.spherical !== undefined) geoNear['spherical'] = stage.spherical;
295
309
  if (stage.maxDistance !== undefined) geoNear['maxDistance'] = stage.maxDistance;
296
310
  if (stage.minDistance !== undefined) geoNear['minDistance'] = stage.minDistance;
297
- if (stage.query) geoNear['query'] = lowerFilter(stage.query);
311
+ if (stage.query) geoNear['query'] = await lowerFilter(stage.query, codecs, ctx);
298
312
  if (stage.key !== undefined) geoNear['key'] = stage.key;
299
313
  if (stage.distanceMultiplier !== undefined)
300
314
  geoNear['distanceMultiplier'] = stage.distanceMultiplier;
@@ -302,9 +316,18 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
302
316
  return { $geoNear: geoNear };
303
317
  }
304
318
  case 'facet': {
319
+ const facetEntries = Object.entries(stage.facets);
320
+ const facetPipelines = await Promise.all(
321
+ facetEntries.map(([, pipeline]) =>
322
+ Promise.all(pipeline.map((s) => lowerStage(s, codecs, ctx))),
323
+ ),
324
+ );
305
325
  const facet: Record<string, unknown> = {};
306
- for (const [key, pipeline] of Object.entries(stage.facets)) {
307
- facet[key] = pipeline.map((s) => lowerStage(s));
326
+ for (let i = 0; i < facetEntries.length; i++) {
327
+ const entry = facetEntries[i];
328
+ if (entry) {
329
+ facet[entry[0]] = facetPipelines[i];
330
+ }
308
331
  }
309
332
  return { $facet: facet };
310
333
  }
@@ -319,7 +342,11 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
319
342
  if (stage.maxDepth !== undefined) graphLookup['maxDepth'] = stage.maxDepth;
320
343
  if (stage.depthField !== undefined) graphLookup['depthField'] = stage.depthField;
321
344
  if (stage.restrictSearchWithMatch)
322
- graphLookup['restrictSearchWithMatch'] = lowerFilter(stage.restrictSearchWithMatch);
345
+ graphLookup['restrictSearchWithMatch'] = await lowerFilter(
346
+ stage.restrictSearchWithMatch,
347
+ codecs,
348
+ ctx,
349
+ );
323
350
  return { $graphLookup: graphLookup };
324
351
  }
325
352
  case 'merge': {
@@ -327,7 +354,7 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
327
354
  if (stage.on !== undefined) merge['on'] = stage.on;
328
355
  if (stage.whenMatched !== undefined) {
329
356
  merge['whenMatched'] = Array.isArray(stage.whenMatched)
330
- ? stage.whenMatched.map((s) => lowerStage(s))
357
+ ? await Promise.all(stage.whenMatched.map((s) => lowerStage(s, codecs, ctx)))
331
358
  : stage.whenMatched;
332
359
  }
333
360
  if (stage.whenNotMatched !== undefined) merge['whenNotMatched'] = stage.whenNotMatched;
@@ -395,8 +422,10 @@ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
395
422
  }
396
423
  }
397
424
 
398
- export function lowerPipeline(
425
+ export async function lowerPipeline(
399
426
  stages: ReadonlyArray<MongoPipelineStage>,
400
- ): Array<Record<string, unknown>> {
401
- return stages.map(lowerStage);
427
+ codecs: MongoCodecRegistry,
428
+ ctx: CodecCallContext,
429
+ ): Promise<Array<Record<string, unknown>>> {
430
+ return Promise.all(stages.map((s) => lowerStage(s, codecs, ctx)));
402
431
  }