@prisma-next/adapter-mongo 0.11.0 → 0.12.0-dev.10

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/src/lowering.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  } from '@prisma-next/mongo-query-ast/execution';
12
12
  import { isExprArray, isRecordArgs } from '@prisma-next/mongo-query-ast/execution';
13
13
  import type { Document } from '@prisma-next/mongo-value';
14
+ import { blindCast } from '@prisma-next/utils/casts';
14
15
  import { resolveValue } from './resolve-value';
15
16
 
16
17
  // Biome flags `{ then: ... }` as a thenable object (noThenProperty). Build via Object.fromEntries to avoid.
@@ -138,6 +139,36 @@ export function lowerAggExpr(expr: MongoAggExpr): unknown {
138
139
  return expr.accept(aggExprLoweringVisitor);
139
140
  }
140
141
 
142
+ /**
143
+ * Structural phase of filter lowering: transforms the filter AST into a
144
+ * plain object without resolving any `MongoParamRef` leaves. Field filter
145
+ * values remain as `MongoValue` (which includes `MongoParamRef`), so the
146
+ * returned document can be passed to `resolveDraftDoc` in the resolve phase.
147
+ * Synchronous — no codec calls.
148
+ */
149
+ export function structuralLowerFilter(filter: MongoFilterExpr): Record<string, unknown> {
150
+ switch (filter.kind) {
151
+ case 'field':
152
+ return { [filter.field]: { [filter.op]: filter.value } };
153
+ case 'and':
154
+ return { $and: filter.exprs.map((e) => structuralLowerFilter(e)) };
155
+ case 'or':
156
+ return { $or: filter.exprs.map((e) => structuralLowerFilter(e)) };
157
+ case 'not':
158
+ return { $nor: [structuralLowerFilter(filter.expr)] };
159
+ case 'exists':
160
+ return { [filter.field]: { $exists: filter.exists } };
161
+ case 'expr':
162
+ return { $expr: lowerAggExpr(filter.aggExpr) };
163
+ default: {
164
+ const _exhaustive: never = filter;
165
+ throw new Error(
166
+ `Unhandled filter kind: ${blindCast<MongoFilterExpr, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
167
+ );
168
+ }
169
+ }
170
+ }
171
+
141
172
  export async function lowerFilter(
142
173
  filter: MongoFilterExpr,
143
174
  codecs: MongoCodecRegistry,
@@ -158,7 +189,9 @@ export async function lowerFilter(
158
189
  return { $expr: lowerAggExpr(filter.aggExpr) };
159
190
  default: {
160
191
  const _exhaustive: never = filter;
161
- throw new Error(`Unhandled filter kind: ${(_exhaustive as MongoFilterExpr).kind}`);
192
+ throw new Error(
193
+ `Unhandled filter kind: ${blindCast<MongoFilterExpr, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
194
+ );
162
195
  }
163
196
  }
164
197
  }
@@ -167,6 +200,12 @@ function isAggExprNode(value: object): value is MongoAggExpr {
167
200
  return 'accept' in value && typeof value.accept === 'function';
168
201
  }
169
202
 
203
+ function isAggExprArray(
204
+ val: MongoAggExpr | ReadonlyArray<MongoAggExpr>,
205
+ ): val is ReadonlyArray<MongoAggExpr> {
206
+ return Array.isArray(val);
207
+ }
208
+
170
209
  function lowerGroupId(groupId: MongoGroupId): unknown {
171
210
  if (groupId === null) return null;
172
211
  if (isAggExprNode(groupId)) return lowerAggExpr(groupId);
@@ -178,10 +217,10 @@ function lowerExprRecord(
178
217
  ): Record<string, unknown> {
179
218
  const result: Record<string, unknown> = {};
180
219
  for (const [key, val] of Object.entries(fields)) {
181
- if (Array.isArray(val)) {
182
- result[key] = val.map((v: MongoAggExpr) => lowerAggExpr(v));
220
+ if (isAggExprArray(val)) {
221
+ result[key] = val.map((v) => lowerAggExpr(v));
183
222
  } else {
184
- result[key] = lowerAggExpr(val as MongoAggExpr);
223
+ result[key] = lowerAggExpr(val);
185
224
  }
186
225
  }
187
226
  return result;
@@ -417,7 +456,9 @@ export async function lowerStage(
417
456
  }
418
457
  default: {
419
458
  const _exhaustive: never = stage;
420
- throw new Error(`Unhandled stage kind: ${(_exhaustive as MongoPipelineStage).kind}`);
459
+ throw new Error(
460
+ `Unhandled stage kind: ${blindCast<MongoPipelineStage, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
461
+ );
421
462
  }
422
463
  }
423
464
  }
@@ -429,3 +470,218 @@ export async function lowerPipeline(
429
470
  ): Promise<Array<Record<string, unknown>>> {
430
471
  return Promise.all(stages.map((s) => lowerStage(s, codecs, ctx)));
431
472
  }
473
+
474
+ /**
475
+ * Structural phase of stage lowering: mirrors `lowerStage` but defers all
476
+ * `MongoParamRef` resolution. Filter sub-documents within stages (e.g.
477
+ * `$match`, `$geoNear.query`, `$graphLookup.restrictSearchWithMatch`) are
478
+ * produced by `structuralLowerFilter` and therefore retain `MongoParamRef`
479
+ * leaves. Sub-pipelines (e.g. `$lookup.pipeline`, `$facet.*`) recurse via
480
+ * `structuralLowerPipeline`. Synchronous — no codec calls.
481
+ */
482
+ export function structuralLowerStage(stage: MongoPipelineStage): Record<string, unknown> {
483
+ switch (stage.kind) {
484
+ case 'match':
485
+ return { $match: structuralLowerFilter(stage.filter) };
486
+ case 'project': {
487
+ const projection: Record<string, unknown> = {};
488
+ for (const [key, val] of Object.entries(stage.projection)) {
489
+ projection[key] = lowerProjectionValue(val);
490
+ }
491
+ return { $project: projection };
492
+ }
493
+ case 'sort':
494
+ return { $sort: { ...stage.sort } };
495
+ case 'limit':
496
+ return { $limit: stage.limit };
497
+ case 'skip':
498
+ return { $skip: stage.skip };
499
+ case 'lookup': {
500
+ const lookup: Record<string, unknown> = {
501
+ from: stage.from,
502
+ as: stage.as,
503
+ };
504
+ if (stage.localField !== undefined) lookup['localField'] = stage.localField;
505
+ if (stage.foreignField !== undefined) lookup['foreignField'] = stage.foreignField;
506
+ if (stage.pipeline) {
507
+ lookup['pipeline'] = structuralLowerPipeline(stage.pipeline);
508
+ }
509
+ if (stage.let_) {
510
+ lookup['let'] = lowerExprRecord(stage.let_);
511
+ }
512
+ return { $lookup: lookup };
513
+ }
514
+ case 'unwind': {
515
+ const unwind: Record<string, unknown> = {
516
+ path: stage.path,
517
+ preserveNullAndEmptyArrays: stage.preserveNullAndEmptyArrays,
518
+ };
519
+ if (stage.includeArrayIndex !== undefined) {
520
+ unwind['includeArrayIndex'] = stage.includeArrayIndex;
521
+ }
522
+ return { $unwind: unwind };
523
+ }
524
+ case 'group': {
525
+ const group: Record<string, unknown> = { _id: lowerGroupId(stage.groupId) };
526
+ for (const [key, acc] of Object.entries(stage.accumulators)) {
527
+ group[key] = lowerAggExpr(acc);
528
+ }
529
+ return { $group: group };
530
+ }
531
+ case 'addFields':
532
+ return { $addFields: lowerExprRecord(stage.fields) };
533
+ case 'replaceRoot':
534
+ return { $replaceRoot: { newRoot: lowerAggExpr(stage.newRoot) } };
535
+ case 'count':
536
+ return { $count: stage.field };
537
+ case 'sortByCount':
538
+ return { $sortByCount: lowerAggExpr(stage.expr) };
539
+ case 'sample':
540
+ return { $sample: { size: stage.size } };
541
+ case 'redact':
542
+ return { $redact: lowerAggExpr(stage.expr) };
543
+ case 'out':
544
+ return { $out: stage.db ? { db: stage.db, coll: stage.collection } : stage.collection };
545
+ case 'unionWith': {
546
+ const unionWith: Record<string, unknown> = { coll: stage.collection };
547
+ if (stage.pipeline) {
548
+ unionWith['pipeline'] = structuralLowerPipeline(stage.pipeline);
549
+ }
550
+ return { $unionWith: unionWith };
551
+ }
552
+ case 'bucket': {
553
+ const bucket: Record<string, unknown> = {
554
+ groupBy: lowerAggExpr(stage.groupBy),
555
+ boundaries: [...stage.boundaries],
556
+ };
557
+ if (stage.default_ !== undefined) bucket['default'] = stage.default_;
558
+ if (stage.output) bucket['output'] = lowerExprRecord(stage.output);
559
+ return { $bucket: bucket };
560
+ }
561
+ case 'bucketAuto': {
562
+ const bucketAuto: Record<string, unknown> = {
563
+ groupBy: lowerAggExpr(stage.groupBy),
564
+ buckets: stage.buckets,
565
+ };
566
+ if (stage.output) bucketAuto['output'] = lowerExprRecord(stage.output);
567
+ if (stage.granularity !== undefined) bucketAuto['granularity'] = stage.granularity;
568
+ return { $bucketAuto: bucketAuto };
569
+ }
570
+ case 'geoNear': {
571
+ const geoNear: Record<string, unknown> = {
572
+ near: stage.near,
573
+ distanceField: stage.distanceField,
574
+ };
575
+ if (stage.spherical !== undefined) geoNear['spherical'] = stage.spherical;
576
+ if (stage.maxDistance !== undefined) geoNear['maxDistance'] = stage.maxDistance;
577
+ if (stage.minDistance !== undefined) geoNear['minDistance'] = stage.minDistance;
578
+ if (stage.query) geoNear['query'] = structuralLowerFilter(stage.query);
579
+ if (stage.key !== undefined) geoNear['key'] = stage.key;
580
+ if (stage.distanceMultiplier !== undefined)
581
+ geoNear['distanceMultiplier'] = stage.distanceMultiplier;
582
+ if (stage.includeLocs !== undefined) geoNear['includeLocs'] = stage.includeLocs;
583
+ return { $geoNear: geoNear };
584
+ }
585
+ case 'facet': {
586
+ const facet: Record<string, unknown> = {};
587
+ for (const [key, pipeline] of Object.entries(stage.facets)) {
588
+ facet[key] = structuralLowerPipeline(pipeline);
589
+ }
590
+ return { $facet: facet };
591
+ }
592
+ case 'graphLookup': {
593
+ const graphLookup: Record<string, unknown> = {
594
+ from: stage.from,
595
+ startWith: lowerAggExpr(stage.startWith),
596
+ connectFromField: stage.connectFromField,
597
+ connectToField: stage.connectToField,
598
+ as: stage.as,
599
+ };
600
+ if (stage.maxDepth !== undefined) graphLookup['maxDepth'] = stage.maxDepth;
601
+ if (stage.depthField !== undefined) graphLookup['depthField'] = stage.depthField;
602
+ if (stage.restrictSearchWithMatch)
603
+ graphLookup['restrictSearchWithMatch'] = structuralLowerFilter(
604
+ stage.restrictSearchWithMatch,
605
+ );
606
+ return { $graphLookup: graphLookup };
607
+ }
608
+ case 'merge': {
609
+ const merge: Record<string, unknown> = { into: stage.into };
610
+ if (stage.on !== undefined) merge['on'] = stage.on;
611
+ if (stage.whenMatched !== undefined) {
612
+ merge['whenMatched'] = Array.isArray(stage.whenMatched)
613
+ ? structuralLowerPipeline(stage.whenMatched)
614
+ : stage.whenMatched;
615
+ }
616
+ if (stage.whenNotMatched !== undefined) merge['whenNotMatched'] = stage.whenNotMatched;
617
+ return { $merge: merge };
618
+ }
619
+ case 'setWindowFields': {
620
+ const swf: Record<string, unknown> = {};
621
+ if (stage.partitionBy) swf['partitionBy'] = lowerAggExpr(stage.partitionBy);
622
+ if (stage.sortBy) swf['sortBy'] = { ...stage.sortBy };
623
+ const output: Record<string, unknown> = {};
624
+ for (const [key, wf] of Object.entries(stage.output)) {
625
+ output[key] = lowerWindowField(wf);
626
+ }
627
+ swf['output'] = output;
628
+ return { $setWindowFields: swf };
629
+ }
630
+ case 'densify': {
631
+ const densify: Record<string, unknown> = {
632
+ field: stage.field,
633
+ range: { ...stage.range },
634
+ };
635
+ if (stage.partitionByFields) densify['partitionByFields'] = [...stage.partitionByFields];
636
+ return { $densify: densify };
637
+ }
638
+ case 'fill': {
639
+ const fill: Record<string, unknown> = {};
640
+ if (stage.partitionBy) fill['partitionBy'] = lowerAggExpr(stage.partitionBy);
641
+ if (stage.partitionByFields) fill['partitionByFields'] = [...stage.partitionByFields];
642
+ if (stage.sortBy) fill['sortBy'] = { ...stage.sortBy };
643
+ const output: Record<string, unknown> = {};
644
+ for (const [key, fo] of Object.entries(stage.output)) {
645
+ const entry: Record<string, unknown> = {};
646
+ if (fo.method !== undefined) entry['method'] = fo.method;
647
+ if (fo.value !== undefined) entry['value'] = lowerAggExpr(fo.value);
648
+ output[key] = entry;
649
+ }
650
+ fill['output'] = output;
651
+ return { $fill: fill };
652
+ }
653
+ case 'search': {
654
+ const search: Record<string, unknown> = { ...stage.config };
655
+ if (stage.index !== undefined) search['index'] = stage.index;
656
+ return { $search: search };
657
+ }
658
+ case 'searchMeta': {
659
+ const searchMeta: Record<string, unknown> = { ...stage.config };
660
+ if (stage.index !== undefined) searchMeta['index'] = stage.index;
661
+ return { $searchMeta: searchMeta };
662
+ }
663
+ case 'vectorSearch': {
664
+ const vs: Record<string, unknown> = {
665
+ index: stage.index,
666
+ path: stage.path,
667
+ queryVector: [...stage.queryVector],
668
+ numCandidates: stage.numCandidates,
669
+ limit: stage.limit,
670
+ };
671
+ if (stage.filter) vs['filter'] = { ...stage.filter };
672
+ return { $vectorSearch: vs };
673
+ }
674
+ default: {
675
+ const _exhaustive: never = stage;
676
+ throw new Error(
677
+ `Unhandled stage kind: ${blindCast<MongoPipelineStage, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
678
+ );
679
+ }
680
+ }
681
+ }
682
+
683
+ export function structuralLowerPipeline(
684
+ stages: ReadonlyArray<MongoPipelineStage>,
685
+ ): Array<Record<string, unknown>> {
686
+ return stages.map((s) => structuralLowerStage(s));
687
+ }
@@ -1,12 +1,11 @@
1
1
  import type { CodecCallContext } from '@prisma-next/framework-components/codec';
2
2
  import type { MongoCodecRegistry } from '@prisma-next/mongo-codec';
3
- import type { MongoAdapter } from '@prisma-next/mongo-lowering';
3
+ import type { MongoAdapter, MongoLoweredDraft } from '@prisma-next/mongo-lowering';
4
4
  import type {
5
5
  MongoQueryPlan,
6
6
  MongoUpdatePipelineStage,
7
7
  MongoUpdateSpec,
8
8
  } from '@prisma-next/mongo-query-ast/execution';
9
- import type { Document, MongoExpr } from '@prisma-next/mongo-value';
10
9
  import type { AnyMongoWireCommand } from '@prisma-next/mongo-wire';
11
10
  import {
12
11
  AggregateWireCommand,
@@ -19,9 +18,10 @@ import {
19
18
  UpdateManyWireCommand,
20
19
  UpdateOneWireCommand,
21
20
  } from '@prisma-next/mongo-wire';
21
+ import { blindCast } from '@prisma-next/utils/casts';
22
22
  import { buildStandardCodecRegistry } from './core/codecs';
23
- import { lowerFilter, lowerPipeline, lowerStage } from './lowering';
24
- import { resolveValue } from './resolve-value';
23
+ import { structuralLowerFilter, structuralLowerPipeline } from './lowering';
24
+ import { resolveDraftDoc } from './resolve-value';
25
25
 
26
26
  function isUpdatePipeline(
27
27
  update: MongoUpdateSpec,
@@ -29,6 +29,23 @@ function isUpdatePipeline(
29
29
  return Array.isArray(update);
30
30
  }
31
31
 
32
+ function isDraftUpdatePipeline(
33
+ update: Record<string, unknown> | ReadonlyArray<Record<string, unknown>>,
34
+ ): update is ReadonlyArray<Record<string, unknown>> {
35
+ return Array.isArray(update);
36
+ }
37
+
38
+ async function resolveUpdate(
39
+ update: Record<string, unknown> | ReadonlyArray<Record<string, unknown>>,
40
+ codecs: MongoCodecRegistry,
41
+ ctx: CodecCallContext,
42
+ ): Promise<Record<string, unknown> | ReadonlyArray<Record<string, unknown>>> {
43
+ if (isDraftUpdatePipeline(update)) {
44
+ return Promise.all(update.map((stage) => resolveDraftDoc(stage, codecs, ctx)));
45
+ }
46
+ return resolveDraftDoc(update, codecs, ctx);
47
+ }
48
+
32
49
  class MongoAdapterImpl implements MongoAdapter {
33
50
  readonly #codecs: MongoCodecRegistry;
34
51
 
@@ -36,125 +53,237 @@ class MongoAdapterImpl implements MongoAdapter {
36
53
  this.#codecs = codecs;
37
54
  }
38
55
 
39
- async #resolveDocument(expr: MongoExpr, ctx: CodecCallContext): Promise<Document> {
40
- const entries = Object.entries(expr);
41
- const resolved = await Promise.all(
42
- entries.map(([, val]) => resolveValue(val, this.#codecs, ctx)),
43
- );
44
- const result: Record<string, unknown> = {};
45
- for (let i = 0; i < entries.length; i++) {
46
- const entry = entries[i];
47
- if (entry) {
48
- result[entry[0]] = resolved[i];
56
+ structuralLower(plan: MongoQueryPlan): MongoLoweredDraft {
57
+ const { command } = plan;
58
+ switch (command.kind) {
59
+ case 'insertOne':
60
+ return { kind: 'insertOne', collection: command.collection, document: command.document };
61
+ case 'insertMany':
62
+ return {
63
+ kind: 'insertMany',
64
+ collection: command.collection,
65
+ documents: command.documents,
66
+ };
67
+ case 'updateOne':
68
+ return {
69
+ kind: 'updateOne',
70
+ collection: command.collection,
71
+ filter: structuralLowerFilter(command.filter),
72
+ update: isUpdatePipeline(command.update)
73
+ ? structuralLowerPipeline(command.update)
74
+ : command.update,
75
+ upsert: command.upsert,
76
+ };
77
+ case 'updateMany':
78
+ return {
79
+ kind: 'updateMany',
80
+ collection: command.collection,
81
+ filter: structuralLowerFilter(command.filter),
82
+ update: isUpdatePipeline(command.update)
83
+ ? structuralLowerPipeline(command.update)
84
+ : command.update,
85
+ upsert: command.upsert,
86
+ };
87
+ case 'deleteOne':
88
+ return {
89
+ kind: 'deleteOne',
90
+ collection: command.collection,
91
+ filter: structuralLowerFilter(command.filter),
92
+ };
93
+ case 'deleteMany':
94
+ return {
95
+ kind: 'deleteMany',
96
+ collection: command.collection,
97
+ filter: structuralLowerFilter(command.filter),
98
+ };
99
+ case 'findOneAndUpdate':
100
+ return {
101
+ kind: 'findOneAndUpdate',
102
+ collection: command.collection,
103
+ filter: structuralLowerFilter(command.filter),
104
+ update: isUpdatePipeline(command.update)
105
+ ? structuralLowerPipeline(command.update)
106
+ : command.update,
107
+ upsert: command.upsert,
108
+ sort: command.sort,
109
+ returnDocument: command.returnDocument,
110
+ };
111
+ case 'findOneAndDelete':
112
+ return {
113
+ kind: 'findOneAndDelete',
114
+ collection: command.collection,
115
+ filter: structuralLowerFilter(command.filter),
116
+ sort: command.sort,
117
+ };
118
+ case 'aggregate':
119
+ return {
120
+ kind: 'aggregate',
121
+ collection: command.collection,
122
+ pipeline: structuralLowerPipeline(command.pipeline),
123
+ };
124
+ case 'rawAggregate':
125
+ return { kind: 'rawAggregate', collection: command.collection, pipeline: command.pipeline };
126
+ case 'rawInsertOne':
127
+ return {
128
+ kind: 'rawInsertOne',
129
+ collection: command.collection,
130
+ document: command.document,
131
+ };
132
+ case 'rawInsertMany':
133
+ return {
134
+ kind: 'rawInsertMany',
135
+ collection: command.collection,
136
+ documents: command.documents,
137
+ };
138
+ case 'rawUpdateOne':
139
+ return {
140
+ kind: 'rawUpdateOne',
141
+ collection: command.collection,
142
+ filter: command.filter,
143
+ update: command.update,
144
+ };
145
+ case 'rawUpdateMany':
146
+ return {
147
+ kind: 'rawUpdateMany',
148
+ collection: command.collection,
149
+ filter: command.filter,
150
+ update: command.update,
151
+ };
152
+ case 'rawDeleteOne':
153
+ return { kind: 'rawDeleteOne', collection: command.collection, filter: command.filter };
154
+ case 'rawDeleteMany':
155
+ return { kind: 'rawDeleteMany', collection: command.collection, filter: command.filter };
156
+ case 'rawFindOneAndUpdate':
157
+ return {
158
+ kind: 'rawFindOneAndUpdate',
159
+ collection: command.collection,
160
+ filter: command.filter,
161
+ update: command.update,
162
+ upsert: command.upsert,
163
+ sort: command.sort,
164
+ returnDocument: command.returnDocument,
165
+ };
166
+ case 'rawFindOneAndDelete':
167
+ return {
168
+ kind: 'rawFindOneAndDelete',
169
+ collection: command.collection,
170
+ filter: command.filter,
171
+ sort: command.sort,
172
+ };
173
+ // v8 ignore next 4
174
+ default: {
175
+ const _exhaustive: never = command;
176
+ throw new Error(
177
+ `Unknown command kind: ${blindCast<{ kind: string }, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
178
+ );
49
179
  }
50
180
  }
51
- return result;
52
181
  }
53
182
 
54
- async #lowerUpdate(
55
- update: MongoUpdateSpec,
183
+ async resolveParams(
184
+ draft: MongoLoweredDraft,
56
185
  ctx: CodecCallContext,
57
- ): Promise<Document | ReadonlyArray<Document>> {
58
- if (isUpdatePipeline(update)) {
59
- return Promise.all(update.map((stage) => lowerStage(stage, this.#codecs, ctx)));
60
- }
61
- return this.#resolveDocument(update, ctx);
62
- }
63
-
64
- async lower(plan: MongoQueryPlan, ctx: CodecCallContext): Promise<AnyMongoWireCommand> {
65
- const { command } = plan;
66
- switch (command.kind) {
186
+ ): Promise<AnyMongoWireCommand> {
187
+ switch (draft.kind) {
67
188
  case 'insertOne':
68
189
  return new InsertOneWireCommand(
69
- command.collection,
70
- await this.#resolveDocument(command.document, ctx),
190
+ draft.collection,
191
+ await resolveDraftDoc(draft.document, this.#codecs, ctx),
192
+ );
193
+ case 'insertMany':
194
+ return new InsertManyWireCommand(
195
+ draft.collection,
196
+ await Promise.all(draft.documents.map((doc) => resolveDraftDoc(doc, this.#codecs, ctx))),
71
197
  );
72
198
  case 'updateOne': {
73
199
  const [filter, update] = await Promise.all([
74
- lowerFilter(command.filter, this.#codecs, ctx),
75
- this.#lowerUpdate(command.update, ctx),
200
+ resolveDraftDoc(draft.filter, this.#codecs, ctx),
201
+ resolveUpdate(draft.update, this.#codecs, ctx),
76
202
  ]);
77
- return new UpdateOneWireCommand(command.collection, filter, update, command.upsert);
203
+ return new UpdateOneWireCommand(draft.collection, filter, update, draft.upsert);
78
204
  }
79
- case 'insertMany':
80
- return new InsertManyWireCommand(
81
- command.collection,
82
- await Promise.all(command.documents.map((doc) => this.#resolveDocument(doc, ctx))),
83
- );
84
205
  case 'updateMany': {
85
206
  const [filter, update] = await Promise.all([
86
- lowerFilter(command.filter, this.#codecs, ctx),
87
- this.#lowerUpdate(command.update, ctx),
207
+ resolveDraftDoc(draft.filter, this.#codecs, ctx),
208
+ resolveUpdate(draft.update, this.#codecs, ctx),
88
209
  ]);
89
- return new UpdateManyWireCommand(command.collection, filter, update, command.upsert);
210
+ return new UpdateManyWireCommand(draft.collection, filter, update, draft.upsert);
90
211
  }
91
212
  case 'deleteOne':
92
213
  return new DeleteOneWireCommand(
93
- command.collection,
94
- await lowerFilter(command.filter, this.#codecs, ctx),
214
+ draft.collection,
215
+ await resolveDraftDoc(draft.filter, this.#codecs, ctx),
95
216
  );
96
217
  case 'deleteMany':
97
218
  return new DeleteManyWireCommand(
98
- command.collection,
99
- await lowerFilter(command.filter, this.#codecs, ctx),
219
+ draft.collection,
220
+ await resolveDraftDoc(draft.filter, this.#codecs, ctx),
100
221
  );
101
222
  case 'findOneAndUpdate': {
102
223
  const [filter, update] = await Promise.all([
103
- lowerFilter(command.filter, this.#codecs, ctx),
104
- this.#lowerUpdate(command.update, ctx),
224
+ resolveDraftDoc(draft.filter, this.#codecs, ctx),
225
+ resolveUpdate(draft.update, this.#codecs, ctx),
105
226
  ]);
106
227
  return new FindOneAndUpdateWireCommand(
107
- command.collection,
228
+ draft.collection,
108
229
  filter,
109
230
  update,
110
- command.upsert,
111
- command.sort,
112
- command.returnDocument,
231
+ draft.upsert,
232
+ draft.sort,
233
+ draft.returnDocument,
113
234
  );
114
235
  }
115
236
  case 'findOneAndDelete':
116
237
  return new FindOneAndDeleteWireCommand(
117
- command.collection,
118
- await lowerFilter(command.filter, this.#codecs, ctx),
119
- command.sort,
238
+ draft.collection,
239
+ await resolveDraftDoc(draft.filter, this.#codecs, ctx),
240
+ draft.sort,
120
241
  );
121
242
  case 'aggregate':
122
243
  return new AggregateWireCommand(
123
- command.collection,
124
- await lowerPipeline(command.pipeline, this.#codecs, ctx),
244
+ draft.collection,
245
+ await Promise.all(
246
+ draft.pipeline.map((stage) => resolveDraftDoc(stage, this.#codecs, ctx)),
247
+ ),
125
248
  );
126
249
  case 'rawAggregate':
127
- return new AggregateWireCommand(command.collection, command.pipeline);
250
+ return new AggregateWireCommand(draft.collection, draft.pipeline);
128
251
  case 'rawInsertOne':
129
- return new InsertOneWireCommand(command.collection, command.document);
252
+ return new InsertOneWireCommand(draft.collection, draft.document);
130
253
  case 'rawInsertMany':
131
- return new InsertManyWireCommand(command.collection, command.documents);
254
+ return new InsertManyWireCommand(draft.collection, draft.documents);
132
255
  case 'rawUpdateOne':
133
- return new UpdateOneWireCommand(command.collection, command.filter, command.update);
256
+ return new UpdateOneWireCommand(draft.collection, draft.filter, draft.update);
134
257
  case 'rawUpdateMany':
135
- return new UpdateManyWireCommand(command.collection, command.filter, command.update);
258
+ return new UpdateManyWireCommand(draft.collection, draft.filter, draft.update);
136
259
  case 'rawDeleteOne':
137
- return new DeleteOneWireCommand(command.collection, command.filter);
260
+ return new DeleteOneWireCommand(draft.collection, draft.filter);
138
261
  case 'rawDeleteMany':
139
- return new DeleteManyWireCommand(command.collection, command.filter);
262
+ return new DeleteManyWireCommand(draft.collection, draft.filter);
140
263
  case 'rawFindOneAndUpdate':
141
264
  return new FindOneAndUpdateWireCommand(
142
- command.collection,
143
- command.filter,
144
- command.update,
145
- command.upsert,
146
- command.sort,
147
- command.returnDocument,
265
+ draft.collection,
266
+ draft.filter,
267
+ draft.update,
268
+ draft.upsert,
269
+ draft.sort,
270
+ draft.returnDocument,
148
271
  );
149
272
  case 'rawFindOneAndDelete':
150
- return new FindOneAndDeleteWireCommand(command.collection, command.filter, command.sort);
273
+ return new FindOneAndDeleteWireCommand(draft.collection, draft.filter, draft.sort);
151
274
  // v8 ignore next 4
152
275
  default: {
153
- const _exhaustive: never = command;
154
- throw new Error(`Unknown command kind: ${(_exhaustive as { kind: string }).kind}`);
276
+ const _exhaustive: never = draft;
277
+ throw new Error(
278
+ `Unknown draft kind: ${blindCast<{ kind: string }, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
279
+ );
155
280
  }
156
281
  }
157
282
  }
283
+
284
+ lower(plan: MongoQueryPlan, ctx: CodecCallContext): Promise<AnyMongoWireCommand> {
285
+ return this.resolveParams(this.structuralLower(plan), ctx);
286
+ }
158
287
  }
159
288
 
160
289
  /**