@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.cjs CHANGED
@@ -49,8 +49,18 @@ __export(index_exports, {
49
49
  Database: () => Database,
50
50
  IndexBuilder: () => IndexBuilder,
51
51
  TypedFindCursor: () => TypedFindCursor,
52
+ ZodmonAuthError: () => ZodmonAuthError,
53
+ ZodmonBulkWriteError: () => ZodmonBulkWriteError,
54
+ ZodmonDocValidationError: () => ZodmonDocValidationError,
55
+ ZodmonDuplicateKeyError: () => ZodmonDuplicateKeyError,
56
+ ZodmonError: () => ZodmonError,
57
+ ZodmonIndexError: () => ZodmonIndexError,
58
+ ZodmonNetworkError: () => ZodmonNetworkError,
52
59
  ZodmonNotFoundError: () => ZodmonNotFoundError,
60
+ ZodmonQueryError: () => ZodmonQueryError,
61
+ ZodmonTimeoutError: () => ZodmonTimeoutError,
53
62
  ZodmonValidationError: () => ZodmonValidationError,
63
+ ZodmonWriteConflictError: () => ZodmonWriteConflictError,
54
64
  aggregate: () => aggregate,
55
65
  checkUnindexedFields: () => checkUnindexedFields,
56
66
  collection: () => collection,
@@ -59,6 +69,7 @@ __export(index_exports, {
59
69
  createExpressionBuilder: () => createExpressionBuilder,
60
70
  deleteMany: () => deleteMany,
61
71
  deleteOne: () => deleteOne,
72
+ deriveProjectedSchema: () => deriveProjectedSchema,
62
73
  extractComparableOptions: () => extractComparableOptions,
63
74
  extractDbName: () => extractDbName,
64
75
  extractFieldIndexes: () => extractFieldIndexes,
@@ -73,6 +84,7 @@ __export(index_exports, {
73
84
  index: () => index,
74
85
  insertMany: () => insertMany,
75
86
  insertOne: () => insertOne,
87
+ isInclusionProjection: () => isInclusionProjection,
76
88
  isOid: () => isOid,
77
89
  objectId: () => objectId,
78
90
  oid: () => oid,
@@ -82,7 +94,8 @@ __export(index_exports, {
82
94
  toCompoundIndexSpec: () => toCompoundIndexSpec,
83
95
  toFieldIndexSpec: () => toFieldIndexSpec,
84
96
  updateMany: () => updateMany,
85
- updateOne: () => updateOne
97
+ updateOne: () => updateOne,
98
+ wrapMongoError: () => wrapMongoError
86
99
  });
87
100
  module.exports = __toCommonJS(index_exports);
88
101
 
@@ -99,30 +112,24 @@ var $avg = (field) => ({
99
112
  __accum: true,
100
113
  expr: { $avg: field }
101
114
  });
102
- var $min = (field) => ({
103
- __accum: true,
104
- expr: { $min: field }
105
- });
106
- var $max = (field) => ({
107
- __accum: true,
108
- expr: { $max: field }
109
- });
110
- var $first = (field) => ({
111
- __accum: true,
112
- expr: { $first: field }
113
- });
114
- var $last = (field) => ({
115
- __accum: true,
116
- expr: { $last: field }
117
- });
118
- var $push = (field) => ({
119
- __accum: true,
120
- expr: { $push: field }
121
- });
122
- var $addToSet = (field) => ({
123
- __accum: true,
124
- expr: { $addToSet: field }
125
- });
115
+ function $min(field) {
116
+ return { __accum: true, expr: { $min: field } };
117
+ }
118
+ function $max(field) {
119
+ return { __accum: true, expr: { $max: field } };
120
+ }
121
+ function $first(field) {
122
+ return { __accum: true, expr: { $first: field } };
123
+ }
124
+ function $last(field) {
125
+ return { __accum: true, expr: { $last: field } };
126
+ }
127
+ function $push(field) {
128
+ return { __accum: true, expr: { $push: field } };
129
+ }
130
+ function $addToSet(field) {
131
+ return { __accum: true, expr: { $addToSet: field } };
132
+ }
126
133
  function createAccumulatorBuilder() {
127
134
  return {
128
135
  count: () => ({ __accum: true, expr: { $sum: 1 } }),
@@ -187,6 +194,245 @@ function createExpressionBuilder() {
187
194
  };
188
195
  }
189
196
 
197
+ // src/errors/wrap.ts
198
+ var import_mongodb = require("mongodb");
199
+
200
+ // src/errors/base.ts
201
+ var ZodmonError = class extends Error {
202
+ name = "ZodmonError";
203
+ /** The MongoDB collection name associated with this error. */
204
+ collection;
205
+ /** The underlying error that caused this error, if any. */
206
+ cause;
207
+ constructor(message, collection2, options) {
208
+ super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
209
+ this.collection = collection2;
210
+ if (options?.cause !== void 0) {
211
+ this.cause = options.cause;
212
+ }
213
+ }
214
+ };
215
+
216
+ // src/errors/auth.ts
217
+ var ZodmonAuthError = class extends ZodmonError {
218
+ name = "ZodmonAuthError";
219
+ /** The MongoDB error code (13 or 18). */
220
+ code;
221
+ constructor(collection2, code, cause) {
222
+ const message = code === 18 ? `Authentication failed for "${collection2}": check connection credentials` : `Not authorized to perform this operation on "${collection2}"`;
223
+ super(message, collection2, { cause });
224
+ this.code = code;
225
+ }
226
+ };
227
+
228
+ // src/errors/bulk-write.ts
229
+ var ZodmonBulkWriteError = class extends ZodmonError {
230
+ name = "ZodmonBulkWriteError";
231
+ /** Number of documents successfully inserted. */
232
+ insertedCount;
233
+ /** Number of documents matched by update filters. */
234
+ matchedCount;
235
+ /** Number of documents actually modified. */
236
+ modifiedCount;
237
+ /** Number of documents deleted. */
238
+ deletedCount;
239
+ /** Individual write errors with their operation index, code, and message. */
240
+ writeErrors;
241
+ constructor(collection2, cause, totalOps) {
242
+ const bulkErr = cause;
243
+ const result = bulkErr["result"] ?? {};
244
+ const rawErrors = bulkErr["writeErrors"] ?? [];
245
+ const writeErrors = rawErrors.map((e) => ({
246
+ index: e["index"] ?? 0,
247
+ code: e["code"] ?? 0,
248
+ message: e["errmsg"] ?? e["message"] ?? "unknown error"
249
+ }));
250
+ const failedMsg = totalOps !== void 0 ? `${writeErrors.length} of ${totalOps} operations failed` : `${writeErrors.length} operations failed`;
251
+ super(`Bulk write failed on "${collection2}": ${failedMsg}`, collection2, { cause });
252
+ this.insertedCount = result["insertedCount"] ?? 0;
253
+ this.matchedCount = result["matchedCount"] ?? 0;
254
+ this.modifiedCount = result["modifiedCount"] ?? 0;
255
+ this.deletedCount = result["deletedCount"] ?? 0;
256
+ this.writeErrors = writeErrors;
257
+ }
258
+ };
259
+
260
+ // src/errors/doc-validation.ts
261
+ var ZodmonDocValidationError = class extends ZodmonError {
262
+ name = "ZodmonDocValidationError";
263
+ /** Server-provided validation failure details. */
264
+ errInfo;
265
+ constructor(collection2, errInfo, cause) {
266
+ super(
267
+ `Server-side document validation failed for "${collection2}": ${cause.message}`,
268
+ collection2,
269
+ { cause }
270
+ );
271
+ this.errInfo = errInfo;
272
+ }
273
+ };
274
+
275
+ // src/errors/duplicate-key.ts
276
+ var INDEX_REGEX = /index:\s+(\S+)/;
277
+ var DUP_KEY_FIELD_REGEX = /dup key:\s*\{\s*(\w+):/;
278
+ var ZodmonDuplicateKeyError = class extends ZodmonError {
279
+ name = "ZodmonDuplicateKeyError";
280
+ /** The first field that caused the duplicate key violation. */
281
+ field;
282
+ /** The duplicate value, or `undefined` if it could not be extracted. */
283
+ value;
284
+ /** The name of the index that was violated. */
285
+ index;
286
+ /** The key pattern of the violated index (e.g. `{ email: 1 }`). */
287
+ keyPattern;
288
+ /** The key values that caused the violation. */
289
+ keyValue;
290
+ constructor(collection2, cause) {
291
+ const serverErr = cause;
292
+ const kp = serverErr["keyPattern"];
293
+ const kv = serverErr["keyValue"];
294
+ let field;
295
+ let value;
296
+ let keyPattern;
297
+ let keyValue;
298
+ if (kp && kv) {
299
+ const firstKey = Object.keys(kp)[0] ?? "unknown";
300
+ field = firstKey;
301
+ value = kv[firstKey];
302
+ keyPattern = kp;
303
+ keyValue = kv;
304
+ } else {
305
+ const fieldMatch = cause.message.match(DUP_KEY_FIELD_REGEX);
306
+ field = fieldMatch?.[1] ?? "unknown";
307
+ value = void 0;
308
+ keyPattern = field !== "unknown" ? { [field]: 1 } : {};
309
+ keyValue = {};
310
+ }
311
+ const indexMatch = cause.message.match(INDEX_REGEX);
312
+ const index2 = indexMatch?.[1] ?? "unknown";
313
+ const valueStr = typeof value === "string" ? `"${value}"` : String(value);
314
+ super(
315
+ `Duplicate key in "${collection2}": ${field} = ${valueStr} (index: ${index2})`,
316
+ collection2,
317
+ { cause }
318
+ );
319
+ this.field = field;
320
+ this.value = value;
321
+ this.index = index2;
322
+ this.keyPattern = keyPattern;
323
+ this.keyValue = keyValue;
324
+ }
325
+ };
326
+
327
+ // src/errors/index-error.ts
328
+ var ZodmonIndexError = class extends ZodmonError {
329
+ name = "ZodmonIndexError";
330
+ /** The MongoDB error code (67, 85, or 86). */
331
+ code;
332
+ constructor(collection2, code, errmsg, cause) {
333
+ const prefix = code === 67 ? "Cannot create index" : code === 85 ? "Index options conflict" : "Index key specs conflict";
334
+ super(`${prefix} on "${collection2}": ${errmsg}`, collection2, { cause });
335
+ this.code = code;
336
+ }
337
+ };
338
+
339
+ // src/errors/network.ts
340
+ var ZodmonNetworkError = class extends ZodmonError {
341
+ name = "ZodmonNetworkError";
342
+ constructor(collection2, cause) {
343
+ super(`Network error on "${collection2}": ${cause.message}`, collection2, { cause });
344
+ }
345
+ };
346
+
347
+ // src/errors/query.ts
348
+ var ZodmonQueryError = class extends ZodmonError {
349
+ name = "ZodmonQueryError";
350
+ /** The MongoDB error code (2, 9, or 292). */
351
+ code;
352
+ constructor(collection2, code, errmsg, cause) {
353
+ 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}`;
354
+ super(message, collection2, { cause });
355
+ this.code = code;
356
+ }
357
+ };
358
+
359
+ // src/errors/timeout.ts
360
+ var ZodmonTimeoutError = class extends ZodmonError {
361
+ name = "ZodmonTimeoutError";
362
+ /** The MongoDB error code (50 or 262). */
363
+ code;
364
+ constructor(collection2, code, cause) {
365
+ super(`Operation timed out on "${collection2}": exceeded server time limit`, collection2, {
366
+ cause
367
+ });
368
+ this.code = code;
369
+ }
370
+ };
371
+
372
+ // src/errors/write-conflict.ts
373
+ var ZodmonWriteConflictError = class extends ZodmonError {
374
+ name = "ZodmonWriteConflictError";
375
+ constructor(collection2, cause) {
376
+ super(
377
+ `Write conflict in "${collection2}": another operation modified this document concurrently \u2014 retry the transaction`,
378
+ collection2,
379
+ { cause }
380
+ );
381
+ }
382
+ };
383
+
384
+ // src/errors/wrap.ts
385
+ function wrapMongoError(err, collection2) {
386
+ if (err instanceof ZodmonError) {
387
+ throw err;
388
+ }
389
+ if (err instanceof import_mongodb.MongoBulkWriteError) {
390
+ throw new ZodmonBulkWriteError(collection2, err);
391
+ }
392
+ if (err instanceof import_mongodb.MongoNetworkError) {
393
+ throw new ZodmonNetworkError(collection2, err);
394
+ }
395
+ if (err instanceof import_mongodb.MongoServerError) {
396
+ switch (err.code) {
397
+ case 11e3:
398
+ case 11001:
399
+ throw new ZodmonDuplicateKeyError(collection2, err);
400
+ case 112:
401
+ throw new ZodmonWriteConflictError(collection2, err);
402
+ case 50:
403
+ case 262:
404
+ throw new ZodmonTimeoutError(collection2, err.code, err);
405
+ case 13:
406
+ case 18:
407
+ throw new ZodmonAuthError(collection2, err.code, err);
408
+ case 67:
409
+ case 85:
410
+ case 86:
411
+ throw new ZodmonIndexError(collection2, err.code, err.message, err);
412
+ case 2:
413
+ case 9:
414
+ case 292:
415
+ throw new ZodmonQueryError(collection2, err.code, err.message, err);
416
+ case 121:
417
+ throw new ZodmonDocValidationError(
418
+ collection2,
419
+ err.errInfo,
420
+ err
421
+ );
422
+ default:
423
+ throw new ZodmonError(`MongoDB error on "${collection2}": ${err.message}`, collection2, {
424
+ cause: err
425
+ });
426
+ }
427
+ }
428
+ if (err instanceof Error) {
429
+ throw new ZodmonError(`Unexpected error on "${collection2}": ${err.message}`, collection2, {
430
+ cause: err
431
+ });
432
+ }
433
+ throw new ZodmonError(`Unexpected error on "${collection2}": ${String(err)}`, collection2);
434
+ }
435
+
190
436
  // src/schema/ref.ts
191
437
  var import_zod = require("zod");
192
438
  var refMetadata = /* @__PURE__ */ new WeakMap();
@@ -272,8 +518,12 @@ var AggregatePipeline = class _AggregatePipeline {
272
518
  * ```
273
519
  */
274
520
  async toArray() {
275
- const cursor = this.nativeCollection.aggregate(this.stages);
276
- return await cursor.toArray();
521
+ try {
522
+ const cursor = this.nativeCollection.aggregate(this.stages);
523
+ return await cursor.toArray();
524
+ } catch (err) {
525
+ wrapMongoError(err, this.definition.name);
526
+ }
277
527
  }
278
528
  /**
279
529
  * Stream pipeline results one document at a time via `for await...of`.
@@ -288,9 +538,13 @@ var AggregatePipeline = class _AggregatePipeline {
288
538
  * ```
289
539
  */
290
540
  async *[Symbol.asyncIterator]() {
291
- const cursor = this.nativeCollection.aggregate(this.stages);
292
- for await (const doc of cursor) {
293
- yield doc;
541
+ try {
542
+ const cursor = this.nativeCollection.aggregate(this.stages);
543
+ for await (const doc of cursor) {
544
+ yield doc;
545
+ }
546
+ } catch (err) {
547
+ wrapMongoError(err, this.definition.name);
294
548
  }
295
549
  }
296
550
  /**
@@ -310,8 +564,12 @@ var AggregatePipeline = class _AggregatePipeline {
310
564
  * ```
311
565
  */
312
566
  async explain() {
313
- const cursor = this.nativeCollection.aggregate(this.stages);
314
- return await cursor.explain();
567
+ try {
568
+ const cursor = this.nativeCollection.aggregate(this.stages);
569
+ return await cursor.explain();
570
+ } catch (err) {
571
+ wrapMongoError(err, this.definition.name);
572
+ }
315
573
  }
316
574
  // ── Shape-preserving stages ──────────────────────────────────────
317
575
  /**
@@ -736,7 +994,7 @@ function aggregate(handle) {
736
994
  }
737
995
 
738
996
  // src/client/client.ts
739
- var import_mongodb2 = require("mongodb");
997
+ var import_mongodb3 = require("mongodb");
740
998
 
741
999
  // src/indexes/spec.ts
742
1000
  function toFieldIndexSpec(def) {
@@ -808,7 +1066,7 @@ async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrpha
808
1066
  const serialized = serializeIndexKey(spec.key);
809
1067
  const existing = existingByKey.get(serialized);
810
1068
  if (!existing) {
811
- if (!dryRun) await native.createIndex(spec.key, spec.options);
1069
+ if (!dryRun) await safeCreateIndex(native, spec.key, spec.options);
812
1070
  acc.created.push(resolveSpecName(spec));
813
1071
  return;
814
1072
  }
@@ -821,8 +1079,8 @@ async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrpha
821
1079
  }
822
1080
  if (dropOrphaned) {
823
1081
  if (!dryRun) {
824
- await native.dropIndex(existingName);
825
- await native.createIndex(spec.key, spec.options);
1082
+ await safeDropIndex(native, existingName);
1083
+ await safeCreateIndex(native, spec.key, spec.options);
826
1084
  }
827
1085
  acc.dropped.push(existingName);
828
1086
  acc.created.push(resolveSpecName(spec));
@@ -835,6 +1093,20 @@ async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrpha
835
1093
  desired: spec.options
836
1094
  });
837
1095
  }
1096
+ async function safeCreateIndex(native, key, options) {
1097
+ try {
1098
+ await native.createIndex(key, options);
1099
+ } catch (err) {
1100
+ wrapMongoError(err, native.collectionName);
1101
+ }
1102
+ }
1103
+ async function safeDropIndex(native, name) {
1104
+ try {
1105
+ await native.dropIndex(name);
1106
+ } catch (err) {
1107
+ wrapMongoError(err, native.collectionName);
1108
+ }
1109
+ }
838
1110
  async function processOrphanedIndexes(existingIndexes, desiredKeys, matchedKeys, native, dryRun, dropOrphaned, dropped) {
839
1111
  for (const idx of existingIndexes) {
840
1112
  const rawName = idx["name"];
@@ -843,7 +1115,7 @@ async function processOrphanedIndexes(existingIndexes, desiredKeys, matchedKeys,
843
1115
  const serialized = serializeIndexKey(idx["key"]);
844
1116
  if (matchedKeys.has(serialized) || desiredKeys.has(serialized)) continue;
845
1117
  if (dropOrphaned) {
846
- if (!dryRun) await native.dropIndex(name);
1118
+ if (!dryRun) await safeDropIndex(native, name);
847
1119
  dropped.push(name);
848
1120
  }
849
1121
  }
@@ -855,7 +1127,7 @@ async function listIndexesSafe(native) {
855
1127
  if (err instanceof Error && err.message.includes("ns does not exist")) {
856
1128
  return [];
857
1129
  }
858
- throw err;
1130
+ wrapMongoError(err, native.collectionName);
859
1131
  }
860
1132
  }
861
1133
  async function syncIndexes(handle, options) {
@@ -904,36 +1176,49 @@ async function syncIndexes(handle, options) {
904
1176
  var import_zod2 = require("zod");
905
1177
 
906
1178
  // src/errors/validation.ts
907
- var ZodmonValidationError = class extends Error {
1179
+ var ZodmonValidationError = class extends ZodmonError {
908
1180
  name = "ZodmonValidationError";
909
- /** The MongoDB collection name where the validation failed. */
910
- collection;
911
1181
  /** The original Zod validation error with detailed issue information. */
912
1182
  zodError;
913
- constructor(collection2, zodError) {
1183
+ /** The document that failed validation. */
1184
+ document;
1185
+ constructor(collection2, zodError, document) {
914
1186
  const fields = zodError.issues.map((issue) => {
915
1187
  const path = issue.path.join(".") || "(root)";
916
1188
  return `${path} (${issue.message})`;
917
1189
  }).join(", ");
918
- super(`Validation failed for "${collection2}": ${fields}`);
919
- this.collection = collection2;
1190
+ super(`Validation failed for "${collection2}": ${fields}`, collection2, { cause: zodError });
920
1191
  this.zodError = zodError;
1192
+ this.document = document;
921
1193
  }
922
1194
  };
923
1195
 
924
1196
  // src/crud/delete.ts
925
1197
  async function deleteOne(handle, filter) {
926
- return await handle.native.deleteOne(filter);
1198
+ try {
1199
+ return await handle.native.deleteOne(filter);
1200
+ } catch (err) {
1201
+ wrapMongoError(err, handle.definition.name);
1202
+ }
927
1203
  }
928
1204
  async function deleteMany(handle, filter) {
929
- return await handle.native.deleteMany(filter);
1205
+ try {
1206
+ return await handle.native.deleteMany(filter);
1207
+ } catch (err) {
1208
+ wrapMongoError(err, handle.definition.name);
1209
+ }
930
1210
  }
931
1211
  async function findOneAndDelete(handle, filter, options) {
932
- const result = await handle.native.findOneAndDelete(
933
- // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
934
- filter,
935
- { includeResultMetadata: false }
936
- );
1212
+ let result;
1213
+ try {
1214
+ result = await handle.native.findOneAndDelete(
1215
+ // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
1216
+ filter,
1217
+ { includeResultMetadata: false }
1218
+ );
1219
+ } catch (err) {
1220
+ wrapMongoError(err, handle.definition.name);
1221
+ }
937
1222
  if (!result) return null;
938
1223
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
939
1224
  if (mode === false || mode === "passthrough") {
@@ -943,7 +1228,7 @@ async function findOneAndDelete(handle, filter, options) {
943
1228
  return handle.definition.schema.parse(result);
944
1229
  } catch (err) {
945
1230
  if (err instanceof import_zod2.z.ZodError) {
946
- throw new ZodmonValidationError(handle.definition.name, err);
1231
+ throw new ZodmonValidationError(handle.definition.name, err, result);
947
1232
  }
948
1233
  throw err;
949
1234
  }
@@ -953,13 +1238,13 @@ async function findOneAndDelete(handle, filter, options) {
953
1238
  var import_zod4 = require("zod");
954
1239
 
955
1240
  // src/errors/not-found.ts
956
- var ZodmonNotFoundError = class extends Error {
1241
+ var ZodmonNotFoundError = class extends ZodmonError {
957
1242
  name = "ZodmonNotFoundError";
958
- /** The MongoDB collection name where the query found no results. */
959
- collection;
960
- constructor(collection2) {
961
- super(`Document not found in "${collection2}"`);
962
- this.collection = collection2;
1243
+ /** The filter that produced no results. */
1244
+ filter;
1245
+ constructor(collection2, filter) {
1246
+ super(`Document not found in "${collection2}"`, collection2);
1247
+ this.filter = filter;
963
1248
  }
964
1249
  };
965
1250
 
@@ -990,15 +1275,15 @@ function checkUnindexedFields(definition, filter) {
990
1275
  var import_zod3 = require("zod");
991
1276
 
992
1277
  // src/crud/paginate.ts
993
- var import_mongodb = require("mongodb");
1278
+ var import_mongodb2 = require("mongodb");
994
1279
  function serializeValue(value) {
995
- if (value instanceof import_mongodb.ObjectId) return { $oid: value.toHexString() };
1280
+ if (value instanceof import_mongodb2.ObjectId) return { $oid: value.toHexString() };
996
1281
  if (value instanceof Date) return { $date: value.getTime() };
997
1282
  return value;
998
1283
  }
999
1284
  function deserializeValue(value) {
1000
1285
  if (value != null && typeof value === "object") {
1001
- if ("$oid" in value) return new import_mongodb.ObjectId(value.$oid);
1286
+ if ("$oid" in value) return new import_mongodb2.ObjectId(value.$oid);
1002
1287
  if ("$date" in value) return new Date(value.$date);
1003
1288
  }
1004
1289
  return value;
@@ -1050,6 +1335,54 @@ function resolveSortKeys(sortSpec) {
1050
1335
  return entries;
1051
1336
  }
1052
1337
 
1338
+ // src/query/projection.ts
1339
+ function isIncludeValue(value) {
1340
+ return value === 1 || value === true;
1341
+ }
1342
+ function isExcludeValue(value) {
1343
+ return value === 0 || value === false;
1344
+ }
1345
+ function isInclusionProjection(projection) {
1346
+ for (const key of Object.keys(projection)) {
1347
+ if (key === "_id") continue;
1348
+ const value = projection[key];
1349
+ if (value !== void 0 && isIncludeValue(value)) return true;
1350
+ }
1351
+ return false;
1352
+ }
1353
+ function buildPickMask(projection, schemaKeys) {
1354
+ const mask = {};
1355
+ const idValue = projection._id;
1356
+ if (!(idValue !== void 0 && isExcludeValue(idValue)) && schemaKeys.has("_id")) {
1357
+ mask._id = true;
1358
+ }
1359
+ for (const key of Object.keys(projection)) {
1360
+ if (key === "_id") continue;
1361
+ const value = projection[key];
1362
+ if (value !== void 0 && isIncludeValue(value) && schemaKeys.has(key)) {
1363
+ mask[key] = true;
1364
+ }
1365
+ }
1366
+ return mask;
1367
+ }
1368
+ function buildOmitMask(projection, schemaKeys) {
1369
+ const mask = {};
1370
+ for (const key of Object.keys(projection)) {
1371
+ const value = projection[key];
1372
+ if (value !== void 0 && isExcludeValue(value) && schemaKeys.has(key)) {
1373
+ mask[key] = true;
1374
+ }
1375
+ }
1376
+ return mask;
1377
+ }
1378
+ function deriveProjectedSchema(schema, projection) {
1379
+ const schemaKeys = new Set(Object.keys(schema.shape));
1380
+ if (isInclusionProjection(projection)) {
1381
+ return schema.pick(buildPickMask(projection, schemaKeys));
1382
+ }
1383
+ return schema.omit(buildOmitMask(projection, schemaKeys));
1384
+ }
1385
+
1053
1386
  // src/query/cursor.ts
1054
1387
  var TypedFindCursor = class {
1055
1388
  /** @internal */
@@ -1068,6 +1401,8 @@ var TypedFindCursor = class {
1068
1401
  /** @internal */
1069
1402
  sortSpec;
1070
1403
  /** @internal */
1404
+ projectedSchema;
1405
+ /** @internal */
1071
1406
  constructor(cursor, definition, mode, nativeCollection, filter) {
1072
1407
  this.cursor = cursor;
1073
1408
  this.schema = definition.schema;
@@ -1076,6 +1411,7 @@ var TypedFindCursor = class {
1076
1411
  this.nativeCollection = nativeCollection;
1077
1412
  this.filter = filter;
1078
1413
  this.sortSpec = null;
1414
+ this.projectedSchema = null;
1079
1415
  }
1080
1416
  /**
1081
1417
  * Set the sort order for the query.
@@ -1149,6 +1485,40 @@ var TypedFindCursor = class {
1149
1485
  this.cursor.hint(indexName);
1150
1486
  return this;
1151
1487
  }
1488
+ /**
1489
+ * Apply a projection to narrow the returned fields.
1490
+ *
1491
+ * Inclusion projections (`{ name: 1 }`) return only the specified fields
1492
+ * plus `_id` (unless `_id: 0`). Exclusion projections (`{ email: 0 }`)
1493
+ * return all fields except those excluded.
1494
+ *
1495
+ * The cursor's output type is narrowed at compile time. A derived Zod
1496
+ * schema is built for runtime validation of the projected fields.
1497
+ *
1498
+ * Projects from the original document type, not from a previous projection.
1499
+ * Calling `.project()` twice overrides the previous projection.
1500
+ *
1501
+ * @param spec - Type-safe projection document.
1502
+ * @returns A new cursor with the narrowed output type.
1503
+ *
1504
+ * @example
1505
+ * ```ts
1506
+ * const names = await find(users, {})
1507
+ * .project({ name: 1 })
1508
+ * .sort({ name: 1 })
1509
+ * .toArray()
1510
+ * // names[0].name ✓
1511
+ * // names[0].email TS error
1512
+ * ```
1513
+ */
1514
+ project(spec) {
1515
+ this.cursor.project(spec);
1516
+ this.projectedSchema = deriveProjectedSchema(
1517
+ this.schema,
1518
+ spec
1519
+ );
1520
+ return this;
1521
+ }
1152
1522
  async paginate(opts) {
1153
1523
  const sortRecord = this.sortSpec ? this.sortSpec : null;
1154
1524
  const sortKeys2 = resolveSortKeys(sortRecord);
@@ -1160,10 +1530,17 @@ var TypedFindCursor = class {
1160
1530
  }
1161
1531
  /** @internal Offset pagination implementation. */
1162
1532
  async offsetPaginate(_sortKeys, sort, opts) {
1163
- const [total, raw2] = await Promise.all([
1164
- this.nativeCollection.countDocuments(this.filter),
1165
- this.nativeCollection.find(this.filter).sort(sort).skip((opts.page - 1) * opts.perPage).limit(opts.perPage).toArray()
1166
- ]);
1533
+ let total;
1534
+ let raw2;
1535
+ try {
1536
+ ;
1537
+ [total, raw2] = await Promise.all([
1538
+ this.nativeCollection.countDocuments(this.filter),
1539
+ this.nativeCollection.find(this.filter).sort(sort).skip((opts.page - 1) * opts.perPage).limit(opts.perPage).toArray()
1540
+ ]);
1541
+ } catch (err) {
1542
+ wrapMongoError(err, this.collectionName);
1543
+ }
1167
1544
  const docs = raw2.map((doc) => this.validateDoc(doc));
1168
1545
  const totalPages = Math.ceil(total / opts.perPage);
1169
1546
  return {
@@ -1187,7 +1564,12 @@ var TypedFindCursor = class {
1187
1564
  combinedFilter = this.filter && Object.keys(this.filter).length > 0 ? { $and: [this.filter, cursorFilter] } : cursorFilter;
1188
1565
  }
1189
1566
  const effectiveSort = isBackward ? Object.fromEntries(sortKeys2.map(([f, d]) => [f, d === 1 ? -1 : 1])) : sort;
1190
- const raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
1567
+ let raw2;
1568
+ try {
1569
+ raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
1570
+ } catch (err) {
1571
+ wrapMongoError(err, this.collectionName);
1572
+ }
1191
1573
  const hasMore = raw2.length > opts.limit;
1192
1574
  if (hasMore) raw2.pop();
1193
1575
  if (isBackward) raw2.reverse();
@@ -1215,7 +1597,12 @@ var TypedFindCursor = class {
1215
1597
  * ```
1216
1598
  */
1217
1599
  async toArray() {
1218
- const raw2 = await this.cursor.toArray();
1600
+ let raw2;
1601
+ try {
1602
+ raw2 = await this.cursor.toArray();
1603
+ } catch (err) {
1604
+ wrapMongoError(err, this.collectionName);
1605
+ }
1219
1606
  return raw2.map((doc) => this.validateDoc(doc));
1220
1607
  }
1221
1608
  /**
@@ -1235,8 +1622,12 @@ var TypedFindCursor = class {
1235
1622
  * ```
1236
1623
  */
1237
1624
  async *[Symbol.asyncIterator]() {
1238
- for await (const doc of this.cursor) {
1239
- yield this.validateDoc(doc);
1625
+ try {
1626
+ for await (const doc of this.cursor) {
1627
+ yield this.validateDoc(doc);
1628
+ }
1629
+ } catch (err) {
1630
+ wrapMongoError(err, this.collectionName);
1240
1631
  }
1241
1632
  }
1242
1633
  /** @internal Validate a single raw document against the schema. */
@@ -1244,11 +1635,12 @@ var TypedFindCursor = class {
1244
1635
  if (this.mode === false || this.mode === "passthrough") {
1245
1636
  return raw2;
1246
1637
  }
1638
+ const schema = this.projectedSchema ?? this.schema;
1247
1639
  try {
1248
- return this.schema.parse(raw2);
1640
+ return schema.parse(raw2);
1249
1641
  } catch (err) {
1250
1642
  if (err instanceof import_zod3.z.ZodError) {
1251
- throw new ZodmonValidationError(this.collectionName, err);
1643
+ throw new ZodmonValidationError(this.collectionName, err, raw2);
1252
1644
  }
1253
1645
  throw err;
1254
1646
  }
@@ -1258,18 +1650,28 @@ var TypedFindCursor = class {
1258
1650
  // src/crud/find.ts
1259
1651
  async function findOne(handle, filter, options) {
1260
1652
  checkUnindexedFields(handle.definition, filter);
1261
- const findOptions = options?.project ? { projection: options.project } : void 0;
1262
- const raw2 = await handle.native.findOne(filter, findOptions);
1653
+ const project = options && "project" in options ? options.project : void 0;
1654
+ const findOptions = project ? { projection: project } : void 0;
1655
+ let raw2;
1656
+ try {
1657
+ raw2 = await handle.native.findOne(filter, findOptions);
1658
+ } catch (err) {
1659
+ wrapMongoError(err, handle.definition.name);
1660
+ }
1263
1661
  if (!raw2) return null;
1264
1662
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
1265
1663
  if (mode === false || mode === "passthrough") {
1266
1664
  return raw2;
1267
1665
  }
1666
+ const schema = project ? deriveProjectedSchema(
1667
+ handle.definition.schema,
1668
+ project
1669
+ ) : handle.definition.schema;
1268
1670
  try {
1269
- return handle.definition.schema.parse(raw2);
1671
+ return schema.parse(raw2);
1270
1672
  } catch (err) {
1271
1673
  if (err instanceof import_zod4.z.ZodError) {
1272
- throw new ZodmonValidationError(handle.definition.name, err);
1674
+ throw new ZodmonValidationError(handle.definition.name, err, raw2);
1273
1675
  }
1274
1676
  throw err;
1275
1677
  }
@@ -1277,7 +1679,7 @@ async function findOne(handle, filter, options) {
1277
1679
  async function findOneOrThrow(handle, filter, options) {
1278
1680
  const doc = await findOne(handle, filter, options);
1279
1681
  if (!doc) {
1280
- throw new ZodmonNotFoundError(handle.definition.name);
1682
+ throw new ZodmonNotFoundError(handle.definition.name, filter);
1281
1683
  }
1282
1684
  return doc;
1283
1685
  }
@@ -1286,7 +1688,12 @@ function find(handle, filter, options) {
1286
1688
  const raw2 = handle.native.find(filter);
1287
1689
  const cursor = raw2;
1288
1690
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
1289
- return new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
1691
+ const typedCursor = new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
1692
+ const project = options && "project" in options ? options.project : void 0;
1693
+ if (project) {
1694
+ return typedCursor.project(project);
1695
+ }
1696
+ return typedCursor;
1290
1697
  }
1291
1698
 
1292
1699
  // src/crud/insert.ts
@@ -1297,11 +1704,15 @@ async function insertOne(handle, doc) {
1297
1704
  parsed = handle.definition.schema.parse(doc);
1298
1705
  } catch (err) {
1299
1706
  if (err instanceof import_zod5.z.ZodError) {
1300
- throw new ZodmonValidationError(handle.definition.name, err);
1707
+ throw new ZodmonValidationError(handle.definition.name, err, doc);
1301
1708
  }
1302
1709
  throw err;
1303
1710
  }
1304
- await handle.native.insertOne(parsed);
1711
+ try {
1712
+ await handle.native.insertOne(parsed);
1713
+ } catch (err) {
1714
+ wrapMongoError(err, handle.definition.name);
1715
+ }
1305
1716
  return parsed;
1306
1717
  }
1307
1718
  async function insertMany(handle, docs) {
@@ -1312,22 +1723,34 @@ async function insertMany(handle, docs) {
1312
1723
  parsed.push(handle.definition.schema.parse(doc));
1313
1724
  } catch (err) {
1314
1725
  if (err instanceof import_zod5.z.ZodError) {
1315
- throw new ZodmonValidationError(handle.definition.name, err);
1726
+ throw new ZodmonValidationError(handle.definition.name, err, doc);
1316
1727
  }
1317
1728
  throw err;
1318
1729
  }
1319
1730
  }
1320
- await handle.native.insertMany(parsed);
1731
+ try {
1732
+ await handle.native.insertMany(parsed);
1733
+ } catch (err) {
1734
+ wrapMongoError(err, handle.definition.name);
1735
+ }
1321
1736
  return parsed;
1322
1737
  }
1323
1738
 
1324
1739
  // src/crud/update.ts
1325
1740
  var import_zod6 = require("zod");
1326
1741
  async function updateOne(handle, filter, update, options) {
1327
- return await handle.native.updateOne(filter, update, options);
1742
+ try {
1743
+ return await handle.native.updateOne(filter, update, options);
1744
+ } catch (err) {
1745
+ wrapMongoError(err, handle.definition.name);
1746
+ }
1328
1747
  }
1329
1748
  async function updateMany(handle, filter, update, options) {
1330
- return await handle.native.updateMany(filter, update, options);
1749
+ try {
1750
+ return await handle.native.updateMany(filter, update, options);
1751
+ } catch (err) {
1752
+ wrapMongoError(err, handle.definition.name);
1753
+ }
1331
1754
  }
1332
1755
  async function findOneAndUpdate(handle, filter, update, options) {
1333
1756
  const driverOptions = {
@@ -1337,14 +1760,19 @@ async function findOneAndUpdate(handle, filter, update, options) {
1337
1760
  if (options?.upsert !== void 0) {
1338
1761
  driverOptions["upsert"] = options.upsert;
1339
1762
  }
1340
- const result = await handle.native.findOneAndUpdate(
1341
- // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
1342
- filter,
1343
- // biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
1344
- update,
1345
- // biome-ignore lint/suspicious/noExplicitAny: dynamic options object is not assignable to driver's FindOneAndUpdateOptions under exactOptionalPropertyTypes
1346
- driverOptions
1347
- );
1763
+ let result;
1764
+ try {
1765
+ result = await handle.native.findOneAndUpdate(
1766
+ // biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
1767
+ filter,
1768
+ // biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
1769
+ update,
1770
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic options object is not assignable to driver's FindOneAndUpdateOptions under exactOptionalPropertyTypes
1771
+ driverOptions
1772
+ );
1773
+ } catch (err) {
1774
+ wrapMongoError(err, handle.definition.name);
1775
+ }
1348
1776
  if (!result) return null;
1349
1777
  const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
1350
1778
  if (mode === false || mode === "passthrough") {
@@ -1354,7 +1782,7 @@ async function findOneAndUpdate(handle, filter, update, options) {
1354
1782
  return handle.definition.schema.parse(result);
1355
1783
  } catch (err) {
1356
1784
  if (err instanceof import_zod6.z.ZodError) {
1357
- throw new ZodmonValidationError(handle.definition.name, err);
1785
+ throw new ZodmonValidationError(handle.definition.name, err, result);
1358
1786
  }
1359
1787
  throw err;
1360
1788
  }
@@ -1414,70 +1842,12 @@ var CollectionHandle = class {
1414
1842
  async insertMany(docs) {
1415
1843
  return await insertMany(this, docs);
1416
1844
  }
1417
- /**
1418
- * Find a single document matching the filter.
1419
- *
1420
- * Queries MongoDB, then validates the fetched document against the collection's
1421
- * Zod schema. Validation mode is resolved from the per-query option, falling
1422
- * back to the collection-level default (which defaults to `'strict'`).
1423
- *
1424
- * @param filter - Type-safe filter to match documents.
1425
- * @param options - Optional projection and validation overrides.
1426
- * @returns The matched document, or `null` if no document matches.
1427
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1428
- *
1429
- * @example
1430
- * ```ts
1431
- * const users = db.use(Users)
1432
- * const user = await users.findOne({ name: 'Ada' })
1433
- * if (user) console.log(user.role)
1434
- * ```
1435
- */
1436
1845
  async findOne(filter, options) {
1437
1846
  return await findOne(this, filter, options);
1438
1847
  }
1439
- /**
1440
- * Find a single document matching the filter, or throw if none exists.
1441
- *
1442
- * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
1443
- * instead of returning `null` when no document matches the filter.
1444
- *
1445
- * @param filter - Type-safe filter to match documents.
1446
- * @param options - Optional projection and validation overrides.
1447
- * @returns The matched document (never null).
1448
- * @throws {ZodmonNotFoundError} When no document matches the filter.
1449
- * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
1450
- *
1451
- * @example
1452
- * ```ts
1453
- * const users = db.use(Users)
1454
- * const user = await users.findOneOrThrow({ name: 'Ada' })
1455
- * console.log(user.role) // guaranteed non-null
1456
- * ```
1457
- */
1458
1848
  async findOneOrThrow(filter, options) {
1459
1849
  return await findOneOrThrow(this, filter, options);
1460
1850
  }
1461
- /**
1462
- * Find all documents matching the filter, returning a chainable typed cursor.
1463
- *
1464
- * The cursor is lazy — no query is executed until a terminal method
1465
- * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
1466
- * to shape the query before executing.
1467
- *
1468
- * @param filter - Type-safe filter to match documents.
1469
- * @param options - Optional validation overrides.
1470
- * @returns A typed cursor for chaining query modifiers.
1471
- *
1472
- * @example
1473
- * ```ts
1474
- * const users = db.use(Users)
1475
- * const admins = await users.find({ role: 'admin' })
1476
- * .sort({ name: 1 })
1477
- * .limit(10)
1478
- * .toArray()
1479
- * ```
1480
- */
1481
1851
  find(filter, options) {
1482
1852
  return find(this, filter, options);
1483
1853
  }
@@ -1674,7 +2044,7 @@ var Database = class {
1674
2044
  /** Registered collection definitions, keyed by name. Used by syncIndexes(). */
1675
2045
  _collections = /* @__PURE__ */ new Map();
1676
2046
  constructor(uri, dbName, options) {
1677
- this._client = new import_mongodb2.MongoClient(uri, options);
2047
+ this._client = new import_mongodb3.MongoClient(uri, options);
1678
2048
  this._db = this._client.db(dbName);
1679
2049
  }
1680
2050
  /**
@@ -1763,7 +2133,7 @@ function createClient(uri, dbNameOrOptions, maybeOptions) {
1763
2133
  }
1764
2134
 
1765
2135
  // src/collection/collection.ts
1766
- var import_mongodb4 = require("mongodb");
2136
+ var import_mongodb5 = require("mongodb");
1767
2137
  var import_zod9 = require("zod");
1768
2138
 
1769
2139
  // src/schema/extensions.ts
@@ -1873,14 +2243,14 @@ function installExtensions() {
1873
2243
  installExtensions();
1874
2244
 
1875
2245
  // src/schema/object-id.ts
1876
- var import_mongodb3 = require("mongodb");
2246
+ var import_mongodb4 = require("mongodb");
1877
2247
  var import_zod8 = require("zod");
1878
2248
  var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
1879
2249
  function objectId() {
1880
2250
  return import_zod8.z.custom((val) => {
1881
- if (val instanceof import_mongodb3.ObjectId) return true;
2251
+ if (val instanceof import_mongodb4.ObjectId) return true;
1882
2252
  return typeof val === "string" && OBJECT_ID_HEX.test(val);
1883
- }, "Invalid ObjectId").transform((val) => val instanceof import_mongodb3.ObjectId ? val : import_mongodb3.ObjectId.createFromHexString(val));
2253
+ }, "Invalid ObjectId").transform((val) => val instanceof import_mongodb4.ObjectId ? val : import_mongodb4.ObjectId.createFromHexString(val));
1884
2254
  }
1885
2255
 
1886
2256
  // src/collection/collection.ts
@@ -1895,7 +2265,7 @@ function extractFieldIndexes(shape) {
1895
2265
  return result;
1896
2266
  }
1897
2267
  function collection(name, shape, options) {
1898
- const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new import_mongodb4.ObjectId()), ...shape };
2268
+ const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new import_mongodb5.ObjectId()), ...shape };
1899
2269
  const schema = import_zod9.z.object(resolvedShape);
1900
2270
  const fieldIndexes = extractFieldIndexes(shape);
1901
2271
  const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
@@ -1966,14 +2336,14 @@ function index(fields) {
1966
2336
  }
1967
2337
 
1968
2338
  // src/helpers/oid.ts
1969
- var import_mongodb5 = require("mongodb");
2339
+ var import_mongodb6 = require("mongodb");
1970
2340
  function oid(value) {
1971
- if (value === void 0) return new import_mongodb5.ObjectId();
1972
- if (value instanceof import_mongodb5.ObjectId) return value;
1973
- return import_mongodb5.ObjectId.createFromHexString(value);
2341
+ if (value === void 0) return new import_mongodb6.ObjectId();
2342
+ if (value instanceof import_mongodb6.ObjectId) return value;
2343
+ return import_mongodb6.ObjectId.createFromHexString(value);
1974
2344
  }
1975
2345
  function isOid(value) {
1976
- return value instanceof import_mongodb5.ObjectId;
2346
+ return value instanceof import_mongodb6.ObjectId;
1977
2347
  }
1978
2348
 
1979
2349
  // src/query/operators.ts
@@ -2046,8 +2416,18 @@ var $ = {
2046
2416
  Database,
2047
2417
  IndexBuilder,
2048
2418
  TypedFindCursor,
2419
+ ZodmonAuthError,
2420
+ ZodmonBulkWriteError,
2421
+ ZodmonDocValidationError,
2422
+ ZodmonDuplicateKeyError,
2423
+ ZodmonError,
2424
+ ZodmonIndexError,
2425
+ ZodmonNetworkError,
2049
2426
  ZodmonNotFoundError,
2427
+ ZodmonQueryError,
2428
+ ZodmonTimeoutError,
2050
2429
  ZodmonValidationError,
2430
+ ZodmonWriteConflictError,
2051
2431
  aggregate,
2052
2432
  checkUnindexedFields,
2053
2433
  collection,
@@ -2056,6 +2436,7 @@ var $ = {
2056
2436
  createExpressionBuilder,
2057
2437
  deleteMany,
2058
2438
  deleteOne,
2439
+ deriveProjectedSchema,
2059
2440
  extractComparableOptions,
2060
2441
  extractDbName,
2061
2442
  extractFieldIndexes,
@@ -2070,6 +2451,7 @@ var $ = {
2070
2451
  index,
2071
2452
  insertMany,
2072
2453
  insertOne,
2454
+ isInclusionProjection,
2073
2455
  isOid,
2074
2456
  objectId,
2075
2457
  oid,
@@ -2079,6 +2461,7 @@ var $ = {
2079
2461
  toCompoundIndexSpec,
2080
2462
  toFieldIndexSpec,
2081
2463
  updateMany,
2082
- updateOne
2464
+ updateOne,
2465
+ wrapMongoError
2083
2466
  });
2084
2467
  //# sourceMappingURL=index.cjs.map