@zodmon/core 0.11.0 → 0.12.0
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/dist/index.cjs +870 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1323 -152
- package/dist/index.d.ts +1323 -152
- package/dist/index.js +861 -149
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,11 @@ __export(index_exports, {
|
|
|
48
48
|
CollectionHandle: () => CollectionHandle,
|
|
49
49
|
Database: () => Database,
|
|
50
50
|
IndexBuilder: () => IndexBuilder,
|
|
51
|
+
PopulateCursor: () => PopulateCursor,
|
|
52
|
+
PopulateOneOrThrowQuery: () => PopulateOneOrThrowQuery,
|
|
53
|
+
PopulateOneQuery: () => PopulateOneQuery,
|
|
54
|
+
PopulateRefBuilder: () => PopulateRefBuilder,
|
|
55
|
+
TransactionContext: () => TransactionContext,
|
|
51
56
|
TypedFindCursor: () => TypedFindCursor,
|
|
52
57
|
ZodmonAuthError: () => ZodmonAuthError,
|
|
53
58
|
ZodmonBulkWriteError: () => ZodmonBulkWriteError,
|
|
@@ -67,9 +72,11 @@ __export(index_exports, {
|
|
|
67
72
|
createAccumulatorBuilder: () => createAccumulatorBuilder,
|
|
68
73
|
createClient: () => createClient,
|
|
69
74
|
createExpressionBuilder: () => createExpressionBuilder,
|
|
75
|
+
createPopulateCursor: () => createPopulateCursor,
|
|
70
76
|
deleteMany: () => deleteMany,
|
|
71
77
|
deleteOne: () => deleteOne,
|
|
72
78
|
deriveProjectedSchema: () => deriveProjectedSchema,
|
|
79
|
+
executePopulate: () => executePopulate,
|
|
73
80
|
extractComparableOptions: () => extractComparableOptions,
|
|
74
81
|
extractDbName: () => extractDbName,
|
|
75
82
|
extractFieldIndexes: () => extractFieldIndexes,
|
|
@@ -89,10 +96,12 @@ __export(index_exports, {
|
|
|
89
96
|
objectId: () => objectId,
|
|
90
97
|
oid: () => oid,
|
|
91
98
|
raw: () => raw,
|
|
99
|
+
resolvePopulateStep: () => resolvePopulateStep,
|
|
92
100
|
serializeIndexKey: () => serializeIndexKey,
|
|
93
101
|
syncIndexes: () => syncIndexes,
|
|
94
102
|
toCompoundIndexSpec: () => toCompoundIndexSpec,
|
|
95
103
|
toFieldIndexSpec: () => toFieldIndexSpec,
|
|
104
|
+
unwrapRefSchema: () => unwrapRefSchema,
|
|
96
105
|
updateMany: () => updateMany,
|
|
97
106
|
updateOne: () => updateOne,
|
|
98
107
|
wrapMongoError: () => wrapMongoError
|
|
@@ -147,50 +156,84 @@ function createAccumulatorBuilder() {
|
|
|
147
156
|
// biome-ignore lint/suspicious/noExplicitAny: Runtime implementation uses string field names and returns plain objects — TypeScript cannot verify that the runtime Accumulator objects match the generic AccumulatorBuilder<T> return types. Safe because type resolution happens at compile time via AccumulatorBuilder<T>, and runtime values are identical to what the standalone $min/$max/etc. produce.
|
|
148
157
|
};
|
|
149
158
|
}
|
|
159
|
+
var isExpr = (v) => typeof v === "object" && v !== null && v.__expr === true;
|
|
150
160
|
function createExpressionBuilder() {
|
|
151
|
-
const
|
|
152
|
-
|
|
161
|
+
const resolveArg = (arg) => {
|
|
162
|
+
if (typeof arg === "number") return arg;
|
|
163
|
+
if (isExpr(arg)) return arg.value;
|
|
164
|
+
return `$${arg}`;
|
|
165
|
+
};
|
|
166
|
+
const resolveExprVal = (v) => isExpr(v) ? v.value : v;
|
|
153
167
|
const expr = (value) => ({ __expr: true, value });
|
|
154
168
|
return {
|
|
155
169
|
// Arithmetic
|
|
156
|
-
add: (
|
|
157
|
-
subtract: (
|
|
158
|
-
multiply: (
|
|
159
|
-
divide: (
|
|
160
|
-
mod: (
|
|
161
|
-
abs: (field) => expr({ $abs:
|
|
162
|
-
ceil: (field) => expr({ $ceil:
|
|
163
|
-
floor: (field) => expr({ $floor:
|
|
164
|
-
round: (field, place = 0) => expr({ $round: [
|
|
170
|
+
add: (a, b) => expr({ $add: [resolveArg(a), resolveArg(b)] }),
|
|
171
|
+
subtract: (a, b) => expr({ $subtract: [resolveArg(a), resolveArg(b)] }),
|
|
172
|
+
multiply: (a, b) => expr({ $multiply: [resolveArg(a), resolveArg(b)] }),
|
|
173
|
+
divide: (a, b) => expr({ $divide: [resolveArg(a), resolveArg(b)] }),
|
|
174
|
+
mod: (a, b) => expr({ $mod: [resolveArg(a), resolveArg(b)] }),
|
|
175
|
+
abs: (field) => expr({ $abs: resolveArg(field) }),
|
|
176
|
+
ceil: (field) => expr({ $ceil: resolveArg(field) }),
|
|
177
|
+
floor: (field) => expr({ $floor: resolveArg(field) }),
|
|
178
|
+
round: (field, place = 0) => expr({ $round: [resolveArg(field), place] }),
|
|
165
179
|
// String
|
|
166
180
|
concat: (...parts) => {
|
|
167
181
|
const resolved = parts.map((p) => {
|
|
168
|
-
if (
|
|
182
|
+
if (isExpr(p)) return p.value;
|
|
183
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(p)) return `$${p}`;
|
|
169
184
|
return p;
|
|
170
185
|
});
|
|
171
186
|
return expr({ $concat: resolved });
|
|
172
187
|
},
|
|
173
|
-
toLower: (field) => expr({ $toLower:
|
|
174
|
-
toUpper: (field) => expr({ $toUpper:
|
|
175
|
-
trim: (field) => expr({ $trim: { input:
|
|
176
|
-
substr: (field, start, length) => expr({ $substrBytes: [
|
|
177
|
-
// Comparison
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
toLower: (field) => expr({ $toLower: resolveArg(field) }),
|
|
189
|
+
toUpper: (field) => expr({ $toUpper: resolveArg(field) }),
|
|
190
|
+
trim: (field) => expr({ $trim: { input: resolveArg(field) } }),
|
|
191
|
+
substr: (field, start, length) => expr({ $substrBytes: [resolveArg(field), start, length] }),
|
|
192
|
+
// Comparison — single runtime implementation handles both overloads:
|
|
193
|
+
// field path → resolveArg('name') → '$name'
|
|
194
|
+
// expression → resolveArg(expr.sub(...)) → { $subtract: [...] }
|
|
195
|
+
eq: (field, value) => expr({ $eq: [resolveArg(field), resolveExprVal(value)] }),
|
|
196
|
+
gt: (field, value) => expr({ $gt: [resolveArg(field), resolveExprVal(value)] }),
|
|
197
|
+
gte: (field, value) => expr({ $gte: [resolveArg(field), resolveExprVal(value)] }),
|
|
198
|
+
lt: (field, value) => expr({ $lt: [resolveArg(field), resolveExprVal(value)] }),
|
|
199
|
+
lte: (field, value) => expr({ $lte: [resolveArg(field), resolveExprVal(value)] }),
|
|
200
|
+
ne: (field, value) => expr({ $ne: [resolveArg(field), resolveExprVal(value)] }),
|
|
184
201
|
// Date
|
|
185
|
-
year: (field) => expr({ $year:
|
|
186
|
-
month: (field) => expr({ $month:
|
|
187
|
-
dayOfMonth: (field) => expr({ $dayOfMonth:
|
|
202
|
+
year: (field) => expr({ $year: resolveArg(field) }),
|
|
203
|
+
month: (field) => expr({ $month: resolveArg(field) }),
|
|
204
|
+
dayOfMonth: (field) => expr({ $dayOfMonth: resolveArg(field) }),
|
|
188
205
|
// Array
|
|
189
|
-
size: (field) => expr({ $size:
|
|
206
|
+
size: (field) => expr({ $size: resolveArg(field) }),
|
|
190
207
|
// Conditional
|
|
191
|
-
cond: (condition, thenValue, elseValue) => expr({
|
|
192
|
-
|
|
193
|
-
|
|
208
|
+
cond: (condition, thenValue, elseValue) => expr({
|
|
209
|
+
$cond: [condition.value, resolveExprVal(thenValue), resolveExprVal(elseValue)]
|
|
210
|
+
}),
|
|
211
|
+
ifNull: (field, fallback) => expr({ $ifNull: [resolveArg(field), fallback] }),
|
|
212
|
+
// Date (extended)
|
|
213
|
+
dayOfWeek: (field) => expr({ $dayOfWeek: resolveArg(field) }),
|
|
214
|
+
dateToString: (field, format) => expr({ $dateToString: { format, date: resolveArg(field) } }),
|
|
215
|
+
// $$NOW is a MongoDB system variable string — not a Document, but valid anywhere
|
|
216
|
+
// an aggregation expression is expected. Cast is safe; the MongoDB driver accepts it.
|
|
217
|
+
now: () => ({ __expr: true, value: "$$NOW" }),
|
|
218
|
+
// String conversion
|
|
219
|
+
toString: (field) => expr({ $toString: resolveArg(field) }),
|
|
220
|
+
// Array (extended)
|
|
221
|
+
inArray: (value, array) => expr({ $in: [resolveArg(value), Array.isArray(array) ? array : resolveArg(array)] }),
|
|
222
|
+
arrayElemAt: (field, index2) => expr({ $arrayElemAt: [resolveArg(field), index2] }),
|
|
223
|
+
// Conditional (extended)
|
|
224
|
+
switch: (branches, fallback) => expr({
|
|
225
|
+
$switch: {
|
|
226
|
+
branches: branches.map((b) => ({
|
|
227
|
+
case: b.case.value,
|
|
228
|
+
// biome-ignore lint/suspicious/noThenProperty: MongoDB $switch branch object requires a `then` key
|
|
229
|
+
then: resolveExprVal(b.then)
|
|
230
|
+
})),
|
|
231
|
+
default: resolveExprVal(fallback)
|
|
232
|
+
}
|
|
233
|
+
}),
|
|
234
|
+
// Field reference
|
|
235
|
+
field: (name) => ({ __expr: true, value: `$${name}` })
|
|
236
|
+
// biome-ignore lint/suspicious/noExplicitAny: Runtime implementation uses resolveArg/resolveExprVal — TypeScript cannot verify generic ExpressionBuilder<T> return types match. Safe because type resolution happens at compile time via ExpressionBuilder<T>.
|
|
194
237
|
};
|
|
195
238
|
}
|
|
196
239
|
|
|
@@ -466,10 +509,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
466
509
|
definition;
|
|
467
510
|
nativeCollection;
|
|
468
511
|
stages;
|
|
469
|
-
|
|
512
|
+
session;
|
|
513
|
+
constructor(definition, nativeCollection, stages, session) {
|
|
470
514
|
this.definition = definition;
|
|
471
515
|
this.nativeCollection = nativeCollection;
|
|
472
516
|
this.stages = stages;
|
|
517
|
+
this.session = session;
|
|
473
518
|
}
|
|
474
519
|
/**
|
|
475
520
|
* Append an arbitrary aggregation stage to the pipeline (escape hatch).
|
|
@@ -500,10 +545,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
500
545
|
* ```
|
|
501
546
|
*/
|
|
502
547
|
raw(stage) {
|
|
503
|
-
return new _AggregatePipeline(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
548
|
+
return new _AggregatePipeline(
|
|
549
|
+
this.definition,
|
|
550
|
+
this.nativeCollection,
|
|
551
|
+
[...this.stages, stage],
|
|
552
|
+
this.session
|
|
553
|
+
);
|
|
507
554
|
}
|
|
508
555
|
/**
|
|
509
556
|
* Execute the pipeline and return all results as an array.
|
|
@@ -519,7 +566,10 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
519
566
|
*/
|
|
520
567
|
async toArray() {
|
|
521
568
|
try {
|
|
522
|
-
const cursor = this.nativeCollection.aggregate(
|
|
569
|
+
const cursor = this.nativeCollection.aggregate(
|
|
570
|
+
this.stages,
|
|
571
|
+
this.session ? { session: this.session } : {}
|
|
572
|
+
);
|
|
523
573
|
return await cursor.toArray();
|
|
524
574
|
} catch (err) {
|
|
525
575
|
wrapMongoError(err, this.definition.name);
|
|
@@ -539,7 +589,10 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
539
589
|
*/
|
|
540
590
|
async *[Symbol.asyncIterator]() {
|
|
541
591
|
try {
|
|
542
|
-
const cursor = this.nativeCollection.aggregate(
|
|
592
|
+
const cursor = this.nativeCollection.aggregate(
|
|
593
|
+
this.stages,
|
|
594
|
+
this.session ? { session: this.session } : {}
|
|
595
|
+
);
|
|
543
596
|
for await (const doc of cursor) {
|
|
544
597
|
yield doc;
|
|
545
598
|
}
|
|
@@ -565,7 +618,10 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
565
618
|
*/
|
|
566
619
|
async explain() {
|
|
567
620
|
try {
|
|
568
|
-
const cursor = this.nativeCollection.aggregate(
|
|
621
|
+
const cursor = this.nativeCollection.aggregate(
|
|
622
|
+
this.stages,
|
|
623
|
+
this.session ? { session: this.session } : {}
|
|
624
|
+
);
|
|
569
625
|
return await cursor.explain();
|
|
570
626
|
} catch (err) {
|
|
571
627
|
wrapMongoError(err, this.definition.name);
|
|
@@ -615,12 +671,30 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
615
671
|
* .toArray()
|
|
616
672
|
* // subset[0].role → 'engineer' | 'designer'
|
|
617
673
|
* ```
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* ```ts
|
|
677
|
+
* // Field-vs-field comparison via $expr callback
|
|
678
|
+
* const overRefunded = await orders.aggregate()
|
|
679
|
+
* .match(
|
|
680
|
+
* { status: 'completed' },
|
|
681
|
+
* (expr) => expr.gt('totalAmount', expr.field('refundedAmount')),
|
|
682
|
+
* )
|
|
683
|
+
* .toArray()
|
|
684
|
+
* ```
|
|
618
685
|
*/
|
|
619
|
-
match(filter) {
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
686
|
+
match(filter, exprCb) {
|
|
687
|
+
const stage = { ...filter };
|
|
688
|
+
if (exprCb) {
|
|
689
|
+
const built = exprCb(createExpressionBuilder());
|
|
690
|
+
stage["$expr"] = built.value;
|
|
691
|
+
}
|
|
692
|
+
const pipeline = new _AggregatePipeline(
|
|
693
|
+
this.definition,
|
|
694
|
+
this.nativeCollection,
|
|
695
|
+
[...this.stages, { $match: stage }],
|
|
696
|
+
this.session
|
|
697
|
+
);
|
|
624
698
|
return pipeline;
|
|
625
699
|
}
|
|
626
700
|
/**
|
|
@@ -640,10 +714,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
640
714
|
* ```
|
|
641
715
|
*/
|
|
642
716
|
sort(spec) {
|
|
643
|
-
return new _AggregatePipeline(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
717
|
+
return new _AggregatePipeline(
|
|
718
|
+
this.definition,
|
|
719
|
+
this.nativeCollection,
|
|
720
|
+
[...this.stages, { $sort: spec }],
|
|
721
|
+
this.session
|
|
722
|
+
);
|
|
647
723
|
}
|
|
648
724
|
/**
|
|
649
725
|
* Skip a number of documents in the pipeline.
|
|
@@ -664,10 +740,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
664
740
|
* ```
|
|
665
741
|
*/
|
|
666
742
|
skip(n) {
|
|
667
|
-
return new _AggregatePipeline(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
743
|
+
return new _AggregatePipeline(
|
|
744
|
+
this.definition,
|
|
745
|
+
this.nativeCollection,
|
|
746
|
+
[...this.stages, { $skip: n }],
|
|
747
|
+
this.session
|
|
748
|
+
);
|
|
671
749
|
}
|
|
672
750
|
/**
|
|
673
751
|
* Limit the number of documents passing through the pipeline.
|
|
@@ -687,10 +765,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
687
765
|
* ```
|
|
688
766
|
*/
|
|
689
767
|
limit(n) {
|
|
690
|
-
return new _AggregatePipeline(
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
768
|
+
return new _AggregatePipeline(
|
|
769
|
+
this.definition,
|
|
770
|
+
this.nativeCollection,
|
|
771
|
+
[...this.stages, { $limit: n }],
|
|
772
|
+
this.session
|
|
773
|
+
);
|
|
694
774
|
}
|
|
695
775
|
// ── Shape-transforming projection stages ─────────────────────────
|
|
696
776
|
/**
|
|
@@ -712,10 +792,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
712
792
|
* ```
|
|
713
793
|
*/
|
|
714
794
|
project(spec) {
|
|
715
|
-
const pipeline = new _AggregatePipeline(
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
795
|
+
const pipeline = new _AggregatePipeline(
|
|
796
|
+
this.definition,
|
|
797
|
+
this.nativeCollection,
|
|
798
|
+
[...this.stages, { $project: spec }],
|
|
799
|
+
this.session
|
|
800
|
+
);
|
|
719
801
|
return pipeline;
|
|
720
802
|
}
|
|
721
803
|
/**
|
|
@@ -736,10 +818,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
736
818
|
*/
|
|
737
819
|
pick(...fields) {
|
|
738
820
|
const spec = Object.fromEntries(fields.map((f) => [f, 1]));
|
|
739
|
-
const pipeline = new _AggregatePipeline(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
821
|
+
const pipeline = new _AggregatePipeline(
|
|
822
|
+
this.definition,
|
|
823
|
+
this.nativeCollection,
|
|
824
|
+
[...this.stages, { $project: spec }],
|
|
825
|
+
this.session
|
|
826
|
+
);
|
|
743
827
|
return pipeline;
|
|
744
828
|
}
|
|
745
829
|
/**
|
|
@@ -760,22 +844,41 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
760
844
|
*/
|
|
761
845
|
omit(...fields) {
|
|
762
846
|
const spec = Object.fromEntries(fields.map((f) => [f, 0]));
|
|
763
|
-
const pipeline = new _AggregatePipeline(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
847
|
+
const pipeline = new _AggregatePipeline(
|
|
848
|
+
this.definition,
|
|
849
|
+
this.nativeCollection,
|
|
850
|
+
[...this.stages, { $project: spec }],
|
|
851
|
+
this.session
|
|
852
|
+
);
|
|
767
853
|
return pipeline;
|
|
768
854
|
}
|
|
769
855
|
groupBy(field, accumulators) {
|
|
770
856
|
const resolved = typeof accumulators === "function" ? accumulators(createAccumulatorBuilder()) : accumulators;
|
|
771
|
-
|
|
857
|
+
let _id;
|
|
858
|
+
if (field === null) {
|
|
859
|
+
_id = null;
|
|
860
|
+
} else if (Array.isArray(field)) {
|
|
861
|
+
const entries = field.map((f) => [f.replaceAll(".", "_"), `$${f}`]);
|
|
862
|
+
const keys = entries.map(([k]) => k);
|
|
863
|
+
const dupes = keys.filter((k, i) => keys.indexOf(k) !== i);
|
|
864
|
+
if (dupes.length > 0) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
`Compound groupBy key collision: ${dupes.join(", ")}. Two or more fields produce the same _id key after dot-to-underscore conversion. Use raw() with explicit aliases instead.`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
_id = Object.fromEntries(entries);
|
|
870
|
+
} else {
|
|
871
|
+
_id = `$${field}`;
|
|
872
|
+
}
|
|
772
873
|
const accumExprs = Object.fromEntries(
|
|
773
874
|
Object.entries(resolved).map(([key, acc]) => [key, acc.expr])
|
|
774
875
|
);
|
|
775
|
-
const pipeline = new _AggregatePipeline(
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
876
|
+
const pipeline = new _AggregatePipeline(
|
|
877
|
+
this.definition,
|
|
878
|
+
this.nativeCollection,
|
|
879
|
+
[...this.stages, { $group: { _id, ...accumExprs } }],
|
|
880
|
+
this.session
|
|
881
|
+
);
|
|
779
882
|
return pipeline;
|
|
780
883
|
}
|
|
781
884
|
// Implementation
|
|
@@ -787,10 +890,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
787
890
|
v && typeof v === "object" && "__expr" in v ? v.value : v
|
|
788
891
|
])
|
|
789
892
|
);
|
|
790
|
-
const pipeline = new _AggregatePipeline(
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
893
|
+
const pipeline = new _AggregatePipeline(
|
|
894
|
+
this.definition,
|
|
895
|
+
this.nativeCollection,
|
|
896
|
+
[...this.stages, { $addFields: stage }],
|
|
897
|
+
this.session
|
|
898
|
+
);
|
|
794
899
|
return pipeline;
|
|
795
900
|
}
|
|
796
901
|
// ── unwind stage ─────────────────────────────────────────────────
|
|
@@ -816,10 +921,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
816
921
|
*/
|
|
817
922
|
unwind(field, options) {
|
|
818
923
|
const stage = options?.preserveEmpty ? { $unwind: { path: `$${field}`, preserveNullAndEmptyArrays: true } } : { $unwind: `$${field}` };
|
|
819
|
-
const pipeline = new _AggregatePipeline(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
924
|
+
const pipeline = new _AggregatePipeline(
|
|
925
|
+
this.definition,
|
|
926
|
+
this.nativeCollection,
|
|
927
|
+
[...this.stages, stage],
|
|
928
|
+
this.session
|
|
929
|
+
);
|
|
823
930
|
return pipeline;
|
|
824
931
|
}
|
|
825
932
|
lookup(fieldOrFrom, options) {
|
|
@@ -867,7 +974,63 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
867
974
|
stages.push({ $unwind: { path: `$${asField}`, preserveNullAndEmptyArrays: true } });
|
|
868
975
|
}
|
|
869
976
|
}
|
|
870
|
-
const pipeline = new _AggregatePipeline(
|
|
977
|
+
const pipeline = new _AggregatePipeline(
|
|
978
|
+
this.definition,
|
|
979
|
+
this.nativeCollection,
|
|
980
|
+
stages,
|
|
981
|
+
this.session
|
|
982
|
+
);
|
|
983
|
+
return pipeline;
|
|
984
|
+
}
|
|
985
|
+
// ── facet stage ──────────────────────────────────────────────────
|
|
986
|
+
/**
|
|
987
|
+
* Run multiple sub-pipelines on the same input documents in parallel.
|
|
988
|
+
*
|
|
989
|
+
* Each key in `spec` maps to a callback that receives a fresh `SubPipeline`
|
|
990
|
+
* starting from `TOutput`. The callback chains stages and returns the terminal
|
|
991
|
+
* pipeline. Zodmon extracts the accumulated stages at runtime to build the
|
|
992
|
+
* `$facet` document. The output type is fully inferred — no annotation needed.
|
|
993
|
+
*
|
|
994
|
+
* Sub-pipelines support all stage methods including `.raw()` for operators not
|
|
995
|
+
* yet first-class. Execution methods (`toArray`, `explain`) are not available
|
|
996
|
+
* inside branches.
|
|
997
|
+
*
|
|
998
|
+
* @param spec - An object mapping branch names to sub-pipeline builder callbacks.
|
|
999
|
+
* @returns A new pipeline whose output is one document with each branch name mapped to an array of results.
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* ```ts
|
|
1003
|
+
* const [report] = await aggregate(orders)
|
|
1004
|
+
* .facet({
|
|
1005
|
+
* byCategory: (sub) => sub
|
|
1006
|
+
* .groupBy('category', acc => ({ count: acc.count() }))
|
|
1007
|
+
* .sort({ count: -1 }),
|
|
1008
|
+
* totals: (sub) => sub
|
|
1009
|
+
* .groupBy(null, acc => ({ grandTotal: acc.sum('amount') })),
|
|
1010
|
+
* })
|
|
1011
|
+
* .toArray()
|
|
1012
|
+
* // report.byCategory → { _id: 'electronics' | 'books' | 'clothing'; count: number }[]
|
|
1013
|
+
* // report.totals → { _id: null; grandTotal: number }[]
|
|
1014
|
+
* ```
|
|
1015
|
+
*/
|
|
1016
|
+
facet(spec) {
|
|
1017
|
+
const branches = {};
|
|
1018
|
+
for (const [key, cb] of Object.entries(spec)) {
|
|
1019
|
+
const sub = new _AggregatePipeline(
|
|
1020
|
+
this.definition,
|
|
1021
|
+
this.nativeCollection,
|
|
1022
|
+
[],
|
|
1023
|
+
this.session
|
|
1024
|
+
// biome-ignore lint/suspicious/noExplicitAny: sub must be cast to `any` so the concrete `AggregatePipeline<TDef, TOutput>` is accepted where `SubPipeline<TDef, TOutput>` (which lacks execution methods) is expected — safe at runtime because the pipeline instance always has the right shape
|
|
1025
|
+
);
|
|
1026
|
+
branches[key] = cb(sub).getStages();
|
|
1027
|
+
}
|
|
1028
|
+
const pipeline = new _AggregatePipeline(
|
|
1029
|
+
this.definition,
|
|
1030
|
+
this.nativeCollection,
|
|
1031
|
+
[...this.stages, { $facet: branches }],
|
|
1032
|
+
this.session
|
|
1033
|
+
);
|
|
871
1034
|
return pipeline;
|
|
872
1035
|
}
|
|
873
1036
|
// ── Convenience shortcuts ────────────────────────────────────────
|
|
@@ -888,11 +1051,16 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
888
1051
|
* ```
|
|
889
1052
|
*/
|
|
890
1053
|
countBy(field) {
|
|
891
|
-
const pipeline = new _AggregatePipeline(
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1054
|
+
const pipeline = new _AggregatePipeline(
|
|
1055
|
+
this.definition,
|
|
1056
|
+
this.nativeCollection,
|
|
1057
|
+
[
|
|
1058
|
+
...this.stages,
|
|
1059
|
+
{ $group: { _id: `$${field}`, count: { $sum: 1 } } },
|
|
1060
|
+
{ $sort: { count: -1 } }
|
|
1061
|
+
],
|
|
1062
|
+
this.session
|
|
1063
|
+
);
|
|
896
1064
|
return pipeline;
|
|
897
1065
|
}
|
|
898
1066
|
/**
|
|
@@ -913,11 +1081,16 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
913
1081
|
* ```
|
|
914
1082
|
*/
|
|
915
1083
|
sumBy(field, sumField) {
|
|
916
|
-
const pipeline = new _AggregatePipeline(
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1084
|
+
const pipeline = new _AggregatePipeline(
|
|
1085
|
+
this.definition,
|
|
1086
|
+
this.nativeCollection,
|
|
1087
|
+
[
|
|
1088
|
+
...this.stages,
|
|
1089
|
+
{ $group: { _id: `$${field}`, total: { $sum: `$${sumField}` } } },
|
|
1090
|
+
{ $sort: { total: -1 } }
|
|
1091
|
+
],
|
|
1092
|
+
this.session
|
|
1093
|
+
);
|
|
921
1094
|
return pipeline;
|
|
922
1095
|
}
|
|
923
1096
|
/**
|
|
@@ -937,10 +1110,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
937
1110
|
* ```
|
|
938
1111
|
*/
|
|
939
1112
|
sortBy(field, direction = "asc") {
|
|
940
|
-
return new _AggregatePipeline(
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1113
|
+
return new _AggregatePipeline(
|
|
1114
|
+
this.definition,
|
|
1115
|
+
this.nativeCollection,
|
|
1116
|
+
[...this.stages, { $sort: { [field]: direction === "desc" ? -1 : 1 } }],
|
|
1117
|
+
this.session
|
|
1118
|
+
);
|
|
944
1119
|
}
|
|
945
1120
|
/**
|
|
946
1121
|
* Return the top N documents sorted by a field descending.
|
|
@@ -959,11 +1134,12 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
959
1134
|
* ```
|
|
960
1135
|
*/
|
|
961
1136
|
top(n, options) {
|
|
962
|
-
return new _AggregatePipeline(
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
{ $limit: n }
|
|
966
|
-
|
|
1137
|
+
return new _AggregatePipeline(
|
|
1138
|
+
this.definition,
|
|
1139
|
+
this.nativeCollection,
|
|
1140
|
+
[...this.stages, { $sort: { [options.by]: -1 } }, { $limit: n }],
|
|
1141
|
+
this.session
|
|
1142
|
+
);
|
|
967
1143
|
}
|
|
968
1144
|
/**
|
|
969
1145
|
* Return the bottom N documents sorted by a field ascending.
|
|
@@ -982,15 +1158,25 @@ var AggregatePipeline = class _AggregatePipeline {
|
|
|
982
1158
|
* ```
|
|
983
1159
|
*/
|
|
984
1160
|
bottom(n, options) {
|
|
985
|
-
return new _AggregatePipeline(
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
{ $limit: n }
|
|
989
|
-
|
|
1161
|
+
return new _AggregatePipeline(
|
|
1162
|
+
this.definition,
|
|
1163
|
+
this.nativeCollection,
|
|
1164
|
+
[...this.stages, { $sort: { [options.by]: 1 } }, { $limit: n }],
|
|
1165
|
+
this.session
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
/** @internal Used by facet() to extract branch stages. Not part of the public API. */
|
|
1169
|
+
getStages() {
|
|
1170
|
+
return this.stages;
|
|
990
1171
|
}
|
|
991
1172
|
};
|
|
992
1173
|
function aggregate(handle) {
|
|
993
|
-
return new AggregatePipeline(
|
|
1174
|
+
return new AggregatePipeline(
|
|
1175
|
+
handle.definition,
|
|
1176
|
+
handle.native,
|
|
1177
|
+
[],
|
|
1178
|
+
handle.session
|
|
1179
|
+
);
|
|
994
1180
|
}
|
|
995
1181
|
|
|
996
1182
|
// src/client/client.ts
|
|
@@ -1172,6 +1358,36 @@ async function syncIndexes(handle, options) {
|
|
|
1172
1358
|
};
|
|
1173
1359
|
}
|
|
1174
1360
|
|
|
1361
|
+
// src/transaction/transaction.ts
|
|
1362
|
+
var TransactionContext = class {
|
|
1363
|
+
/** @internal */
|
|
1364
|
+
session;
|
|
1365
|
+
/** @internal */
|
|
1366
|
+
constructor(session) {
|
|
1367
|
+
this.session = session;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Bind a collection handle to this transaction's session.
|
|
1371
|
+
*
|
|
1372
|
+
* Returns a cloned handle whose CRUD operations automatically include
|
|
1373
|
+
* the transaction session. The original handle is not modified.
|
|
1374
|
+
*
|
|
1375
|
+
* @param handle - An existing collection handle from `db.use()`.
|
|
1376
|
+
* @returns A new handle bound to the transaction session.
|
|
1377
|
+
*
|
|
1378
|
+
* @example
|
|
1379
|
+
* ```ts
|
|
1380
|
+
* await db.transaction(async (tx) => {
|
|
1381
|
+
* const txUsers = tx.use(users)
|
|
1382
|
+
* await txUsers.insertOne({ name: 'Ada' })
|
|
1383
|
+
* })
|
|
1384
|
+
* ```
|
|
1385
|
+
*/
|
|
1386
|
+
use(handle) {
|
|
1387
|
+
return handle.withSession(this.session);
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1175
1391
|
// src/crud/delete.ts
|
|
1176
1392
|
var import_zod2 = require("zod");
|
|
1177
1393
|
|
|
@@ -1196,14 +1412,22 @@ var ZodmonValidationError = class extends ZodmonError {
|
|
|
1196
1412
|
// src/crud/delete.ts
|
|
1197
1413
|
async function deleteOne(handle, filter) {
|
|
1198
1414
|
try {
|
|
1199
|
-
return await handle.native.deleteOne(
|
|
1415
|
+
return await handle.native.deleteOne(
|
|
1416
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
1417
|
+
filter,
|
|
1418
|
+
handle.session ? { session: handle.session } : {}
|
|
1419
|
+
);
|
|
1200
1420
|
} catch (err) {
|
|
1201
1421
|
wrapMongoError(err, handle.definition.name);
|
|
1202
1422
|
}
|
|
1203
1423
|
}
|
|
1204
1424
|
async function deleteMany(handle, filter) {
|
|
1205
1425
|
try {
|
|
1206
|
-
return await handle.native.deleteMany(
|
|
1426
|
+
return await handle.native.deleteMany(
|
|
1427
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
1428
|
+
filter,
|
|
1429
|
+
handle.session ? { session: handle.session } : {}
|
|
1430
|
+
);
|
|
1207
1431
|
} catch (err) {
|
|
1208
1432
|
wrapMongoError(err, handle.definition.name);
|
|
1209
1433
|
}
|
|
@@ -1214,7 +1438,7 @@ async function findOneAndDelete(handle, filter, options) {
|
|
|
1214
1438
|
result = await handle.native.findOneAndDelete(
|
|
1215
1439
|
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
1216
1440
|
filter,
|
|
1217
|
-
{ includeResultMetadata: false }
|
|
1441
|
+
handle.session ? { includeResultMetadata: false, session: handle.session } : { includeResultMetadata: false }
|
|
1218
1442
|
);
|
|
1219
1443
|
} catch (err) {
|
|
1220
1444
|
wrapMongoError(err, handle.definition.name);
|
|
@@ -1225,7 +1449,8 @@ async function findOneAndDelete(handle, filter, options) {
|
|
|
1225
1449
|
return result;
|
|
1226
1450
|
}
|
|
1227
1451
|
try {
|
|
1228
|
-
|
|
1452
|
+
const schema = mode === "strict" ? handle.definition.strictSchema : handle.definition.schema;
|
|
1453
|
+
return schema.parse(result);
|
|
1229
1454
|
} catch (err) {
|
|
1230
1455
|
if (err instanceof import_zod2.z.ZodError) {
|
|
1231
1456
|
throw new ZodmonValidationError(handle.definition.name, err, result);
|
|
@@ -1235,7 +1460,7 @@ async function findOneAndDelete(handle, filter, options) {
|
|
|
1235
1460
|
}
|
|
1236
1461
|
|
|
1237
1462
|
// src/crud/find.ts
|
|
1238
|
-
var
|
|
1463
|
+
var import_zod5 = require("zod");
|
|
1239
1464
|
|
|
1240
1465
|
// src/errors/not-found.ts
|
|
1241
1466
|
var ZodmonNotFoundError = class extends ZodmonError {
|
|
@@ -1272,7 +1497,7 @@ function checkUnindexedFields(definition, filter) {
|
|
|
1272
1497
|
}
|
|
1273
1498
|
|
|
1274
1499
|
// src/query/cursor.ts
|
|
1275
|
-
var
|
|
1500
|
+
var import_zod4 = require("zod");
|
|
1276
1501
|
|
|
1277
1502
|
// src/crud/paginate.ts
|
|
1278
1503
|
var import_mongodb2 = require("mongodb");
|
|
@@ -1335,6 +1560,258 @@ function resolveSortKeys(sortSpec) {
|
|
|
1335
1560
|
return entries;
|
|
1336
1561
|
}
|
|
1337
1562
|
|
|
1563
|
+
// src/populate/builder.ts
|
|
1564
|
+
var PopulateRefBuilder = class {
|
|
1565
|
+
/**
|
|
1566
|
+
* Declare a projection to apply when fetching the referenced documents.
|
|
1567
|
+
*
|
|
1568
|
+
* Supported: inclusion (`{ name: 1 }`), exclusion (`{ email: 0 }`), or
|
|
1569
|
+
* `_id` suppression (`{ name: 1, _id: 0 }`).
|
|
1570
|
+
*
|
|
1571
|
+
* @param projection - MongoDB-style inclusion or exclusion projection.
|
|
1572
|
+
* @returns A config object carrying the projection type for compile-time narrowing.
|
|
1573
|
+
*
|
|
1574
|
+
* @example
|
|
1575
|
+
* ```ts
|
|
1576
|
+
* (b) => b.project({ name: 1, email: 1 })
|
|
1577
|
+
* (b) => b.project({ password: 0 })
|
|
1578
|
+
* ```
|
|
1579
|
+
*/
|
|
1580
|
+
project(projection) {
|
|
1581
|
+
return { projection };
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
// src/populate/execute.ts
|
|
1586
|
+
var import_zod3 = require("zod");
|
|
1587
|
+
function unwrapRefSchema(schema) {
|
|
1588
|
+
const def = schema._zod.def;
|
|
1589
|
+
if (def && typeof def === "object") {
|
|
1590
|
+
if ("innerType" in def && def.innerType instanceof import_zod3.z.ZodType) {
|
|
1591
|
+
return unwrapRefSchema(def.innerType);
|
|
1592
|
+
}
|
|
1593
|
+
if ("element" in def && def.element instanceof import_zod3.z.ZodType) {
|
|
1594
|
+
return unwrapRefSchema(def.element);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return schema;
|
|
1598
|
+
}
|
|
1599
|
+
function resolveRefField(shape, fieldName, collectionName) {
|
|
1600
|
+
const fieldSchema = shape[fieldName];
|
|
1601
|
+
if (!fieldSchema) {
|
|
1602
|
+
throw new Error(
|
|
1603
|
+
`[zodmon] populate: field '${fieldName}' does not exist on collection '${collectionName}'.`
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
const isArray = fieldSchema instanceof import_zod3.z.ZodArray;
|
|
1607
|
+
const inner = unwrapRefSchema(fieldSchema);
|
|
1608
|
+
const ref = getRefMetadata(inner);
|
|
1609
|
+
if (!ref) {
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
`[zodmon] populate: field '${fieldName}' has no .ref() metadata. Only fields declared with .ref(Collection) can be populated.`
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
return { isArray, ref };
|
|
1615
|
+
}
|
|
1616
|
+
function resolvePopulateStep(definition, previousSteps, path, as, projection) {
|
|
1617
|
+
const dotIndex = path.indexOf(".");
|
|
1618
|
+
if (dotIndex === -1) {
|
|
1619
|
+
const shape = definition.shape;
|
|
1620
|
+
const { isArray: isArray2, ref: ref2 } = resolveRefField(shape, path, definition.name);
|
|
1621
|
+
return {
|
|
1622
|
+
originalPath: path,
|
|
1623
|
+
leafField: path,
|
|
1624
|
+
as,
|
|
1625
|
+
parentOutputPath: void 0,
|
|
1626
|
+
targetCollection: ref2.collection,
|
|
1627
|
+
isArray: isArray2,
|
|
1628
|
+
...projection !== void 0 ? { projection } : {}
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
const parentPath = path.slice(0, dotIndex);
|
|
1632
|
+
const leafField = path.slice(dotIndex + 1);
|
|
1633
|
+
const parentStep = previousSteps.find((s) => s.as === parentPath);
|
|
1634
|
+
if (!parentStep) {
|
|
1635
|
+
throw new Error(
|
|
1636
|
+
`[zodmon] populate: parent '${parentPath}' has not been populated. Populate '${parentPath}' before populating '${path}'.`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
const parentShape = parentStep.targetCollection.shape;
|
|
1640
|
+
const { isArray, ref } = resolveRefField(parentShape, leafField, parentStep.targetCollection.name);
|
|
1641
|
+
return {
|
|
1642
|
+
originalPath: path,
|
|
1643
|
+
leafField,
|
|
1644
|
+
as,
|
|
1645
|
+
parentOutputPath: parentPath,
|
|
1646
|
+
targetCollection: ref.collection,
|
|
1647
|
+
isArray,
|
|
1648
|
+
...projection !== void 0 ? { projection } : {}
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function expandValue(value) {
|
|
1652
|
+
if (value == null) return [];
|
|
1653
|
+
if (Array.isArray(value)) {
|
|
1654
|
+
const result = [];
|
|
1655
|
+
for (const item of value) {
|
|
1656
|
+
if (item != null && typeof item === "object") {
|
|
1657
|
+
result.push(item);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return result;
|
|
1661
|
+
}
|
|
1662
|
+
if (typeof value === "object") {
|
|
1663
|
+
return [value];
|
|
1664
|
+
}
|
|
1665
|
+
return [];
|
|
1666
|
+
}
|
|
1667
|
+
function getNestedTargets(doc, path) {
|
|
1668
|
+
const parts = path.split(".");
|
|
1669
|
+
let targets = [doc];
|
|
1670
|
+
for (const part of parts) {
|
|
1671
|
+
targets = targets.flatMap((target) => expandValue(target[part]));
|
|
1672
|
+
}
|
|
1673
|
+
return targets;
|
|
1674
|
+
}
|
|
1675
|
+
function addUniqueId(value, idSet, idValues) {
|
|
1676
|
+
const key = String(value);
|
|
1677
|
+
if (!idSet.has(key)) {
|
|
1678
|
+
idSet.add(key);
|
|
1679
|
+
idValues.push(value);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
function collectIds(targets, leafField) {
|
|
1683
|
+
const idSet = /* @__PURE__ */ new Set();
|
|
1684
|
+
const idValues = [];
|
|
1685
|
+
for (const target of targets) {
|
|
1686
|
+
const value = target[leafField];
|
|
1687
|
+
if (value == null) continue;
|
|
1688
|
+
if (Array.isArray(value)) {
|
|
1689
|
+
for (const id of value) {
|
|
1690
|
+
addUniqueId(id, idSet, idValues);
|
|
1691
|
+
}
|
|
1692
|
+
} else {
|
|
1693
|
+
addUniqueId(value, idSet, idValues);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
return idValues;
|
|
1697
|
+
}
|
|
1698
|
+
function resolvePopulatedValue(value, map) {
|
|
1699
|
+
if (value == null) return value;
|
|
1700
|
+
if (Array.isArray(value)) {
|
|
1701
|
+
return value.map((id) => map.get(String(id))).filter((d) => d != null);
|
|
1702
|
+
}
|
|
1703
|
+
return map.get(String(value)) ?? null;
|
|
1704
|
+
}
|
|
1705
|
+
function mergePopulated(targets, step, map) {
|
|
1706
|
+
for (const target of targets) {
|
|
1707
|
+
const value = target[step.leafField];
|
|
1708
|
+
const populated = resolvePopulatedValue(value, map);
|
|
1709
|
+
if (step.as !== step.leafField) {
|
|
1710
|
+
delete target[step.leafField];
|
|
1711
|
+
}
|
|
1712
|
+
target[step.as] = populated;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
async function executePopulate(documents, steps, getCollection) {
|
|
1716
|
+
for (const step of steps) {
|
|
1717
|
+
const targets = step.parentOutputPath ? documents.flatMap((doc) => getNestedTargets(doc, step.parentOutputPath)) : documents;
|
|
1718
|
+
const idValues = collectIds(targets, step.leafField);
|
|
1719
|
+
if (idValues.length === 0) continue;
|
|
1720
|
+
const col = getCollection(step.targetCollection.name);
|
|
1721
|
+
const findOptions = step.projection !== void 0 ? { projection: step.projection } : {};
|
|
1722
|
+
const fetched = await col.find({ _id: { $in: idValues } }, findOptions).toArray();
|
|
1723
|
+
const map = /* @__PURE__ */ new Map();
|
|
1724
|
+
for (const doc of fetched) {
|
|
1725
|
+
map.set(String(doc._id), doc);
|
|
1726
|
+
}
|
|
1727
|
+
mergePopulated(targets, step, map);
|
|
1728
|
+
}
|
|
1729
|
+
return documents;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/populate/cursor.ts
|
|
1733
|
+
var PopulateCursor = class _PopulateCursor {
|
|
1734
|
+
cursor;
|
|
1735
|
+
definition;
|
|
1736
|
+
steps;
|
|
1737
|
+
nativeCollection;
|
|
1738
|
+
/** @internal */
|
|
1739
|
+
constructor(cursor, definition, steps, nativeCollection) {
|
|
1740
|
+
this.cursor = cursor;
|
|
1741
|
+
this.definition = definition;
|
|
1742
|
+
this.steps = steps;
|
|
1743
|
+
this.nativeCollection = nativeCollection;
|
|
1744
|
+
}
|
|
1745
|
+
// Implementation -- TypeScript cannot narrow overloaded generics in the
|
|
1746
|
+
// implementation body, so param types are widened and the return is cast.
|
|
1747
|
+
populate(field, asOrConfigure) {
|
|
1748
|
+
let alias;
|
|
1749
|
+
let projection;
|
|
1750
|
+
if (typeof asOrConfigure === "function") {
|
|
1751
|
+
alias = field.includes(".") ? field.split(".").pop() ?? field : field;
|
|
1752
|
+
const config = asOrConfigure(new PopulateRefBuilder());
|
|
1753
|
+
projection = config.projection;
|
|
1754
|
+
} else {
|
|
1755
|
+
alias = asOrConfigure ?? (field.includes(".") ? field.split(".").pop() ?? field : field);
|
|
1756
|
+
projection = void 0;
|
|
1757
|
+
}
|
|
1758
|
+
const step = resolvePopulateStep(this.definition, this.steps, field, alias, projection);
|
|
1759
|
+
const newSteps = [...this.steps, step];
|
|
1760
|
+
return new _PopulateCursor(this.cursor, this.definition, newSteps, this.nativeCollection);
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Execute the query and return all matching documents as a populated array.
|
|
1764
|
+
*
|
|
1765
|
+
* Fetches all documents from the underlying cursor, then applies populate
|
|
1766
|
+
* steps in order using batch `$in` queries (no N+1 problem).
|
|
1767
|
+
*
|
|
1768
|
+
* @returns Array of populated documents.
|
|
1769
|
+
*
|
|
1770
|
+
* @example
|
|
1771
|
+
* ```ts
|
|
1772
|
+
* const posts = await db.use(Posts)
|
|
1773
|
+
* .find({})
|
|
1774
|
+
* .populate('authorId', 'author')
|
|
1775
|
+
* .toArray()
|
|
1776
|
+
* ```
|
|
1777
|
+
*/
|
|
1778
|
+
async toArray() {
|
|
1779
|
+
const docs = await this.cursor.toArray();
|
|
1780
|
+
if (this.steps.length === 0) return docs;
|
|
1781
|
+
const populated = await executePopulate(
|
|
1782
|
+
docs,
|
|
1783
|
+
this.steps,
|
|
1784
|
+
(name) => this.nativeCollection.db.collection(name)
|
|
1785
|
+
);
|
|
1786
|
+
return populated;
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Async iterator for streaming populated documents.
|
|
1790
|
+
*
|
|
1791
|
+
* Fetches all documents first (populate requires the full batch for
|
|
1792
|
+
* efficient `$in` queries), then yields results one at a time.
|
|
1793
|
+
*
|
|
1794
|
+
* @yields Populated documents one at a time.
|
|
1795
|
+
*
|
|
1796
|
+
* @example
|
|
1797
|
+
* ```ts
|
|
1798
|
+
* for await (const post of db.use(Posts).find({}).populate('authorId', 'author')) {
|
|
1799
|
+
* console.log(post.author.name)
|
|
1800
|
+
* }
|
|
1801
|
+
* ```
|
|
1802
|
+
*/
|
|
1803
|
+
async *[Symbol.asyncIterator]() {
|
|
1804
|
+
const results = await this.toArray();
|
|
1805
|
+
for (const doc of results) {
|
|
1806
|
+
yield doc;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
function createPopulateCursor(cursor, definition, steps) {
|
|
1811
|
+
const nativeCollection = cursor.nativeCollection;
|
|
1812
|
+
return new PopulateCursor(cursor, definition, steps, nativeCollection);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1338
1815
|
// src/query/projection.ts
|
|
1339
1816
|
function isIncludeValue(value) {
|
|
1340
1817
|
return value === 1 || value === true;
|
|
@@ -1388,6 +1865,8 @@ var TypedFindCursor = class {
|
|
|
1388
1865
|
/** @internal */
|
|
1389
1866
|
cursor;
|
|
1390
1867
|
/** @internal */
|
|
1868
|
+
definition;
|
|
1869
|
+
/** @internal */
|
|
1391
1870
|
schema;
|
|
1392
1871
|
/** @internal */
|
|
1393
1872
|
collectionName;
|
|
@@ -1399,17 +1878,21 @@ var TypedFindCursor = class {
|
|
|
1399
1878
|
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter is not assignable to MongoDB's Filter; stored opaquely for paginate
|
|
1400
1879
|
filter;
|
|
1401
1880
|
/** @internal */
|
|
1881
|
+
session;
|
|
1882
|
+
/** @internal */
|
|
1402
1883
|
sortSpec;
|
|
1403
1884
|
/** @internal */
|
|
1404
1885
|
projectedSchema;
|
|
1405
1886
|
/** @internal */
|
|
1406
|
-
constructor(cursor, definition, mode, nativeCollection, filter) {
|
|
1887
|
+
constructor(cursor, definition, mode, nativeCollection, filter, session) {
|
|
1407
1888
|
this.cursor = cursor;
|
|
1889
|
+
this.definition = definition;
|
|
1408
1890
|
this.schema = definition.schema;
|
|
1409
1891
|
this.collectionName = definition.name;
|
|
1410
1892
|
this.mode = mode;
|
|
1411
1893
|
this.nativeCollection = nativeCollection;
|
|
1412
1894
|
this.filter = filter;
|
|
1895
|
+
this.session = session;
|
|
1413
1896
|
this.sortSpec = null;
|
|
1414
1897
|
this.projectedSchema = null;
|
|
1415
1898
|
}
|
|
@@ -1519,6 +2002,14 @@ var TypedFindCursor = class {
|
|
|
1519
2002
|
);
|
|
1520
2003
|
return this;
|
|
1521
2004
|
}
|
|
2005
|
+
// Implementation — creates a PopulateCursor and delegates the first populate call.
|
|
2006
|
+
// No circular runtime dependency: populate/cursor.ts imports TypedFindCursor as a
|
|
2007
|
+
// *type* only (erased at runtime), so the runtime import flows one way:
|
|
2008
|
+
// query/cursor.ts → populate/cursor.ts.
|
|
2009
|
+
populate(field, asOrConfigure) {
|
|
2010
|
+
const popCursor = createPopulateCursor(this, this.definition, []);
|
|
2011
|
+
return popCursor.populate(field, asOrConfigure);
|
|
2012
|
+
}
|
|
1522
2013
|
async paginate(opts) {
|
|
1523
2014
|
const sortRecord = this.sortSpec ? this.sortSpec : null;
|
|
1524
2015
|
const sortKeys2 = resolveSortKeys(sortRecord);
|
|
@@ -1535,8 +2026,11 @@ var TypedFindCursor = class {
|
|
|
1535
2026
|
try {
|
|
1536
2027
|
;
|
|
1537
2028
|
[total, raw2] = await Promise.all([
|
|
1538
|
-
this.nativeCollection.countDocuments(
|
|
1539
|
-
|
|
2029
|
+
this.nativeCollection.countDocuments(
|
|
2030
|
+
this.filter,
|
|
2031
|
+
this.session ? { session: this.session } : {}
|
|
2032
|
+
),
|
|
2033
|
+
this.nativeCollection.find(this.filter, this.session ? { session: this.session } : void 0).sort(sort).skip((opts.page - 1) * opts.perPage).limit(opts.perPage).toArray()
|
|
1540
2034
|
]);
|
|
1541
2035
|
} catch (err) {
|
|
1542
2036
|
wrapMongoError(err, this.collectionName);
|
|
@@ -1566,7 +2060,7 @@ var TypedFindCursor = class {
|
|
|
1566
2060
|
const effectiveSort = isBackward ? Object.fromEntries(sortKeys2.map(([f, d]) => [f, d === 1 ? -1 : 1])) : sort;
|
|
1567
2061
|
let raw2;
|
|
1568
2062
|
try {
|
|
1569
|
-
raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
|
|
2063
|
+
raw2 = await this.nativeCollection.find(combinedFilter, this.session ? { session: this.session } : void 0).sort(effectiveSort).limit(opts.limit + 1).toArray();
|
|
1570
2064
|
} catch (err) {
|
|
1571
2065
|
wrapMongoError(err, this.collectionName);
|
|
1572
2066
|
}
|
|
@@ -1635,11 +2129,11 @@ var TypedFindCursor = class {
|
|
|
1635
2129
|
if (this.mode === false || this.mode === "passthrough") {
|
|
1636
2130
|
return raw2;
|
|
1637
2131
|
}
|
|
1638
|
-
const schema = this.projectedSchema ?? this.schema;
|
|
2132
|
+
const schema = this.projectedSchema ?? (this.mode === "strict" ? this.definition.strictSchema : this.schema);
|
|
1639
2133
|
try {
|
|
1640
2134
|
return schema.parse(raw2);
|
|
1641
2135
|
} catch (err) {
|
|
1642
|
-
if (err instanceof
|
|
2136
|
+
if (err instanceof import_zod4.z.ZodError) {
|
|
1643
2137
|
throw new ZodmonValidationError(this.collectionName, err, raw2);
|
|
1644
2138
|
}
|
|
1645
2139
|
throw err;
|
|
@@ -1654,7 +2148,11 @@ async function findOne(handle, filter, options) {
|
|
|
1654
2148
|
const findOptions = project ? { projection: project } : void 0;
|
|
1655
2149
|
let raw2;
|
|
1656
2150
|
try {
|
|
1657
|
-
raw2 = await handle.native.findOne(
|
|
2151
|
+
raw2 = await handle.native.findOne(
|
|
2152
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
2153
|
+
filter,
|
|
2154
|
+
handle.session ? { ...findOptions, session: handle.session } : findOptions
|
|
2155
|
+
);
|
|
1658
2156
|
} catch (err) {
|
|
1659
2157
|
wrapMongoError(err, handle.definition.name);
|
|
1660
2158
|
}
|
|
@@ -1666,11 +2164,11 @@ async function findOne(handle, filter, options) {
|
|
|
1666
2164
|
const schema = project ? deriveProjectedSchema(
|
|
1667
2165
|
handle.definition.schema,
|
|
1668
2166
|
project
|
|
1669
|
-
) : handle.definition.schema;
|
|
2167
|
+
) : mode === "strict" ? handle.definition.strictSchema : handle.definition.schema;
|
|
1670
2168
|
try {
|
|
1671
2169
|
return schema.parse(raw2);
|
|
1672
2170
|
} catch (err) {
|
|
1673
|
-
if (err instanceof
|
|
2171
|
+
if (err instanceof import_zod5.z.ZodError) {
|
|
1674
2172
|
throw new ZodmonValidationError(handle.definition.name, err, raw2);
|
|
1675
2173
|
}
|
|
1676
2174
|
throw err;
|
|
@@ -1685,10 +2183,21 @@ async function findOneOrThrow(handle, filter, options) {
|
|
|
1685
2183
|
}
|
|
1686
2184
|
function find(handle, filter, options) {
|
|
1687
2185
|
checkUnindexedFields(handle.definition, filter);
|
|
1688
|
-
const raw2 = handle.native.find(
|
|
2186
|
+
const raw2 = handle.native.find(
|
|
2187
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
2188
|
+
filter,
|
|
2189
|
+
handle.session ? { session: handle.session } : void 0
|
|
2190
|
+
);
|
|
1689
2191
|
const cursor = raw2;
|
|
1690
2192
|
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
1691
|
-
const typedCursor = new TypedFindCursor(
|
|
2193
|
+
const typedCursor = new TypedFindCursor(
|
|
2194
|
+
cursor,
|
|
2195
|
+
handle.definition,
|
|
2196
|
+
mode,
|
|
2197
|
+
handle.native,
|
|
2198
|
+
filter,
|
|
2199
|
+
handle.session
|
|
2200
|
+
);
|
|
1692
2201
|
const project = options && "project" in options ? options.project : void 0;
|
|
1693
2202
|
if (project) {
|
|
1694
2203
|
return typedCursor.project(project);
|
|
@@ -1697,19 +2206,19 @@ function find(handle, filter, options) {
|
|
|
1697
2206
|
}
|
|
1698
2207
|
|
|
1699
2208
|
// src/crud/insert.ts
|
|
1700
|
-
var
|
|
2209
|
+
var import_zod6 = require("zod");
|
|
1701
2210
|
async function insertOne(handle, doc) {
|
|
1702
2211
|
let parsed;
|
|
1703
2212
|
try {
|
|
1704
2213
|
parsed = handle.definition.schema.parse(doc);
|
|
1705
2214
|
} catch (err) {
|
|
1706
|
-
if (err instanceof
|
|
2215
|
+
if (err instanceof import_zod6.z.ZodError) {
|
|
1707
2216
|
throw new ZodmonValidationError(handle.definition.name, err, doc);
|
|
1708
2217
|
}
|
|
1709
2218
|
throw err;
|
|
1710
2219
|
}
|
|
1711
2220
|
try {
|
|
1712
|
-
await handle.native.insertOne(parsed);
|
|
2221
|
+
await handle.native.insertOne(parsed, handle.session ? { session: handle.session } : {});
|
|
1713
2222
|
} catch (err) {
|
|
1714
2223
|
wrapMongoError(err, handle.definition.name);
|
|
1715
2224
|
}
|
|
@@ -1722,14 +2231,14 @@ async function insertMany(handle, docs) {
|
|
|
1722
2231
|
try {
|
|
1723
2232
|
parsed.push(handle.definition.schema.parse(doc));
|
|
1724
2233
|
} catch (err) {
|
|
1725
|
-
if (err instanceof
|
|
2234
|
+
if (err instanceof import_zod6.z.ZodError) {
|
|
1726
2235
|
throw new ZodmonValidationError(handle.definition.name, err, doc);
|
|
1727
2236
|
}
|
|
1728
2237
|
throw err;
|
|
1729
2238
|
}
|
|
1730
2239
|
}
|
|
1731
2240
|
try {
|
|
1732
|
-
await handle.native.insertMany(parsed);
|
|
2241
|
+
await handle.native.insertMany(parsed, handle.session ? { session: handle.session } : {});
|
|
1733
2242
|
} catch (err) {
|
|
1734
2243
|
wrapMongoError(err, handle.definition.name);
|
|
1735
2244
|
}
|
|
@@ -1737,17 +2246,29 @@ async function insertMany(handle, docs) {
|
|
|
1737
2246
|
}
|
|
1738
2247
|
|
|
1739
2248
|
// src/crud/update.ts
|
|
1740
|
-
var
|
|
2249
|
+
var import_zod7 = require("zod");
|
|
1741
2250
|
async function updateOne(handle, filter, update, options) {
|
|
1742
2251
|
try {
|
|
1743
|
-
return await handle.native.updateOne(
|
|
2252
|
+
return await handle.native.updateOne(
|
|
2253
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
2254
|
+
filter,
|
|
2255
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
|
|
2256
|
+
update,
|
|
2257
|
+
handle.session ? { ...options, session: handle.session } : options
|
|
2258
|
+
);
|
|
1744
2259
|
} catch (err) {
|
|
1745
2260
|
wrapMongoError(err, handle.definition.name);
|
|
1746
2261
|
}
|
|
1747
2262
|
}
|
|
1748
2263
|
async function updateMany(handle, filter, update, options) {
|
|
1749
2264
|
try {
|
|
1750
|
-
return await handle.native.updateMany(
|
|
2265
|
+
return await handle.native.updateMany(
|
|
2266
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
2267
|
+
filter,
|
|
2268
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
|
|
2269
|
+
update,
|
|
2270
|
+
handle.session ? { ...options, session: handle.session } : options
|
|
2271
|
+
);
|
|
1751
2272
|
} catch (err) {
|
|
1752
2273
|
wrapMongoError(err, handle.definition.name);
|
|
1753
2274
|
}
|
|
@@ -1760,6 +2281,9 @@ async function findOneAndUpdate(handle, filter, update, options) {
|
|
|
1760
2281
|
if (options?.upsert !== void 0) {
|
|
1761
2282
|
driverOptions["upsert"] = options.upsert;
|
|
1762
2283
|
}
|
|
2284
|
+
if (handle.session) {
|
|
2285
|
+
driverOptions["session"] = handle.session;
|
|
2286
|
+
}
|
|
1763
2287
|
let result;
|
|
1764
2288
|
try {
|
|
1765
2289
|
result = await handle.native.findOneAndUpdate(
|
|
@@ -1779,24 +2303,165 @@ async function findOneAndUpdate(handle, filter, update, options) {
|
|
|
1779
2303
|
return result;
|
|
1780
2304
|
}
|
|
1781
2305
|
try {
|
|
1782
|
-
|
|
2306
|
+
const schema = mode === "strict" ? handle.definition.strictSchema : handle.definition.schema;
|
|
2307
|
+
return schema.parse(result);
|
|
1783
2308
|
} catch (err) {
|
|
1784
|
-
if (err instanceof
|
|
2309
|
+
if (err instanceof import_zod7.z.ZodError) {
|
|
1785
2310
|
throw new ZodmonValidationError(handle.definition.name, err, result);
|
|
1786
2311
|
}
|
|
1787
2312
|
throw err;
|
|
1788
2313
|
}
|
|
1789
2314
|
}
|
|
1790
2315
|
|
|
2316
|
+
// src/populate/query.ts
|
|
2317
|
+
var PopulateOneQuery = class _PopulateOneQuery {
|
|
2318
|
+
handle;
|
|
2319
|
+
filter;
|
|
2320
|
+
options;
|
|
2321
|
+
steps;
|
|
2322
|
+
constructor(handle, filter, options, steps = []) {
|
|
2323
|
+
this.handle = handle;
|
|
2324
|
+
this.filter = filter;
|
|
2325
|
+
this.options = options;
|
|
2326
|
+
this.steps = steps;
|
|
2327
|
+
}
|
|
2328
|
+
// Implementation — TypeScript cannot narrow overloaded generics in the
|
|
2329
|
+
// implementation body, so param types are widened and the return is cast.
|
|
2330
|
+
populate(field, asOrConfigure) {
|
|
2331
|
+
let alias;
|
|
2332
|
+
let projection;
|
|
2333
|
+
if (typeof asOrConfigure === "function") {
|
|
2334
|
+
alias = field.includes(".") ? field.split(".").pop() ?? field : field;
|
|
2335
|
+
const config = asOrConfigure(new PopulateRefBuilder());
|
|
2336
|
+
projection = config.projection;
|
|
2337
|
+
} else {
|
|
2338
|
+
alias = asOrConfigure ?? (field.includes(".") ? field.split(".").pop() ?? field : field);
|
|
2339
|
+
projection = void 0;
|
|
2340
|
+
}
|
|
2341
|
+
const step = resolvePopulateStep(this.handle.definition, this.steps, field, alias, projection);
|
|
2342
|
+
const newSteps = [...this.steps, step];
|
|
2343
|
+
return new _PopulateOneQuery(this.handle, this.filter, this.options, newSteps);
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Attach fulfillment and rejection handlers to the query promise.
|
|
2347
|
+
*
|
|
2348
|
+
* Executes the base findOne query and applies populate steps if any.
|
|
2349
|
+
* Returns `null` when no document matches the filter.
|
|
2350
|
+
*/
|
|
2351
|
+
// biome-ignore lint/suspicious/noThenProperty: PromiseLike requires a then method
|
|
2352
|
+
then(onfulfilled, onrejected) {
|
|
2353
|
+
const promise = this.execute();
|
|
2354
|
+
return promise.then(onfulfilled, onrejected);
|
|
2355
|
+
}
|
|
2356
|
+
async execute() {
|
|
2357
|
+
const doc = await findOne(this.handle, this.filter, this.options);
|
|
2358
|
+
if (!doc) return null;
|
|
2359
|
+
if (this.steps.length === 0) return doc;
|
|
2360
|
+
const populated = await executePopulate(
|
|
2361
|
+
[doc],
|
|
2362
|
+
this.steps,
|
|
2363
|
+
(name) => this.handle.native.db.collection(name)
|
|
2364
|
+
);
|
|
2365
|
+
return populated[0] ?? null;
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
var PopulateOneOrThrowQuery = class _PopulateOneOrThrowQuery {
|
|
2369
|
+
handle;
|
|
2370
|
+
filter;
|
|
2371
|
+
options;
|
|
2372
|
+
steps;
|
|
2373
|
+
constructor(handle, filter, options, steps = []) {
|
|
2374
|
+
this.handle = handle;
|
|
2375
|
+
this.filter = filter;
|
|
2376
|
+
this.options = options;
|
|
2377
|
+
this.steps = steps;
|
|
2378
|
+
}
|
|
2379
|
+
// Implementation — see PopulateOneQuery for reasoning on casts.
|
|
2380
|
+
populate(field, asOrConfigure) {
|
|
2381
|
+
let alias;
|
|
2382
|
+
let projection;
|
|
2383
|
+
if (typeof asOrConfigure === "function") {
|
|
2384
|
+
alias = field.includes(".") ? field.split(".").pop() ?? field : field;
|
|
2385
|
+
const config = asOrConfigure(new PopulateRefBuilder());
|
|
2386
|
+
projection = config.projection;
|
|
2387
|
+
} else {
|
|
2388
|
+
alias = asOrConfigure ?? (field.includes(".") ? field.split(".").pop() ?? field : field);
|
|
2389
|
+
projection = void 0;
|
|
2390
|
+
}
|
|
2391
|
+
const step = resolvePopulateStep(this.handle.definition, this.steps, field, alias, projection);
|
|
2392
|
+
const newSteps = [...this.steps, step];
|
|
2393
|
+
return new _PopulateOneOrThrowQuery(this.handle, this.filter, this.options, newSteps);
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Attach fulfillment and rejection handlers to the query promise.
|
|
2397
|
+
*
|
|
2398
|
+
* Executes the base findOneOrThrow query and applies populate steps if any.
|
|
2399
|
+
* Throws {@link ZodmonNotFoundError} when no document matches.
|
|
2400
|
+
*/
|
|
2401
|
+
// biome-ignore lint/suspicious/noThenProperty: PromiseLike requires a then method
|
|
2402
|
+
then(onfulfilled, onrejected) {
|
|
2403
|
+
const promise = this.execute();
|
|
2404
|
+
return promise.then(onfulfilled, onrejected);
|
|
2405
|
+
}
|
|
2406
|
+
async execute() {
|
|
2407
|
+
const doc = await findOne(this.handle, this.filter, this.options);
|
|
2408
|
+
if (!doc) {
|
|
2409
|
+
throw new ZodmonNotFoundError(this.handle.definition.name, this.filter);
|
|
2410
|
+
}
|
|
2411
|
+
if (this.steps.length === 0) return doc;
|
|
2412
|
+
const populated = await executePopulate(
|
|
2413
|
+
[doc],
|
|
2414
|
+
this.steps,
|
|
2415
|
+
(name) => this.handle.native.db.collection(name)
|
|
2416
|
+
);
|
|
2417
|
+
const result = populated[0];
|
|
2418
|
+
if (!result) {
|
|
2419
|
+
throw new ZodmonNotFoundError(this.handle.definition.name, this.filter);
|
|
2420
|
+
}
|
|
2421
|
+
return result;
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
|
|
1791
2425
|
// src/client/handle.ts
|
|
1792
|
-
var CollectionHandle = class {
|
|
2426
|
+
var CollectionHandle = class _CollectionHandle {
|
|
1793
2427
|
/** The collection definition containing schema, name, and index metadata. */
|
|
1794
2428
|
definition;
|
|
1795
2429
|
/** The underlying MongoDB driver collection, typed to the inferred document type. */
|
|
1796
2430
|
native;
|
|
1797
|
-
|
|
2431
|
+
/**
|
|
2432
|
+
* The MongoDB client session bound to this handle, if any.
|
|
2433
|
+
*
|
|
2434
|
+
* When set, all CRUD and aggregation operations performed through this
|
|
2435
|
+
* handle will include the session in their options, enabling transactional
|
|
2436
|
+
* reads and writes. Undefined when no session is bound.
|
|
2437
|
+
*/
|
|
2438
|
+
session;
|
|
2439
|
+
constructor(definition, native, session) {
|
|
1798
2440
|
this.definition = definition;
|
|
1799
2441
|
this.native = native;
|
|
2442
|
+
this.session = session;
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Create a new handle bound to the given MongoDB client session.
|
|
2446
|
+
*
|
|
2447
|
+
* Returns a new {@link CollectionHandle} that shares the same collection
|
|
2448
|
+
* definition and native driver collection, but passes `session` to every
|
|
2449
|
+
* CRUD and aggregation operation. The original handle is not modified.
|
|
2450
|
+
*
|
|
2451
|
+
* @param session - The MongoDB `ClientSession` to bind.
|
|
2452
|
+
* @returns A new handle with the session attached.
|
|
2453
|
+
*
|
|
2454
|
+
* @example
|
|
2455
|
+
* ```ts
|
|
2456
|
+
* const users = db.use(Users)
|
|
2457
|
+
* await db.client.withSession(async (session) => {
|
|
2458
|
+
* const bound = users.withSession(session)
|
|
2459
|
+
* await bound.insertOne({ name: 'Ada' }) // uses session
|
|
2460
|
+
* })
|
|
2461
|
+
* ```
|
|
2462
|
+
*/
|
|
2463
|
+
withSession(session) {
|
|
2464
|
+
return new _CollectionHandle(this.definition, this.native, session);
|
|
1800
2465
|
}
|
|
1801
2466
|
/**
|
|
1802
2467
|
* Insert a single document into the collection.
|
|
@@ -1842,11 +2507,17 @@ var CollectionHandle = class {
|
|
|
1842
2507
|
async insertMany(docs) {
|
|
1843
2508
|
return await insertMany(this, docs);
|
|
1844
2509
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
2510
|
+
findOne(filter, options) {
|
|
2511
|
+
if (options && "project" in options) {
|
|
2512
|
+
return findOne(this, filter, options);
|
|
2513
|
+
}
|
|
2514
|
+
return new PopulateOneQuery(this, filter, options);
|
|
1847
2515
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
2516
|
+
findOneOrThrow(filter, options) {
|
|
2517
|
+
if (options && "project" in options) {
|
|
2518
|
+
return findOneOrThrow(this, filter, options);
|
|
2519
|
+
}
|
|
2520
|
+
return new PopulateOneOrThrowQuery(this, filter, options);
|
|
1850
2521
|
}
|
|
1851
2522
|
find(filter, options) {
|
|
1852
2523
|
return find(this, filter, options);
|
|
@@ -2096,12 +2767,51 @@ var Database = class {
|
|
|
2096
2767
|
return results;
|
|
2097
2768
|
}
|
|
2098
2769
|
/**
|
|
2099
|
-
* Execute a function within a MongoDB transaction
|
|
2770
|
+
* Execute a function within a MongoDB transaction.
|
|
2771
|
+
*
|
|
2772
|
+
* Starts a client session and runs the callback inside
|
|
2773
|
+
* `session.withTransaction()`. The driver handles commit on success,
|
|
2774
|
+
* abort on error, and automatic retries for transient transaction errors.
|
|
2775
|
+
*
|
|
2776
|
+
* The return value of `fn` is forwarded as the return value of this method.
|
|
2777
|
+
*
|
|
2778
|
+
* @param fn - Async callback receiving a {@link TransactionContext}.
|
|
2779
|
+
* @returns The value returned by `fn`.
|
|
2780
|
+
*
|
|
2781
|
+
* @example
|
|
2782
|
+
* ```ts
|
|
2783
|
+
* const user = await db.transaction(async (tx) => {
|
|
2784
|
+
* const txUsers = tx.use(users)
|
|
2785
|
+
* return await txUsers.insertOne({ name: 'Ada' })
|
|
2786
|
+
* })
|
|
2787
|
+
* ```
|
|
2100
2788
|
*
|
|
2101
|
-
*
|
|
2789
|
+
* @example
|
|
2790
|
+
* ```ts
|
|
2791
|
+
* // Rollback on error
|
|
2792
|
+
* try {
|
|
2793
|
+
* await db.transaction(async (tx) => {
|
|
2794
|
+
* const txUsers = tx.use(users)
|
|
2795
|
+
* await txUsers.insertOne({ name: 'Ada' })
|
|
2796
|
+
* throw new Error('abort!')
|
|
2797
|
+
* })
|
|
2798
|
+
* } catch (err) {
|
|
2799
|
+
* // insert was rolled back, err is the original error
|
|
2800
|
+
* }
|
|
2801
|
+
* ```
|
|
2102
2802
|
*/
|
|
2103
|
-
transaction(
|
|
2104
|
-
|
|
2803
|
+
async transaction(fn) {
|
|
2804
|
+
const session = this._client.startSession();
|
|
2805
|
+
try {
|
|
2806
|
+
let result;
|
|
2807
|
+
await session.withTransaction(async () => {
|
|
2808
|
+
const tx = new TransactionContext(session);
|
|
2809
|
+
result = await fn(tx);
|
|
2810
|
+
});
|
|
2811
|
+
return result;
|
|
2812
|
+
} finally {
|
|
2813
|
+
await session.endSession();
|
|
2814
|
+
}
|
|
2105
2815
|
}
|
|
2106
2816
|
/**
|
|
2107
2817
|
* Close the underlying `MongoClient` connection. Safe to call even if
|
|
@@ -2134,10 +2844,10 @@ function createClient(uri, dbNameOrOptions, maybeOptions) {
|
|
|
2134
2844
|
|
|
2135
2845
|
// src/collection/collection.ts
|
|
2136
2846
|
var import_mongodb5 = require("mongodb");
|
|
2137
|
-
var
|
|
2847
|
+
var import_zod10 = require("zod");
|
|
2138
2848
|
|
|
2139
2849
|
// src/schema/extensions.ts
|
|
2140
|
-
var
|
|
2850
|
+
var import_zod8 = require("zod");
|
|
2141
2851
|
var indexMetadata = /* @__PURE__ */ new WeakMap();
|
|
2142
2852
|
function getIndexMetadata(schema) {
|
|
2143
2853
|
if (typeof schema !== "object" || schema === null) return void 0;
|
|
@@ -2145,7 +2855,7 @@ function getIndexMetadata(schema) {
|
|
|
2145
2855
|
}
|
|
2146
2856
|
var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
|
|
2147
2857
|
function installExtensions() {
|
|
2148
|
-
const proto =
|
|
2858
|
+
const proto = import_zod8.z.ZodType.prototype;
|
|
2149
2859
|
if (GUARD in proto) return;
|
|
2150
2860
|
Object.defineProperty(proto, "index", {
|
|
2151
2861
|
/**
|
|
@@ -2244,10 +2954,10 @@ installExtensions();
|
|
|
2244
2954
|
|
|
2245
2955
|
// src/schema/object-id.ts
|
|
2246
2956
|
var import_mongodb4 = require("mongodb");
|
|
2247
|
-
var
|
|
2957
|
+
var import_zod9 = require("zod");
|
|
2248
2958
|
var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
|
|
2249
2959
|
function objectId() {
|
|
2250
|
-
return
|
|
2960
|
+
return import_zod9.z.custom((val) => {
|
|
2251
2961
|
if (val instanceof import_mongodb4.ObjectId) return true;
|
|
2252
2962
|
return typeof val === "string" && OBJECT_ID_HEX.test(val);
|
|
2253
2963
|
}, "Invalid ObjectId").transform((val) => val instanceof import_mongodb4.ObjectId ? val : import_mongodb4.ObjectId.createFromHexString(val));
|
|
@@ -2266,7 +2976,8 @@ function extractFieldIndexes(shape) {
|
|
|
2266
2976
|
}
|
|
2267
2977
|
function collection(name, shape, options) {
|
|
2268
2978
|
const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new import_mongodb5.ObjectId()), ...shape };
|
|
2269
|
-
const schema =
|
|
2979
|
+
const schema = import_zod10.z.object(resolvedShape);
|
|
2980
|
+
const strictSchema = schema.strict();
|
|
2270
2981
|
const fieldIndexes = extractFieldIndexes(shape);
|
|
2271
2982
|
const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
|
|
2272
2983
|
return {
|
|
@@ -2276,6 +2987,7 @@ function collection(name, shape, options) {
|
|
|
2276
2987
|
// not assignable to ZodObject<ResolvedShape<TShape>>. The cast is safe because
|
|
2277
2988
|
// the runtime shape is correct — only the readonly modifier differs.
|
|
2278
2989
|
schema,
|
|
2990
|
+
strictSchema,
|
|
2279
2991
|
shape,
|
|
2280
2992
|
fieldIndexes,
|
|
2281
2993
|
// Safe cast: compoundIndexes is TIndexes at runtime (or an empty array when
|
|
@@ -2415,6 +3127,11 @@ var $ = {
|
|
|
2415
3127
|
CollectionHandle,
|
|
2416
3128
|
Database,
|
|
2417
3129
|
IndexBuilder,
|
|
3130
|
+
PopulateCursor,
|
|
3131
|
+
PopulateOneOrThrowQuery,
|
|
3132
|
+
PopulateOneQuery,
|
|
3133
|
+
PopulateRefBuilder,
|
|
3134
|
+
TransactionContext,
|
|
2418
3135
|
TypedFindCursor,
|
|
2419
3136
|
ZodmonAuthError,
|
|
2420
3137
|
ZodmonBulkWriteError,
|
|
@@ -2434,9 +3151,11 @@ var $ = {
|
|
|
2434
3151
|
createAccumulatorBuilder,
|
|
2435
3152
|
createClient,
|
|
2436
3153
|
createExpressionBuilder,
|
|
3154
|
+
createPopulateCursor,
|
|
2437
3155
|
deleteMany,
|
|
2438
3156
|
deleteOne,
|
|
2439
3157
|
deriveProjectedSchema,
|
|
3158
|
+
executePopulate,
|
|
2440
3159
|
extractComparableOptions,
|
|
2441
3160
|
extractDbName,
|
|
2442
3161
|
extractFieldIndexes,
|
|
@@ -2456,10 +3175,12 @@ var $ = {
|
|
|
2456
3175
|
objectId,
|
|
2457
3176
|
oid,
|
|
2458
3177
|
raw,
|
|
3178
|
+
resolvePopulateStep,
|
|
2459
3179
|
serializeIndexKey,
|
|
2460
3180
|
syncIndexes,
|
|
2461
3181
|
toCompoundIndexSpec,
|
|
2462
3182
|
toFieldIndexSpec,
|
|
3183
|
+
unwrapRefSchema,
|
|
2463
3184
|
updateMany,
|
|
2464
3185
|
updateOne,
|
|
2465
3186
|
wrapMongoError
|