@zodmon/core 0.9.0 → 0.11.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.js CHANGED
@@ -11,30 +11,24 @@ var $avg = (field) => ({
11
11
  __accum: true,
12
12
  expr: { $avg: field }
13
13
  });
14
- var $min = (field) => ({
15
- __accum: true,
16
- expr: { $min: field }
17
- });
18
- var $max = (field) => ({
19
- __accum: true,
20
- expr: { $max: field }
21
- });
22
- var $first = (field) => ({
23
- __accum: true,
24
- expr: { $first: field }
25
- });
26
- var $last = (field) => ({
27
- __accum: true,
28
- expr: { $last: field }
29
- });
30
- var $push = (field) => ({
31
- __accum: true,
32
- expr: { $push: field }
33
- });
34
- var $addToSet = (field) => ({
35
- __accum: true,
36
- expr: { $addToSet: field }
37
- });
14
+ function $min(field) {
15
+ return { __accum: true, expr: { $min: field } };
16
+ }
17
+ function $max(field) {
18
+ return { __accum: true, expr: { $max: field } };
19
+ }
20
+ function $first(field) {
21
+ return { __accum: true, expr: { $first: field } };
22
+ }
23
+ function $last(field) {
24
+ return { __accum: true, expr: { $last: field } };
25
+ }
26
+ function $push(field) {
27
+ return { __accum: true, expr: { $push: field } };
28
+ }
29
+ function $addToSet(field) {
30
+ return { __accum: true, expr: { $addToSet: field } };
31
+ }
38
32
  function createAccumulatorBuilder() {
39
33
  return {
40
34
  count: () => ({ __accum: true, expr: { $sum: 1 } }),
@@ -99,6 +93,245 @@ function createExpressionBuilder() {
99
93
  };
100
94
  }
101
95
 
96
+ // src/errors/wrap.ts
97
+ import { MongoBulkWriteError, MongoNetworkError, MongoServerError } from "mongodb";
98
+
99
+ // src/errors/base.ts
100
+ var ZodmonError = class extends Error {
101
+ name = "ZodmonError";
102
+ /** The MongoDB collection name associated with this error. */
103
+ collection;
104
+ /** The underlying error that caused this error, if any. */
105
+ cause;
106
+ constructor(message, collection2, options) {
107
+ super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
108
+ this.collection = collection2;
109
+ if (options?.cause !== void 0) {
110
+ this.cause = options.cause;
111
+ }
112
+ }
113
+ };
114
+
115
+ // src/errors/auth.ts
116
+ var ZodmonAuthError = class extends ZodmonError {
117
+ name = "ZodmonAuthError";
118
+ /** The MongoDB error code (13 or 18). */
119
+ code;
120
+ constructor(collection2, code, cause) {
121
+ const message = code === 18 ? `Authentication failed for "${collection2}": check connection credentials` : `Not authorized to perform this operation on "${collection2}"`;
122
+ super(message, collection2, { cause });
123
+ this.code = code;
124
+ }
125
+ };
126
+
127
+ // src/errors/bulk-write.ts
128
+ var ZodmonBulkWriteError = class extends ZodmonError {
129
+ name = "ZodmonBulkWriteError";
130
+ /** Number of documents successfully inserted. */
131
+ insertedCount;
132
+ /** Number of documents matched by update filters. */
133
+ matchedCount;
134
+ /** Number of documents actually modified. */
135
+ modifiedCount;
136
+ /** Number of documents deleted. */
137
+ deletedCount;
138
+ /** Individual write errors with their operation index, code, and message. */
139
+ writeErrors;
140
+ constructor(collection2, cause, totalOps) {
141
+ const bulkErr = cause;
142
+ const result = bulkErr["result"] ?? {};
143
+ const rawErrors = bulkErr["writeErrors"] ?? [];
144
+ const writeErrors = rawErrors.map((e) => ({
145
+ index: e["index"] ?? 0,
146
+ code: e["code"] ?? 0,
147
+ message: e["errmsg"] ?? e["message"] ?? "unknown error"
148
+ }));
149
+ const failedMsg = totalOps !== void 0 ? `${writeErrors.length} of ${totalOps} operations failed` : `${writeErrors.length} operations failed`;
150
+ super(`Bulk write failed on "${collection2}": ${failedMsg}`, collection2, { cause });
151
+ this.insertedCount = result["insertedCount"] ?? 0;
152
+ this.matchedCount = result["matchedCount"] ?? 0;
153
+ this.modifiedCount = result["modifiedCount"] ?? 0;
154
+ this.deletedCount = result["deletedCount"] ?? 0;
155
+ this.writeErrors = writeErrors;
156
+ }
157
+ };
158
+
159
+ // src/errors/doc-validation.ts
160
+ var ZodmonDocValidationError = class extends ZodmonError {
161
+ name = "ZodmonDocValidationError";
162
+ /** Server-provided validation failure details. */
163
+ errInfo;
164
+ constructor(collection2, errInfo, cause) {
165
+ super(
166
+ `Server-side document validation failed for "${collection2}": ${cause.message}`,
167
+ collection2,
168
+ { cause }
169
+ );
170
+ this.errInfo = errInfo;
171
+ }
172
+ };
173
+
174
+ // src/errors/duplicate-key.ts
175
+ var INDEX_REGEX = /index:\s+(\S+)/;
176
+ var DUP_KEY_FIELD_REGEX = /dup key:\s*\{\s*(\w+):/;
177
+ var ZodmonDuplicateKeyError = class extends ZodmonError {
178
+ name = "ZodmonDuplicateKeyError";
179
+ /** The first field that caused the duplicate key violation. */
180
+ field;
181
+ /** The duplicate value, or `undefined` if it could not be extracted. */
182
+ value;
183
+ /** The name of the index that was violated. */
184
+ index;
185
+ /** The key pattern of the violated index (e.g. `{ email: 1 }`). */
186
+ keyPattern;
187
+ /** The key values that caused the violation. */
188
+ keyValue;
189
+ constructor(collection2, cause) {
190
+ const serverErr = cause;
191
+ const kp = serverErr["keyPattern"];
192
+ const kv = serverErr["keyValue"];
193
+ let field;
194
+ let value;
195
+ let keyPattern;
196
+ let keyValue;
197
+ if (kp && kv) {
198
+ const firstKey = Object.keys(kp)[0] ?? "unknown";
199
+ field = firstKey;
200
+ value = kv[firstKey];
201
+ keyPattern = kp;
202
+ keyValue = kv;
203
+ } else {
204
+ const fieldMatch = cause.message.match(DUP_KEY_FIELD_REGEX);
205
+ field = fieldMatch?.[1] ?? "unknown";
206
+ value = void 0;
207
+ keyPattern = field !== "unknown" ? { [field]: 1 } : {};
208
+ keyValue = {};
209
+ }
210
+ const indexMatch = cause.message.match(INDEX_REGEX);
211
+ const index2 = indexMatch?.[1] ?? "unknown";
212
+ const valueStr = typeof value === "string" ? `"${value}"` : String(value);
213
+ super(
214
+ `Duplicate key in "${collection2}": ${field} = ${valueStr} (index: ${index2})`,
215
+ collection2,
216
+ { cause }
217
+ );
218
+ this.field = field;
219
+ this.value = value;
220
+ this.index = index2;
221
+ this.keyPattern = keyPattern;
222
+ this.keyValue = keyValue;
223
+ }
224
+ };
225
+
226
+ // src/errors/index-error.ts
227
+ var ZodmonIndexError = class extends ZodmonError {
228
+ name = "ZodmonIndexError";
229
+ /** The MongoDB error code (67, 85, or 86). */
230
+ code;
231
+ constructor(collection2, code, errmsg, cause) {
232
+ const prefix = code === 67 ? "Cannot create index" : code === 85 ? "Index options conflict" : "Index key specs conflict";
233
+ super(`${prefix} on "${collection2}": ${errmsg}`, collection2, { cause });
234
+ this.code = code;
235
+ }
236
+ };
237
+
238
+ // src/errors/network.ts
239
+ var ZodmonNetworkError = class extends ZodmonError {
240
+ name = "ZodmonNetworkError";
241
+ constructor(collection2, cause) {
242
+ super(`Network error on "${collection2}": ${cause.message}`, collection2, { cause });
243
+ }
244
+ };
245
+
246
+ // src/errors/query.ts
247
+ var ZodmonQueryError = class extends ZodmonError {
248
+ name = "ZodmonQueryError";
249
+ /** The MongoDB error code (2, 9, or 292). */
250
+ code;
251
+ constructor(collection2, code, errmsg, cause) {
252
+ const message = code === 292 ? `Query exceeded memory limit on "${collection2}": enable allowDiskUse for large sorts or aggregations` : code === 9 ? `Failed to parse query on "${collection2}": ${errmsg}` : `Bad value in query on "${collection2}": ${errmsg}`;
253
+ super(message, collection2, { cause });
254
+ this.code = code;
255
+ }
256
+ };
257
+
258
+ // src/errors/timeout.ts
259
+ var ZodmonTimeoutError = class extends ZodmonError {
260
+ name = "ZodmonTimeoutError";
261
+ /** The MongoDB error code (50 or 262). */
262
+ code;
263
+ constructor(collection2, code, cause) {
264
+ super(`Operation timed out on "${collection2}": exceeded server time limit`, collection2, {
265
+ cause
266
+ });
267
+ this.code = code;
268
+ }
269
+ };
270
+
271
+ // src/errors/write-conflict.ts
272
+ var ZodmonWriteConflictError = class extends ZodmonError {
273
+ name = "ZodmonWriteConflictError";
274
+ constructor(collection2, cause) {
275
+ super(
276
+ `Write conflict in "${collection2}": another operation modified this document concurrently \u2014 retry the transaction`,
277
+ collection2,
278
+ { cause }
279
+ );
280
+ }
281
+ };
282
+
283
+ // src/errors/wrap.ts
284
+ function wrapMongoError(err, collection2) {
285
+ if (err instanceof ZodmonError) {
286
+ throw err;
287
+ }
288
+ if (err instanceof MongoBulkWriteError) {
289
+ throw new ZodmonBulkWriteError(collection2, err);
290
+ }
291
+ if (err instanceof MongoNetworkError) {
292
+ throw new ZodmonNetworkError(collection2, err);
293
+ }
294
+ if (err instanceof MongoServerError) {
295
+ switch (err.code) {
296
+ case 11e3:
297
+ case 11001:
298
+ throw new ZodmonDuplicateKeyError(collection2, err);
299
+ case 112:
300
+ throw new ZodmonWriteConflictError(collection2, err);
301
+ case 50:
302
+ case 262:
303
+ throw new ZodmonTimeoutError(collection2, err.code, err);
304
+ case 13:
305
+ case 18:
306
+ throw new ZodmonAuthError(collection2, err.code, err);
307
+ case 67:
308
+ case 85:
309
+ case 86:
310
+ throw new ZodmonIndexError(collection2, err.code, err.message, err);
311
+ case 2:
312
+ case 9:
313
+ case 292:
314
+ throw new ZodmonQueryError(collection2, err.code, err.message, err);
315
+ case 121:
316
+ throw new ZodmonDocValidationError(
317
+ collection2,
318
+ err.errInfo,
319
+ err
320
+ );
321
+ default:
322
+ throw new ZodmonError(`MongoDB error on "${collection2}": ${err.message}`, collection2, {
323
+ cause: err
324
+ });
325
+ }
326
+ }
327
+ if (err instanceof Error) {
328
+ throw new ZodmonError(`Unexpected error on "${collection2}": ${err.message}`, collection2, {
329
+ cause: err
330
+ });
331
+ }
332
+ throw new ZodmonError(`Unexpected error on "${collection2}": ${String(err)}`, collection2);
333
+ }
334
+
102
335
  // src/schema/ref.ts
103
336
  import { z } from "zod";
104
337
  var refMetadata = /* @__PURE__ */ new WeakMap();
@@ -184,8 +417,12 @@ var AggregatePipeline = class _AggregatePipeline {
184
417
  * ```
185
418
  */
186
419
  async toArray() {
187
- const cursor = this.nativeCollection.aggregate(this.stages);
188
- return await cursor.toArray();
420
+ try {
421
+ const cursor = this.nativeCollection.aggregate(this.stages);
422
+ return await cursor.toArray();
423
+ } catch (err) {
424
+ wrapMongoError(err, this.definition.name);
425
+ }
189
426
  }
190
427
  /**
191
428
  * Stream pipeline results one document at a time via `for await...of`.
@@ -200,9 +437,13 @@ var AggregatePipeline = class _AggregatePipeline {
200
437
  * ```
201
438
  */
202
439
  async *[Symbol.asyncIterator]() {
203
- const cursor = this.nativeCollection.aggregate(this.stages);
204
- for await (const doc of cursor) {
205
- yield doc;
440
+ try {
441
+ const cursor = this.nativeCollection.aggregate(this.stages);
442
+ for await (const doc of cursor) {
443
+ yield doc;
444
+ }
445
+ } catch (err) {
446
+ wrapMongoError(err, this.definition.name);
206
447
  }
207
448
  }
208
449
  /**
@@ -222,8 +463,12 @@ var AggregatePipeline = class _AggregatePipeline {
222
463
  * ```
223
464
  */
224
465
  async explain() {
225
- const cursor = this.nativeCollection.aggregate(this.stages);
226
- return await cursor.explain();
466
+ try {
467
+ const cursor = this.nativeCollection.aggregate(this.stages);
468
+ return await cursor.explain();
469
+ } catch (err) {
470
+ wrapMongoError(err, this.definition.name);
471
+ }
227
472
  }
228
473
  // ── Shape-preserving stages ──────────────────────────────────────
229
474
  /**
@@ -720,7 +965,7 @@ async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrpha
720
965
  const serialized = serializeIndexKey(spec.key);
721
966
  const existing = existingByKey.get(serialized);
722
967
  if (!existing) {
723
- if (!dryRun) await native.createIndex(spec.key, spec.options);
968
+ if (!dryRun) await safeCreateIndex(native, spec.key, spec.options);
724
969
  acc.created.push(resolveSpecName(spec));
725
970
  return;
726
971
  }
@@ -733,8 +978,8 @@ async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrpha
733
978
  }
734
979
  if (dropOrphaned) {
735
980
  if (!dryRun) {
736
- await native.dropIndex(existingName);
737
- await native.createIndex(spec.key, spec.options);
981
+ await safeDropIndex(native, existingName);
982
+ await safeCreateIndex(native, spec.key, spec.options);
738
983
  }
739
984
  acc.dropped.push(existingName);
740
985
  acc.created.push(resolveSpecName(spec));
@@ -747,6 +992,20 @@ async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrpha
747
992
  desired: spec.options
748
993
  });
749
994
  }
995
+ async function safeCreateIndex(native, key, options) {
996
+ try {
997
+ await native.createIndex(key, options);
998
+ } catch (err) {
999
+ wrapMongoError(err, native.collectionName);
1000
+ }
1001
+ }
1002
+ async function safeDropIndex(native, name) {
1003
+ try {
1004
+ await native.dropIndex(name);
1005
+ } catch (err) {
1006
+ wrapMongoError(err, native.collectionName);
1007
+ }
1008
+ }
750
1009
  async function processOrphanedIndexes(existingIndexes, desiredKeys, matchedKeys, native, dryRun, dropOrphaned, dropped) {
751
1010
  for (const idx of existingIndexes) {
752
1011
  const rawName = idx["name"];
@@ -755,7 +1014,7 @@ async function processOrphanedIndexes(existingIndexes, desiredKeys, matchedKeys,
755
1014
  const serialized = serializeIndexKey(idx["key"]);
756
1015
  if (matchedKeys.has(serialized) || desiredKeys.has(serialized)) continue;
757
1016
  if (dropOrphaned) {
758
- if (!dryRun) await native.dropIndex(name);
1017
+ if (!dryRun) await safeDropIndex(native, name);
759
1018
  dropped.push(name);
760
1019
  }
761
1020
  }
@@ -767,7 +1026,7 @@ async function listIndexesSafe(native) {
767
1026
  if (err instanceof Error && err.message.includes("ns does not exist")) {
768
1027
  return [];
769
1028
  }
770
- throw err;
1029
+ wrapMongoError(err, native.collectionName);
771
1030
  }
772
1031
  }
773
1032
  async function syncIndexes(handle, options) {
@@ -816,36 +1075,49 @@ async function syncIndexes(handle, options) {
816
1075
  import { z as z2 } from "zod";
817
1076
 
818
1077
  // src/errors/validation.ts
819
- var ZodmonValidationError = class extends Error {
1078
+ var ZodmonValidationError = class extends ZodmonError {
820
1079
  name = "ZodmonValidationError";
821
- /** The MongoDB collection name where the validation failed. */
822
- collection;
823
1080
  /** The original Zod validation error with detailed issue information. */
824
1081
  zodError;
825
- constructor(collection2, zodError) {
1082
+ /** The document that failed validation. */
1083
+ document;
1084
+ constructor(collection2, zodError, document) {
826
1085
  const fields = zodError.issues.map((issue) => {
827
1086
  const path = issue.path.join(".") || "(root)";
828
1087
  return `${path} (${issue.message})`;
829
1088
  }).join(", ");
830
- super(`Validation failed for "${collection2}": ${fields}`);
831
- this.collection = collection2;
1089
+ super(`Validation failed for "${collection2}": ${fields}`, collection2, { cause: zodError });
832
1090
  this.zodError = zodError;
1091
+ this.document = document;
833
1092
  }
834
1093
  };
835
1094
 
836
1095
  // src/crud/delete.ts
837
1096
  async function deleteOne(handle, filter) {
838
- return await handle.native.deleteOne(filter);
1097
+ try {
1098
+ return await handle.native.deleteOne(filter);
1099
+ } catch (err) {
1100
+ wrapMongoError(err, handle.definition.name);
1101
+ }
839
1102
  }
840
1103
  async function deleteMany(handle, filter) {
841
- return await handle.native.deleteMany(filter);
1104
+ try {
1105
+ return await handle.native.deleteMany(filter);
1106
+ } catch (err) {
1107
+ wrapMongoError(err, handle.definition.name);
1108
+ }
842
1109
  }
843
1110
  async function findOneAndDelete(handle, filter, options) {
844
- const result = await handle.native.findOneAndDelete(
845
- // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
846
- filter,
847
- { includeResultMetadata: false }
848
- );
1111
+ let result;
1112
+ try {
1113
+ result = await handle.native.findOneAndDelete(
1114
+ // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
1115
+ filter,
1116
+ { includeResultMetadata: false }
1117
+ );
1118
+ } catch (err) {
1119
+ wrapMongoError(err, handle.definition.name);
1120
+ }
849
1121
  if (!result) return null;
850
1122
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
851
1123
  if (mode === false || mode === "passthrough") {
@@ -855,7 +1127,7 @@ async function findOneAndDelete(handle, filter, options) {
855
1127
  return handle.definition.schema.parse(result);
856
1128
  } catch (err) {
857
1129
  if (err instanceof z2.ZodError) {
858
- throw new ZodmonValidationError(handle.definition.name, err);
1130
+ throw new ZodmonValidationError(handle.definition.name, err, result);
859
1131
  }
860
1132
  throw err;
861
1133
  }
@@ -865,13 +1137,13 @@ async function findOneAndDelete(handle, filter, options) {
865
1137
  import { z as z4 } from "zod";
866
1138
 
867
1139
  // src/errors/not-found.ts
868
- var ZodmonNotFoundError = class extends Error {
1140
+ var ZodmonNotFoundError = class extends ZodmonError {
869
1141
  name = "ZodmonNotFoundError";
870
- /** The MongoDB collection name where the query found no results. */
871
- collection;
872
- constructor(collection2) {
873
- super(`Document not found in "${collection2}"`);
874
- this.collection = collection2;
1142
+ /** The filter that produced no results. */
1143
+ filter;
1144
+ constructor(collection2, filter) {
1145
+ super(`Document not found in "${collection2}"`, collection2);
1146
+ this.filter = filter;
875
1147
  }
876
1148
  };
877
1149
 
@@ -962,6 +1234,54 @@ function resolveSortKeys(sortSpec) {
962
1234
  return entries;
963
1235
  }
964
1236
 
1237
+ // src/query/projection.ts
1238
+ function isIncludeValue(value) {
1239
+ return value === 1 || value === true;
1240
+ }
1241
+ function isExcludeValue(value) {
1242
+ return value === 0 || value === false;
1243
+ }
1244
+ function isInclusionProjection(projection) {
1245
+ for (const key of Object.keys(projection)) {
1246
+ if (key === "_id") continue;
1247
+ const value = projection[key];
1248
+ if (value !== void 0 && isIncludeValue(value)) return true;
1249
+ }
1250
+ return false;
1251
+ }
1252
+ function buildPickMask(projection, schemaKeys) {
1253
+ const mask = {};
1254
+ const idValue = projection._id;
1255
+ if (!(idValue !== void 0 && isExcludeValue(idValue)) && schemaKeys.has("_id")) {
1256
+ mask._id = true;
1257
+ }
1258
+ for (const key of Object.keys(projection)) {
1259
+ if (key === "_id") continue;
1260
+ const value = projection[key];
1261
+ if (value !== void 0 && isIncludeValue(value) && schemaKeys.has(key)) {
1262
+ mask[key] = true;
1263
+ }
1264
+ }
1265
+ return mask;
1266
+ }
1267
+ function buildOmitMask(projection, schemaKeys) {
1268
+ const mask = {};
1269
+ for (const key of Object.keys(projection)) {
1270
+ const value = projection[key];
1271
+ if (value !== void 0 && isExcludeValue(value) && schemaKeys.has(key)) {
1272
+ mask[key] = true;
1273
+ }
1274
+ }
1275
+ return mask;
1276
+ }
1277
+ function deriveProjectedSchema(schema, projection) {
1278
+ const schemaKeys = new Set(Object.keys(schema.shape));
1279
+ if (isInclusionProjection(projection)) {
1280
+ return schema.pick(buildPickMask(projection, schemaKeys));
1281
+ }
1282
+ return schema.omit(buildOmitMask(projection, schemaKeys));
1283
+ }
1284
+
965
1285
  // src/query/cursor.ts
966
1286
  var TypedFindCursor = class {
967
1287
  /** @internal */
@@ -980,6 +1300,8 @@ var TypedFindCursor = class {
980
1300
  /** @internal */
981
1301
  sortSpec;
982
1302
  /** @internal */
1303
+ projectedSchema;
1304
+ /** @internal */
983
1305
  constructor(cursor, definition, mode, nativeCollection, filter) {
984
1306
  this.cursor = cursor;
985
1307
  this.schema = definition.schema;
@@ -988,6 +1310,7 @@ var TypedFindCursor = class {
988
1310
  this.nativeCollection = nativeCollection;
989
1311
  this.filter = filter;
990
1312
  this.sortSpec = null;
1313
+ this.projectedSchema = null;
991
1314
  }
992
1315
  /**
993
1316
  * Set the sort order for the query.
@@ -1061,6 +1384,40 @@ var TypedFindCursor = class {
1061
1384
  this.cursor.hint(indexName);
1062
1385
  return this;
1063
1386
  }
1387
+ /**
1388
+ * Apply a projection to narrow the returned fields.
1389
+ *
1390
+ * Inclusion projections (`{ name: 1 }`) return only the specified fields
1391
+ * plus `_id` (unless `_id: 0`). Exclusion projections (`{ email: 0 }`)
1392
+ * return all fields except those excluded.
1393
+ *
1394
+ * The cursor's output type is narrowed at compile time. A derived Zod
1395
+ * schema is built for runtime validation of the projected fields.
1396
+ *
1397
+ * Projects from the original document type, not from a previous projection.
1398
+ * Calling `.project()` twice overrides the previous projection.
1399
+ *
1400
+ * @param spec - Type-safe projection document.
1401
+ * @returns A new cursor with the narrowed output type.
1402
+ *
1403
+ * @example
1404
+ * ```ts
1405
+ * const names = await find(users, {})
1406
+ * .project({ name: 1 })
1407
+ * .sort({ name: 1 })
1408
+ * .toArray()
1409
+ * // names[0].name ✓
1410
+ * // names[0].email TS error
1411
+ * ```
1412
+ */
1413
+ project(spec) {
1414
+ this.cursor.project(spec);
1415
+ this.projectedSchema = deriveProjectedSchema(
1416
+ this.schema,
1417
+ spec
1418
+ );
1419
+ return this;
1420
+ }
1064
1421
  async paginate(opts) {
1065
1422
  const sortRecord = this.sortSpec ? this.sortSpec : null;
1066
1423
  const sortKeys2 = resolveSortKeys(sortRecord);
@@ -1072,10 +1429,17 @@ var TypedFindCursor = class {
1072
1429
  }
1073
1430
  /** @internal Offset pagination implementation. */
1074
1431
  async offsetPaginate(_sortKeys, sort, opts) {
1075
- const [total, raw2] = await Promise.all([
1076
- this.nativeCollection.countDocuments(this.filter),
1077
- this.nativeCollection.find(this.filter).sort(sort).skip((opts.page - 1) * opts.perPage).limit(opts.perPage).toArray()
1078
- ]);
1432
+ let total;
1433
+ let raw2;
1434
+ try {
1435
+ ;
1436
+ [total, raw2] = await Promise.all([
1437
+ this.nativeCollection.countDocuments(this.filter),
1438
+ this.nativeCollection.find(this.filter).sort(sort).skip((opts.page - 1) * opts.perPage).limit(opts.perPage).toArray()
1439
+ ]);
1440
+ } catch (err) {
1441
+ wrapMongoError(err, this.collectionName);
1442
+ }
1079
1443
  const docs = raw2.map((doc) => this.validateDoc(doc));
1080
1444
  const totalPages = Math.ceil(total / opts.perPage);
1081
1445
  return {
@@ -1099,7 +1463,12 @@ var TypedFindCursor = class {
1099
1463
  combinedFilter = this.filter && Object.keys(this.filter).length > 0 ? { $and: [this.filter, cursorFilter] } : cursorFilter;
1100
1464
  }
1101
1465
  const effectiveSort = isBackward ? Object.fromEntries(sortKeys2.map(([f, d]) => [f, d === 1 ? -1 : 1])) : sort;
1102
- const raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
1466
+ let raw2;
1467
+ try {
1468
+ raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
1469
+ } catch (err) {
1470
+ wrapMongoError(err, this.collectionName);
1471
+ }
1103
1472
  const hasMore = raw2.length > opts.limit;
1104
1473
  if (hasMore) raw2.pop();
1105
1474
  if (isBackward) raw2.reverse();
@@ -1127,7 +1496,12 @@ var TypedFindCursor = class {
1127
1496
  * ```
1128
1497
  */
1129
1498
  async toArray() {
1130
- const raw2 = await this.cursor.toArray();
1499
+ let raw2;
1500
+ try {
1501
+ raw2 = await this.cursor.toArray();
1502
+ } catch (err) {
1503
+ wrapMongoError(err, this.collectionName);
1504
+ }
1131
1505
  return raw2.map((doc) => this.validateDoc(doc));
1132
1506
  }
1133
1507
  /**
@@ -1147,8 +1521,12 @@ var TypedFindCursor = class {
1147
1521
  * ```
1148
1522
  */
1149
1523
  async *[Symbol.asyncIterator]() {
1150
- for await (const doc of this.cursor) {
1151
- yield this.validateDoc(doc);
1524
+ try {
1525
+ for await (const doc of this.cursor) {
1526
+ yield this.validateDoc(doc);
1527
+ }
1528
+ } catch (err) {
1529
+ wrapMongoError(err, this.collectionName);
1152
1530
  }
1153
1531
  }
1154
1532
  /** @internal Validate a single raw document against the schema. */
@@ -1156,11 +1534,12 @@ var TypedFindCursor = class {
1156
1534
  if (this.mode === false || this.mode === "passthrough") {
1157
1535
  return raw2;
1158
1536
  }
1537
+ const schema = this.projectedSchema ?? this.schema;
1159
1538
  try {
1160
- return this.schema.parse(raw2);
1539
+ return schema.parse(raw2);
1161
1540
  } catch (err) {
1162
1541
  if (err instanceof z3.ZodError) {
1163
- throw new ZodmonValidationError(this.collectionName, err);
1542
+ throw new ZodmonValidationError(this.collectionName, err, raw2);
1164
1543
  }
1165
1544
  throw err;
1166
1545
  }
@@ -1170,18 +1549,28 @@ var TypedFindCursor = class {
1170
1549
  // src/crud/find.ts
1171
1550
  async function findOne(handle, filter, options) {
1172
1551
  checkUnindexedFields(handle.definition, filter);
1173
- const findOptions = options?.project ? { projection: options.project } : void 0;
1174
- const raw2 = await handle.native.findOne(filter, findOptions);
1552
+ const project = options && "project" in options ? options.project : void 0;
1553
+ const findOptions = project ? { projection: project } : void 0;
1554
+ let raw2;
1555
+ try {
1556
+ raw2 = await handle.native.findOne(filter, findOptions);
1557
+ } catch (err) {
1558
+ wrapMongoError(err, handle.definition.name);
1559
+ }
1175
1560
  if (!raw2) return null;
1176
1561
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
1177
1562
  if (mode === false || mode === "passthrough") {
1178
1563
  return raw2;
1179
1564
  }
1565
+ const schema = project ? deriveProjectedSchema(
1566
+ handle.definition.schema,
1567
+ project
1568
+ ) : handle.definition.schema;
1180
1569
  try {
1181
- return handle.definition.schema.parse(raw2);
1570
+ return schema.parse(raw2);
1182
1571
  } catch (err) {
1183
1572
  if (err instanceof z4.ZodError) {
1184
- throw new ZodmonValidationError(handle.definition.name, err);
1573
+ throw new ZodmonValidationError(handle.definition.name, err, raw2);
1185
1574
  }
1186
1575
  throw err;
1187
1576
  }
@@ -1189,7 +1578,7 @@ async function findOne(handle, filter, options) {
1189
1578
  async function findOneOrThrow(handle, filter, options) {
1190
1579
  const doc = await findOne(handle, filter, options);
1191
1580
  if (!doc) {
1192
- throw new ZodmonNotFoundError(handle.definition.name);
1581
+ throw new ZodmonNotFoundError(handle.definition.name, filter);
1193
1582
  }
1194
1583
  return doc;
1195
1584
  }
@@ -1198,7 +1587,12 @@ function find(handle, filter, options) {
1198
1587
  const raw2 = handle.native.find(filter);
1199
1588
  const cursor = raw2;
1200
1589
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
1201
- return new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
1590
+ const typedCursor = new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
1591
+ const project = options && "project" in options ? options.project : void 0;
1592
+ if (project) {
1593
+ return typedCursor.project(project);
1594
+ }
1595
+ return typedCursor;
1202
1596
  }
1203
1597
 
1204
1598
  // src/crud/insert.ts
@@ -1209,11 +1603,15 @@ async function insertOne(handle, doc) {
1209
1603
  parsed = handle.definition.schema.parse(doc);
1210
1604
  } catch (err) {
1211
1605
  if (err instanceof z5.ZodError) {
1212
- throw new ZodmonValidationError(handle.definition.name, err);
1606
+ throw new ZodmonValidationError(handle.definition.name, err, doc);
1213
1607
  }
1214
1608
  throw err;
1215
1609
  }
1216
- await handle.native.insertOne(parsed);
1610
+ try {
1611
+ await handle.native.insertOne(parsed);
1612
+ } catch (err) {
1613
+ wrapMongoError(err, handle.definition.name);
1614
+ }
1217
1615
  return parsed;
1218
1616
  }
1219
1617
  async function insertMany(handle, docs) {
@@ -1224,22 +1622,34 @@ async function insertMany(handle, docs) {
1224
1622
  parsed.push(handle.definition.schema.parse(doc));
1225
1623
  } catch (err) {
1226
1624
  if (err instanceof z5.ZodError) {
1227
- throw new ZodmonValidationError(handle.definition.name, err);
1625
+ throw new ZodmonValidationError(handle.definition.name, err, doc);
1228
1626
  }
1229
1627
  throw err;
1230
1628
  }
1231
1629
  }
1232
- await handle.native.insertMany(parsed);
1630
+ try {
1631
+ await handle.native.insertMany(parsed);
1632
+ } catch (err) {
1633
+ wrapMongoError(err, handle.definition.name);
1634
+ }
1233
1635
  return parsed;
1234
1636
  }
1235
1637
 
1236
1638
  // src/crud/update.ts
1237
1639
  import { z as z6 } from "zod";
1238
1640
  async function updateOne(handle, filter, update, options) {
1239
- return await handle.native.updateOne(filter, update, options);
1641
+ try {
1642
+ return await handle.native.updateOne(filter, update, options);
1643
+ } catch (err) {
1644
+ wrapMongoError(err, handle.definition.name);
1645
+ }
1240
1646
  }
1241
1647
  async function updateMany(handle, filter, update, options) {
1242
- return await handle.native.updateMany(filter, update, options);
1648
+ try {
1649
+ return await handle.native.updateMany(filter, update, options);
1650
+ } catch (err) {
1651
+ wrapMongoError(err, handle.definition.name);
1652
+ }
1243
1653
  }
1244
1654
  async function findOneAndUpdate(handle, filter, update, options) {
1245
1655
  const driverOptions = {
@@ -1249,14 +1659,19 @@ async function findOneAndUpdate(handle, filter, update, options) {
1249
1659
  if (options?.upsert !== void 0) {
1250
1660
  driverOptions["upsert"] = options.upsert;
1251
1661
  }
1252
- const result = await handle.native.findOneAndUpdate(
1253
- // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
1254
- filter,
1255
- // biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
1256
- update,
1257
- // biome-ignore lint/suspicious/noExplicitAny: dynamic options object is not assignable to driver's FindOneAndUpdateOptions under exactOptionalPropertyTypes
1258
- driverOptions
1259
- );
1662
+ let result;
1663
+ try {
1664
+ result = await handle.native.findOneAndUpdate(
1665
+ // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
1666
+ filter,
1667
+ // biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
1668
+ update,
1669
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic options object is not assignable to driver's FindOneAndUpdateOptions under exactOptionalPropertyTypes
1670
+ driverOptions
1671
+ );
1672
+ } catch (err) {
1673
+ wrapMongoError(err, handle.definition.name);
1674
+ }
1260
1675
  if (!result) return null;
1261
1676
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
1262
1677
  if (mode === false || mode === "passthrough") {
@@ -1266,7 +1681,7 @@ async function findOneAndUpdate(handle, filter, update, options) {
1266
1681
  return handle.definition.schema.parse(result);
1267
1682
  } catch (err) {
1268
1683
  if (err instanceof z6.ZodError) {
1269
- throw new ZodmonValidationError(handle.definition.name, err);
1684
+ throw new ZodmonValidationError(handle.definition.name, err, result);
1270
1685
  }
1271
1686
  throw err;
1272
1687
  }
@@ -1326,70 +1741,12 @@ var CollectionHandle = class {
1326
1741
  async insertMany(docs) {
1327
1742
  return await insertMany(this, docs);
1328
1743
  }
1329
- /**
1330
- * Find a single document matching the filter.
1331
- *
1332
- * Queries MongoDB, then validates the fetched document against the collection's
1333
- * Zod schema. Validation mode is resolved from the per-query option, falling
1334
- * back to the collection-level default (which defaults to `'strict'`).
1335
- *
1336
- * @param filter - Type-safe filter to match documents.
1337
- * @param options - Optional projection and validation overrides.
1338
- * @returns The matched document, or `null` if no document matches.
1339
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1340
- *
1341
- * @example
1342
- * ```ts
1343
- * const users = db.use(Users)
1344
- * const user = await users.findOne({ name: 'Ada' })
1345
- * if (user) console.log(user.role)
1346
- * ```
1347
- */
1348
1744
  async findOne(filter, options) {
1349
1745
  return await findOne(this, filter, options);
1350
1746
  }
1351
- /**
1352
- * Find a single document matching the filter, or throw if none exists.
1353
- *
1354
- * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
1355
- * instead of returning `null` when no document matches the filter.
1356
- *
1357
- * @param filter - Type-safe filter to match documents.
1358
- * @param options - Optional projection and validation overrides.
1359
- * @returns The matched document (never null).
1360
- * @throws {ZodmonNotFoundError} When no document matches the filter.
1361
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1362
- *
1363
- * @example
1364
- * ```ts
1365
- * const users = db.use(Users)
1366
- * const user = await users.findOneOrThrow({ name: 'Ada' })
1367
- * console.log(user.role) // guaranteed non-null
1368
- * ```
1369
- */
1370
1747
  async findOneOrThrow(filter, options) {
1371
1748
  return await findOneOrThrow(this, filter, options);
1372
1749
  }
1373
- /**
1374
- * Find all documents matching the filter, returning a chainable typed cursor.
1375
- *
1376
- * The cursor is lazy — no query is executed until a terminal method
1377
- * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
1378
- * to shape the query before executing.
1379
- *
1380
- * @param filter - Type-safe filter to match documents.
1381
- * @param options - Optional validation overrides.
1382
- * @returns A typed cursor for chaining query modifiers.
1383
- *
1384
- * @example
1385
- * ```ts
1386
- * const users = db.use(Users)
1387
- * const admins = await users.find({ role: 'admin' })
1388
- * .sort({ name: 1 })
1389
- * .limit(10)
1390
- * .toArray()
1391
- * ```
1392
- */
1393
1750
  find(filter, options) {
1394
1751
  return find(this, filter, options);
1395
1752
  }
@@ -1957,8 +2314,18 @@ export {
1957
2314
  Database,
1958
2315
  IndexBuilder,
1959
2316
  TypedFindCursor,
2317
+ ZodmonAuthError,
2318
+ ZodmonBulkWriteError,
2319
+ ZodmonDocValidationError,
2320
+ ZodmonDuplicateKeyError,
2321
+ ZodmonError,
2322
+ ZodmonIndexError,
2323
+ ZodmonNetworkError,
1960
2324
  ZodmonNotFoundError,
2325
+ ZodmonQueryError,
2326
+ ZodmonTimeoutError,
1961
2327
  ZodmonValidationError,
2328
+ ZodmonWriteConflictError,
1962
2329
  aggregate,
1963
2330
  checkUnindexedFields,
1964
2331
  collection,
@@ -1967,6 +2334,7 @@ export {
1967
2334
  createExpressionBuilder,
1968
2335
  deleteMany,
1969
2336
  deleteOne,
2337
+ deriveProjectedSchema,
1970
2338
  extractComparableOptions,
1971
2339
  extractDbName,
1972
2340
  extractFieldIndexes,
@@ -1981,6 +2349,7 @@ export {
1981
2349
  index,
1982
2350
  insertMany,
1983
2351
  insertOne,
2352
+ isInclusionProjection,
1984
2353
  isOid,
1985
2354
  objectId,
1986
2355
  oid,
@@ -1990,6 +2359,7 @@ export {
1990
2359
  toCompoundIndexSpec,
1991
2360
  toFieldIndexSpec,
1992
2361
  updateMany,
1993
- updateOne
2362
+ updateOne,
2363
+ wrapMongoError
1994
2364
  };
1995
2365
  //# sourceMappingURL=index.js.map