@prisma-next/adapter-mongo 0.3.0-dev.135 → 0.3.0-dev.146

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,402 @@
1
+ import type {
2
+ MongoAggExpr,
3
+ MongoAggExprVisitor,
4
+ MongoFilterExpr,
5
+ MongoGroupId,
6
+ MongoPipelineStage,
7
+ MongoProjectionValue,
8
+ MongoWindowField,
9
+ } from '@prisma-next/mongo-query-ast';
10
+ import { isExprArray, isRecordArgs } from '@prisma-next/mongo-query-ast';
11
+ import type { Document } from '@prisma-next/mongo-value';
12
+ import { resolveValue } from './resolve-value';
13
+
14
+ // Biome flags `{ then: ... }` as a thenable object (noThenProperty). Build via Object.fromEntries to avoid.
15
+ const THEN_KEY = 'then';
16
+
17
+ function condBranch(
18
+ caseOrIf: MongoAggExpr,
19
+ thenExpr: MongoAggExpr,
20
+ elseExpr?: MongoAggExpr,
21
+ ): Record<string, unknown> {
22
+ const entries: Array<[string, unknown]> = [
23
+ [elseExpr ? 'if' : 'case', lowerAggExpr(caseOrIf)],
24
+ [THEN_KEY, lowerAggExpr(thenExpr)],
25
+ ];
26
+ if (elseExpr) {
27
+ entries.push(['else', lowerAggExpr(elseExpr)]);
28
+ }
29
+ return Object.fromEntries(entries);
30
+ }
31
+
32
+ const aggExprLoweringVisitor: MongoAggExprVisitor<unknown> = {
33
+ fieldRef(expr) {
34
+ return `$${expr.path}`;
35
+ },
36
+
37
+ literal(expr) {
38
+ return needsLiteralWrap(expr.value) ? { $literal: expr.value } : expr.value;
39
+ },
40
+
41
+ operator(expr) {
42
+ const { args } = expr;
43
+ let loweredArgs: unknown;
44
+ if (isExprArray(args)) {
45
+ loweredArgs = args.map((a) => lowerAggExpr(a));
46
+ } else if (isRecordArgs(args)) {
47
+ loweredArgs = lowerExprRecord(args);
48
+ } else {
49
+ loweredArgs = lowerAggExpr(args);
50
+ }
51
+ return { [expr.op]: loweredArgs };
52
+ },
53
+
54
+ accumulator(expr) {
55
+ if (expr.arg === null) {
56
+ return { [expr.op]: {} };
57
+ }
58
+ if (isRecordArgs(expr.arg)) {
59
+ return { [expr.op]: lowerExprRecord(expr.arg) };
60
+ }
61
+ return { [expr.op]: lowerAggExpr(expr.arg) };
62
+ },
63
+
64
+ cond(expr) {
65
+ return { $cond: condBranch(expr.condition, expr.then_, expr.else_) };
66
+ },
67
+
68
+ switch_(expr) {
69
+ return {
70
+ $switch: {
71
+ branches: expr.branches.map((b) => condBranch(b.case_, b.then_)),
72
+ default: lowerAggExpr(expr.default_),
73
+ },
74
+ };
75
+ },
76
+
77
+ filter(expr) {
78
+ return {
79
+ $filter: {
80
+ input: lowerAggExpr(expr.input),
81
+ cond: lowerAggExpr(expr.cond),
82
+ as: expr.as,
83
+ },
84
+ };
85
+ },
86
+
87
+ map(expr) {
88
+ return {
89
+ $map: {
90
+ input: lowerAggExpr(expr.input),
91
+ in: lowerAggExpr(expr.in_),
92
+ as: expr.as,
93
+ },
94
+ };
95
+ },
96
+
97
+ reduce(expr) {
98
+ return {
99
+ $reduce: {
100
+ input: lowerAggExpr(expr.input),
101
+ initialValue: lowerAggExpr(expr.initialValue),
102
+ in: lowerAggExpr(expr.in_),
103
+ },
104
+ };
105
+ },
106
+
107
+ let_(expr) {
108
+ const vars: Record<string, unknown> = {};
109
+ for (const [key, val] of Object.entries(expr.vars)) {
110
+ vars[key] = lowerAggExpr(val);
111
+ }
112
+ return { $let: { vars, in: lowerAggExpr(expr.in_) } };
113
+ },
114
+
115
+ mergeObjects(expr) {
116
+ return { $mergeObjects: expr.exprs.map((e) => lowerAggExpr(e)) };
117
+ },
118
+ };
119
+
120
+ function needsLiteralWrap(value: unknown): boolean {
121
+ if (typeof value === 'string' && value.startsWith('$')) {
122
+ return true;
123
+ }
124
+ if (Array.isArray(value)) {
125
+ return value.some((v) => needsLiteralWrap(v));
126
+ }
127
+ if (value !== null && typeof value === 'object') {
128
+ return Object.entries(value as Record<string, unknown>).some(
129
+ ([k, v]) => k.startsWith('$') || needsLiteralWrap(v),
130
+ );
131
+ }
132
+ return false;
133
+ }
134
+
135
+ export function lowerAggExpr(expr: MongoAggExpr): unknown {
136
+ return expr.accept(aggExprLoweringVisitor);
137
+ }
138
+
139
+ export function lowerFilter(filter: MongoFilterExpr): Document {
140
+ switch (filter.kind) {
141
+ case 'field':
142
+ return { [filter.field]: { [filter.op]: resolveValue(filter.value) } };
143
+ case 'and':
144
+ return { $and: filter.exprs.map((e) => lowerFilter(e)) };
145
+ case 'or':
146
+ return { $or: filter.exprs.map((e) => lowerFilter(e)) };
147
+ case 'not':
148
+ return { $nor: [lowerFilter(filter.expr)] };
149
+ case 'exists':
150
+ return { [filter.field]: { $exists: filter.exists } };
151
+ case 'expr':
152
+ return { $expr: lowerAggExpr(filter.aggExpr) };
153
+ default: {
154
+ const _exhaustive: never = filter;
155
+ throw new Error(`Unhandled filter kind: ${(_exhaustive as MongoFilterExpr).kind}`);
156
+ }
157
+ }
158
+ }
159
+
160
+ function isAggExprNode(value: object): value is MongoAggExpr {
161
+ return 'accept' in value && typeof value.accept === 'function';
162
+ }
163
+
164
+ function lowerGroupId(groupId: MongoGroupId): unknown {
165
+ if (groupId === null) return null;
166
+ if (isAggExprNode(groupId)) return lowerAggExpr(groupId);
167
+ return lowerExprRecord(groupId);
168
+ }
169
+
170
+ function lowerExprRecord(
171
+ fields: Readonly<Record<string, MongoAggExpr | ReadonlyArray<MongoAggExpr>>>,
172
+ ): Record<string, unknown> {
173
+ const result: Record<string, unknown> = {};
174
+ for (const [key, val] of Object.entries(fields)) {
175
+ if (Array.isArray(val)) {
176
+ result[key] = val.map((v: MongoAggExpr) => lowerAggExpr(v));
177
+ } else {
178
+ result[key] = lowerAggExpr(val as MongoAggExpr);
179
+ }
180
+ }
181
+ return result;
182
+ }
183
+
184
+ function lowerProjectionValue(value: MongoProjectionValue): unknown {
185
+ if (typeof value === 'number') return value;
186
+ return lowerAggExpr(value);
187
+ }
188
+
189
+ function lowerWindowField(wf: MongoWindowField): Record<string, unknown> {
190
+ const lowered = lowerAggExpr(wf.operator);
191
+ if (typeof lowered !== 'object' || lowered === null) {
192
+ throw new Error('Window field operator must lower to an object');
193
+ }
194
+ const result: Record<string, unknown> = { ...lowered };
195
+ if (wf.window) {
196
+ result['window'] = { ...wf.window };
197
+ }
198
+ return result;
199
+ }
200
+
201
+ export function lowerStage(stage: MongoPipelineStage): Record<string, unknown> {
202
+ switch (stage.kind) {
203
+ case 'match':
204
+ return { $match: lowerFilter(stage.filter) };
205
+ case 'project': {
206
+ const projection: Record<string, unknown> = {};
207
+ for (const [key, val] of Object.entries(stage.projection)) {
208
+ projection[key] = lowerProjectionValue(val);
209
+ }
210
+ return { $project: projection };
211
+ }
212
+ case 'sort':
213
+ return { $sort: { ...stage.sort } };
214
+ case 'limit':
215
+ return { $limit: stage.limit };
216
+ case 'skip':
217
+ return { $skip: stage.skip };
218
+ case 'lookup': {
219
+ const lookup: Record<string, unknown> = {
220
+ from: stage.from,
221
+ as: stage.as,
222
+ };
223
+ if (stage.localField !== undefined) lookup['localField'] = stage.localField;
224
+ if (stage.foreignField !== undefined) lookup['foreignField'] = stage.foreignField;
225
+ if (stage.pipeline) {
226
+ lookup['pipeline'] = stage.pipeline.map((s) => lowerStage(s));
227
+ }
228
+ if (stage.let_) {
229
+ lookup['let'] = lowerExprRecord(stage.let_);
230
+ }
231
+ return { $lookup: lookup };
232
+ }
233
+ case 'unwind': {
234
+ const unwind: Record<string, unknown> = {
235
+ path: stage.path,
236
+ preserveNullAndEmptyArrays: stage.preserveNullAndEmptyArrays,
237
+ };
238
+ if (stage.includeArrayIndex !== undefined) {
239
+ unwind['includeArrayIndex'] = stage.includeArrayIndex;
240
+ }
241
+ return { $unwind: unwind };
242
+ }
243
+ case 'group': {
244
+ const group: Record<string, unknown> = { _id: lowerGroupId(stage.groupId) };
245
+ for (const [key, acc] of Object.entries(stage.accumulators)) {
246
+ group[key] = lowerAggExpr(acc);
247
+ }
248
+ return { $group: group };
249
+ }
250
+ case 'addFields':
251
+ return { $addFields: lowerExprRecord(stage.fields) };
252
+ case 'replaceRoot':
253
+ return { $replaceRoot: { newRoot: lowerAggExpr(stage.newRoot) } };
254
+ case 'count':
255
+ return { $count: stage.field };
256
+ case 'sortByCount':
257
+ return { $sortByCount: lowerAggExpr(stage.expr) };
258
+ case 'sample':
259
+ return { $sample: { size: stage.size } };
260
+ case 'redact':
261
+ return { $redact: lowerAggExpr(stage.expr) };
262
+ case 'out':
263
+ return { $out: stage.db ? { db: stage.db, coll: stage.collection } : stage.collection };
264
+ case 'unionWith': {
265
+ const unionWith: Record<string, unknown> = { coll: stage.collection };
266
+ if (stage.pipeline) {
267
+ unionWith['pipeline'] = stage.pipeline.map((s) => lowerStage(s));
268
+ }
269
+ return { $unionWith: unionWith };
270
+ }
271
+ case 'bucket': {
272
+ const bucket: Record<string, unknown> = {
273
+ groupBy: lowerAggExpr(stage.groupBy),
274
+ boundaries: [...stage.boundaries],
275
+ };
276
+ if (stage.default_ !== undefined) bucket['default'] = stage.default_;
277
+ if (stage.output) bucket['output'] = lowerExprRecord(stage.output);
278
+ return { $bucket: bucket };
279
+ }
280
+ case 'bucketAuto': {
281
+ const bucketAuto: Record<string, unknown> = {
282
+ groupBy: lowerAggExpr(stage.groupBy),
283
+ buckets: stage.buckets,
284
+ };
285
+ if (stage.output) bucketAuto['output'] = lowerExprRecord(stage.output);
286
+ if (stage.granularity !== undefined) bucketAuto['granularity'] = stage.granularity;
287
+ return { $bucketAuto: bucketAuto };
288
+ }
289
+ case 'geoNear': {
290
+ const geoNear: Record<string, unknown> = {
291
+ near: stage.near,
292
+ distanceField: stage.distanceField,
293
+ };
294
+ if (stage.spherical !== undefined) geoNear['spherical'] = stage.spherical;
295
+ if (stage.maxDistance !== undefined) geoNear['maxDistance'] = stage.maxDistance;
296
+ if (stage.minDistance !== undefined) geoNear['minDistance'] = stage.minDistance;
297
+ if (stage.query) geoNear['query'] = lowerFilter(stage.query);
298
+ if (stage.key !== undefined) geoNear['key'] = stage.key;
299
+ if (stage.distanceMultiplier !== undefined)
300
+ geoNear['distanceMultiplier'] = stage.distanceMultiplier;
301
+ if (stage.includeLocs !== undefined) geoNear['includeLocs'] = stage.includeLocs;
302
+ return { $geoNear: geoNear };
303
+ }
304
+ case 'facet': {
305
+ const facet: Record<string, unknown> = {};
306
+ for (const [key, pipeline] of Object.entries(stage.facets)) {
307
+ facet[key] = pipeline.map((s) => lowerStage(s));
308
+ }
309
+ return { $facet: facet };
310
+ }
311
+ case 'graphLookup': {
312
+ const graphLookup: Record<string, unknown> = {
313
+ from: stage.from,
314
+ startWith: lowerAggExpr(stage.startWith),
315
+ connectFromField: stage.connectFromField,
316
+ connectToField: stage.connectToField,
317
+ as: stage.as,
318
+ };
319
+ if (stage.maxDepth !== undefined) graphLookup['maxDepth'] = stage.maxDepth;
320
+ if (stage.depthField !== undefined) graphLookup['depthField'] = stage.depthField;
321
+ if (stage.restrictSearchWithMatch)
322
+ graphLookup['restrictSearchWithMatch'] = lowerFilter(stage.restrictSearchWithMatch);
323
+ return { $graphLookup: graphLookup };
324
+ }
325
+ case 'merge': {
326
+ const merge: Record<string, unknown> = { into: stage.into };
327
+ if (stage.on !== undefined) merge['on'] = stage.on;
328
+ if (stage.whenMatched !== undefined) {
329
+ merge['whenMatched'] = Array.isArray(stage.whenMatched)
330
+ ? stage.whenMatched.map((s) => lowerStage(s))
331
+ : stage.whenMatched;
332
+ }
333
+ if (stage.whenNotMatched !== undefined) merge['whenNotMatched'] = stage.whenNotMatched;
334
+ return { $merge: merge };
335
+ }
336
+ case 'setWindowFields': {
337
+ const swf: Record<string, unknown> = {};
338
+ if (stage.partitionBy) swf['partitionBy'] = lowerAggExpr(stage.partitionBy);
339
+ if (stage.sortBy) swf['sortBy'] = { ...stage.sortBy };
340
+ const output: Record<string, unknown> = {};
341
+ for (const [key, wf] of Object.entries(stage.output)) {
342
+ output[key] = lowerWindowField(wf);
343
+ }
344
+ swf['output'] = output;
345
+ return { $setWindowFields: swf };
346
+ }
347
+ case 'densify': {
348
+ const densify: Record<string, unknown> = {
349
+ field: stage.field,
350
+ range: { ...stage.range },
351
+ };
352
+ if (stage.partitionByFields) densify['partitionByFields'] = [...stage.partitionByFields];
353
+ return { $densify: densify };
354
+ }
355
+ case 'fill': {
356
+ const fill: Record<string, unknown> = {};
357
+ if (stage.partitionBy) fill['partitionBy'] = lowerAggExpr(stage.partitionBy);
358
+ if (stage.partitionByFields) fill['partitionByFields'] = [...stage.partitionByFields];
359
+ if (stage.sortBy) fill['sortBy'] = { ...stage.sortBy };
360
+ const output: Record<string, unknown> = {};
361
+ for (const [key, fo] of Object.entries(stage.output)) {
362
+ const entry: Record<string, unknown> = {};
363
+ if (fo.method !== undefined) entry['method'] = fo.method;
364
+ if (fo.value !== undefined) entry['value'] = lowerAggExpr(fo.value);
365
+ output[key] = entry;
366
+ }
367
+ fill['output'] = output;
368
+ return { $fill: fill };
369
+ }
370
+ case 'search': {
371
+ const search: Record<string, unknown> = { ...stage.config };
372
+ if (stage.index !== undefined) search['index'] = stage.index;
373
+ return { $search: search };
374
+ }
375
+ case 'searchMeta': {
376
+ const searchMeta: Record<string, unknown> = { ...stage.config };
377
+ if (stage.index !== undefined) searchMeta['index'] = stage.index;
378
+ return { $searchMeta: searchMeta };
379
+ }
380
+ case 'vectorSearch': {
381
+ const vs: Record<string, unknown> = {
382
+ index: stage.index,
383
+ path: stage.path,
384
+ queryVector: [...stage.queryVector],
385
+ numCandidates: stage.numCandidates,
386
+ limit: stage.limit,
387
+ };
388
+ if (stage.filter) vs['filter'] = { ...stage.filter };
389
+ return { $vectorSearch: vs };
390
+ }
391
+ default: {
392
+ const _exhaustive: never = stage;
393
+ throw new Error(`Unhandled stage kind: ${(_exhaustive as MongoPipelineStage).kind}`);
394
+ }
395
+ }
396
+ }
397
+
398
+ export function lowerPipeline(
399
+ stages: ReadonlyArray<MongoPipelineStage>,
400
+ ): Array<Record<string, unknown>> {
401
+ return stages.map(lowerStage);
402
+ }
@@ -1,103 +1,114 @@
1
+ import type { MongoAdapter } from '@prisma-next/mongo-lowering';
1
2
  import type {
2
- AnyMongoCommand,
3
- AnyMongoWireCommand,
4
- Document,
5
- MongoAdapter,
6
- MongoExecutionPlan,
7
- MongoExpr,
8
- MongoLoweringContext,
9
3
  MongoQueryPlan,
10
- MongoValue,
11
- } from '@prisma-next/mongo-core';
4
+ MongoUpdatePipelineStage,
5
+ MongoUpdateSpec,
6
+ } from '@prisma-next/mongo-query-ast';
7
+ import type { Document, MongoExpr } from '@prisma-next/mongo-value';
8
+ import type { AnyMongoWireCommand } from '@prisma-next/mongo-wire';
12
9
  import {
13
10
  AggregateWireCommand,
11
+ DeleteManyWireCommand,
14
12
  DeleteOneWireCommand,
15
- FindWireCommand,
13
+ FindOneAndDeleteWireCommand,
14
+ FindOneAndUpdateWireCommand,
15
+ InsertManyWireCommand,
16
16
  InsertOneWireCommand,
17
- MongoParamRef,
17
+ UpdateManyWireCommand,
18
18
  UpdateOneWireCommand,
19
- } from '@prisma-next/mongo-core';
19
+ } from '@prisma-next/mongo-wire';
20
+ import { lowerFilter, lowerPipeline, lowerStage } from './lowering';
21
+ import { resolveValue } from './resolve-value';
20
22
 
21
- class MongoAdapterImpl implements MongoAdapter {
22
- lower<Row>(
23
- queryPlan: MongoQueryPlan<Row>,
24
- _context: MongoLoweringContext,
25
- ): MongoExecutionPlan<Row> {
26
- const wireCommand = this.#lowerCommand(queryPlan.command);
27
- return Object.freeze({
28
- wireCommand,
29
- command: queryPlan.command,
30
- meta: queryPlan.meta,
31
- });
23
+ function resolveDocument(expr: MongoExpr): Document {
24
+ const result: Record<string, unknown> = {};
25
+ for (const [key, val] of Object.entries(expr)) {
26
+ result[key] = resolveValue(val);
32
27
  }
28
+ return result;
29
+ }
30
+
31
+ function isUpdatePipeline(
32
+ update: MongoUpdateSpec,
33
+ ): update is ReadonlyArray<MongoUpdatePipelineStage> {
34
+ return Array.isArray(update);
35
+ }
36
+
37
+ function lowerUpdate(update: MongoUpdateSpec): Document | ReadonlyArray<Document> {
38
+ if (isUpdatePipeline(update)) {
39
+ return update.map((stage) => lowerStage(stage));
40
+ }
41
+ return resolveDocument(update);
42
+ }
33
43
 
34
- #lowerCommand(command: AnyMongoCommand): AnyMongoWireCommand {
44
+ class MongoAdapterImpl implements MongoAdapter {
45
+ lower(plan: MongoQueryPlan): AnyMongoWireCommand {
46
+ const { command } = plan;
35
47
  switch (command.kind) {
36
- case 'find': {
37
- const options: {
38
- projection?: Document;
39
- sort?: Document;
40
- limit?: number;
41
- skip?: number;
42
- } = {};
43
- if (command.projection) options.projection = { ...command.projection };
44
- if (command.sort) options.sort = { ...command.sort };
45
- if (command.limit !== undefined) options.limit = command.limit;
46
- if (command.skip !== undefined) options.skip = command.skip;
47
- return new FindWireCommand(
48
+ case 'insertOne':
49
+ return new InsertOneWireCommand(command.collection, resolveDocument(command.document));
50
+ case 'updateOne':
51
+ return new UpdateOneWireCommand(
48
52
  command.collection,
49
- command.filter ? this.#resolveDocument(command.filter) : undefined,
50
- options,
53
+ lowerFilter(command.filter),
54
+ lowerUpdate(command.update),
51
55
  );
52
- }
53
- case 'insertOne':
54
- return new InsertOneWireCommand(
56
+ case 'insertMany':
57
+ return new InsertManyWireCommand(
55
58
  command.collection,
56
- this.#resolveDocument(command.document),
59
+ command.documents.map((doc) => resolveDocument(doc)),
57
60
  );
58
- case 'updateOne':
59
- return new UpdateOneWireCommand(
61
+ case 'updateMany':
62
+ return new UpdateManyWireCommand(
60
63
  command.collection,
61
- this.#resolveDocument(command.filter),
62
- this.#resolveDocument(command.update),
64
+ lowerFilter(command.filter),
65
+ lowerUpdate(command.update),
63
66
  );
64
67
  case 'deleteOne':
65
- return new DeleteOneWireCommand(command.collection, this.#resolveDocument(command.filter));
68
+ return new DeleteOneWireCommand(command.collection, lowerFilter(command.filter));
69
+ case 'deleteMany':
70
+ return new DeleteManyWireCommand(command.collection, lowerFilter(command.filter));
71
+ case 'findOneAndUpdate':
72
+ return new FindOneAndUpdateWireCommand(
73
+ command.collection,
74
+ lowerFilter(command.filter),
75
+ lowerUpdate(command.update),
76
+ command.upsert,
77
+ );
78
+ case 'findOneAndDelete':
79
+ return new FindOneAndDeleteWireCommand(command.collection, lowerFilter(command.filter));
66
80
  case 'aggregate':
67
- return new AggregateWireCommand(
81
+ return new AggregateWireCommand(command.collection, lowerPipeline(command.pipeline));
82
+ case 'rawAggregate':
83
+ return new AggregateWireCommand(command.collection, command.pipeline);
84
+ case 'rawInsertOne':
85
+ return new InsertOneWireCommand(command.collection, command.document);
86
+ case 'rawInsertMany':
87
+ return new InsertManyWireCommand(command.collection, command.documents);
88
+ case 'rawUpdateOne':
89
+ return new UpdateOneWireCommand(command.collection, command.filter, command.update);
90
+ case 'rawUpdateMany':
91
+ return new UpdateManyWireCommand(command.collection, command.filter, command.update);
92
+ case 'rawDeleteOne':
93
+ return new DeleteOneWireCommand(command.collection, command.filter);
94
+ case 'rawDeleteMany':
95
+ return new DeleteManyWireCommand(command.collection, command.filter);
96
+ case 'rawFindOneAndUpdate':
97
+ return new FindOneAndUpdateWireCommand(
68
98
  command.collection,
69
- command.pipeline.map((stage) => ({ ...stage })),
99
+ command.filter,
100
+ command.update,
101
+ command.upsert,
70
102
  );
103
+ case 'rawFindOneAndDelete':
104
+ return new FindOneAndDeleteWireCommand(command.collection, command.filter);
105
+ // v8 ignore next 4
71
106
  default: {
72
107
  const _exhaustive: never = command;
73
108
  throw new Error(`Unknown command kind: ${(_exhaustive as { kind: string }).kind}`);
74
109
  }
75
110
  }
76
111
  }
77
-
78
- #resolveValue(value: MongoValue): unknown {
79
- if (value instanceof MongoParamRef) {
80
- return value.value;
81
- }
82
- if (value === null || typeof value !== 'object') {
83
- return value;
84
- }
85
- if (value instanceof Date) {
86
- return value;
87
- }
88
- if (Array.isArray(value)) {
89
- return value.map((v) => this.#resolveValue(v));
90
- }
91
- return this.#resolveDocument(value as MongoExpr);
92
- }
93
-
94
- #resolveDocument(expr: MongoExpr): Document {
95
- const result: Record<string, unknown> = {};
96
- for (const [key, val] of Object.entries(expr)) {
97
- result[key] = this.#resolveValue(val);
98
- }
99
- return result;
100
- }
101
112
  }
102
113
 
103
114
  export function createMongoAdapter(): MongoAdapter {
@@ -0,0 +1,22 @@
1
+ import type { MongoValue } from '@prisma-next/mongo-value';
2
+ import { MongoParamRef } from '@prisma-next/mongo-value';
3
+
4
+ export function resolveValue(value: MongoValue): unknown {
5
+ if (value instanceof MongoParamRef) {
6
+ return value.value;
7
+ }
8
+ if (value === null || typeof value !== 'object') {
9
+ return value;
10
+ }
11
+ if (value instanceof Date) {
12
+ return value;
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return value.map((v) => resolveValue(v));
16
+ }
17
+ const result: Record<string, unknown> = {};
18
+ for (const [key, val] of Object.entries(value)) {
19
+ result[key] = resolveValue(val);
20
+ }
21
+ return result;
22
+ }