@zodmon/core 0.3.0 → 0.5.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
@@ -37,13 +37,21 @@ __export(index_exports, {
37
37
  CollectionHandle: () => CollectionHandle,
38
38
  Database: () => Database,
39
39
  IndexBuilder: () => IndexBuilder,
40
+ TypedFindCursor: () => TypedFindCursor,
41
+ ZodmonNotFoundError: () => ZodmonNotFoundError,
42
+ ZodmonValidationError: () => ZodmonValidationError,
40
43
  collection: () => collection,
41
44
  createClient: () => createClient,
42
45
  extractDbName: () => extractDbName,
43
46
  extractFieldIndexes: () => extractFieldIndexes,
47
+ find: () => find,
48
+ findOne: () => findOne,
49
+ findOneOrThrow: () => findOneOrThrow,
44
50
  getIndexMetadata: () => getIndexMetadata,
45
51
  getRefMetadata: () => getRefMetadata,
46
52
  index: () => index,
53
+ insertMany: () => insertMany,
54
+ insertOne: () => insertOne,
47
55
  installExtensions: () => installExtensions,
48
56
  installRefExtension: () => installRefExtension,
49
57
  isOid: () => isOid,
@@ -56,16 +64,344 @@ module.exports = __toCommonJS(index_exports);
56
64
  // src/client/client.ts
57
65
  var import_mongodb = require("mongodb");
58
66
 
67
+ // src/crud/find.ts
68
+ var import_zod2 = require("zod");
69
+
70
+ // src/errors/not-found.ts
71
+ var ZodmonNotFoundError = class extends Error {
72
+ name = "ZodmonNotFoundError";
73
+ /** The MongoDB collection name where the query found no results. */
74
+ collection;
75
+ constructor(collection2) {
76
+ super(`Document not found in "${collection2}"`);
77
+ this.collection = collection2;
78
+ }
79
+ };
80
+
81
+ // src/errors/validation.ts
82
+ var ZodmonValidationError = class extends Error {
83
+ name = "ZodmonValidationError";
84
+ /** The MongoDB collection name where the validation failed. */
85
+ collection;
86
+ /** The original Zod validation error with detailed issue information. */
87
+ zodError;
88
+ constructor(collection2, zodError) {
89
+ const fields = zodError.issues.map((issue) => {
90
+ const path = issue.path.join(".") || "(root)";
91
+ return `${path} (${issue.message})`;
92
+ }).join(", ");
93
+ super(`Validation failed for "${collection2}": ${fields}`);
94
+ this.collection = collection2;
95
+ this.zodError = zodError;
96
+ }
97
+ };
98
+
99
+ // src/query/cursor.ts
100
+ var import_zod = require("zod");
101
+ var TypedFindCursor = class {
102
+ /** @internal */
103
+ cursor;
104
+ /** @internal */
105
+ schema;
106
+ /** @internal */
107
+ collectionName;
108
+ /** @internal */
109
+ mode;
110
+ /** @internal */
111
+ constructor(cursor, definition, mode) {
112
+ this.cursor = cursor;
113
+ this.schema = definition.schema;
114
+ this.collectionName = definition.name;
115
+ this.mode = mode;
116
+ }
117
+ /**
118
+ * Set the sort order for the query.
119
+ *
120
+ * Only top-level document fields are accepted as sort keys.
121
+ * Values must be `1` (ascending) or `-1` (descending).
122
+ *
123
+ * @param spec - Sort specification mapping field names to sort direction.
124
+ * @returns `this` for chaining.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * find(users, {}).sort({ name: 1, age: -1 }).toArray()
129
+ * ```
130
+ */
131
+ sort(spec) {
132
+ this.cursor.sort(spec);
133
+ return this;
134
+ }
135
+ /**
136
+ * Skip the first `n` documents in the result set.
137
+ *
138
+ * @param n - Number of documents to skip.
139
+ * @returns `this` for chaining.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * find(users, {}).skip(10).limit(10).toArray() // page 2
144
+ * ```
145
+ */
146
+ skip(n) {
147
+ this.cursor.skip(n);
148
+ return this;
149
+ }
150
+ /**
151
+ * Limit the number of documents returned.
152
+ *
153
+ * @param n - Maximum number of documents to return.
154
+ * @returns `this` for chaining.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * find(users, {}).limit(10).toArray() // at most 10 docs
159
+ * ```
160
+ */
161
+ limit(n) {
162
+ this.cursor.limit(n);
163
+ return this;
164
+ }
165
+ /**
166
+ * Execute the query and return all matching documents as an array.
167
+ *
168
+ * Each document is validated against the collection's Zod schema
169
+ * according to the resolved validation mode.
170
+ *
171
+ * @returns Array of validated documents.
172
+ * @throws {ZodmonValidationError} When a document fails schema validation in strict/strip mode.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * const admins = await find(users, { role: 'admin' }).toArray()
177
+ * ```
178
+ */
179
+ async toArray() {
180
+ const raw2 = await this.cursor.toArray();
181
+ return raw2.map((doc) => this.validateDoc(doc));
182
+ }
183
+ /**
184
+ * Async iterator for streaming documents one at a time.
185
+ *
186
+ * Each yielded document is validated against the collection's Zod schema.
187
+ * Memory-efficient for large result sets.
188
+ *
189
+ * @yields Validated documents one at a time.
190
+ * @throws {ZodmonValidationError} When a document fails schema validation.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * for await (const user of find(users, {})) {
195
+ * console.log(user.name)
196
+ * }
197
+ * ```
198
+ */
199
+ async *[Symbol.asyncIterator]() {
200
+ for await (const doc of this.cursor) {
201
+ yield this.validateDoc(doc);
202
+ }
203
+ }
204
+ /** @internal Validate a single raw document against the schema. */
205
+ validateDoc(raw2) {
206
+ if (this.mode === false || this.mode === "passthrough") {
207
+ return raw2;
208
+ }
209
+ try {
210
+ return this.schema.parse(raw2);
211
+ } catch (err) {
212
+ if (err instanceof import_zod.z.ZodError) {
213
+ throw new ZodmonValidationError(this.collectionName, err);
214
+ }
215
+ throw err;
216
+ }
217
+ }
218
+ };
219
+
220
+ // src/crud/find.ts
221
+ async function findOne(handle, filter, options) {
222
+ const findOptions = options?.project ? { projection: options.project } : void 0;
223
+ const raw2 = await handle.native.findOne(filter, findOptions);
224
+ if (!raw2) return null;
225
+ const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
226
+ if (mode === false || mode === "passthrough") {
227
+ return raw2;
228
+ }
229
+ try {
230
+ return handle.definition.schema.parse(raw2);
231
+ } catch (err) {
232
+ if (err instanceof import_zod2.z.ZodError) {
233
+ throw new ZodmonValidationError(handle.definition.name, err);
234
+ }
235
+ throw err;
236
+ }
237
+ }
238
+ async function findOneOrThrow(handle, filter, options) {
239
+ const doc = await findOne(handle, filter, options);
240
+ if (!doc) {
241
+ throw new ZodmonNotFoundError(handle.definition.name);
242
+ }
243
+ return doc;
244
+ }
245
+ function find(handle, filter, options) {
246
+ const raw2 = handle.native.find(filter);
247
+ const cursor = raw2;
248
+ const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
249
+ return new TypedFindCursor(cursor, handle.definition, mode);
250
+ }
251
+
252
+ // src/crud/insert.ts
253
+ var import_zod3 = require("zod");
254
+ async function insertOne(handle, doc) {
255
+ let parsed;
256
+ try {
257
+ parsed = handle.definition.schema.parse(doc);
258
+ } catch (err) {
259
+ if (err instanceof import_zod3.z.ZodError) {
260
+ throw new ZodmonValidationError(handle.definition.name, err);
261
+ }
262
+ throw err;
263
+ }
264
+ await handle.native.insertOne(parsed);
265
+ return parsed;
266
+ }
267
+ async function insertMany(handle, docs) {
268
+ if (docs.length === 0) return [];
269
+ const parsed = [];
270
+ for (const doc of docs) {
271
+ try {
272
+ parsed.push(handle.definition.schema.parse(doc));
273
+ } catch (err) {
274
+ if (err instanceof import_zod3.z.ZodError) {
275
+ throw new ZodmonValidationError(handle.definition.name, err);
276
+ }
277
+ throw err;
278
+ }
279
+ }
280
+ await handle.native.insertMany(parsed);
281
+ return parsed;
282
+ }
283
+
59
284
  // src/client/handle.ts
60
285
  var CollectionHandle = class {
61
286
  /** The collection definition containing schema, name, and index metadata. */
62
287
  definition;
63
- /** The underlying MongoDB driver collection, typed to `TDoc`. */
288
+ /** The underlying MongoDB driver collection, typed to the inferred document type. */
64
289
  native;
65
290
  constructor(definition, native) {
66
291
  this.definition = definition;
67
292
  this.native = native;
68
293
  }
294
+ /**
295
+ * Insert a single document into the collection.
296
+ *
297
+ * Validates the input against the collection's Zod schema before writing.
298
+ * Schema defaults (including auto-generated `_id`) are applied during
299
+ * validation. Returns the full document with all defaults filled in.
300
+ *
301
+ * @param doc - The document to insert. Fields with `.default()` are optional.
302
+ * @returns The inserted document with `_id` and all defaults applied.
303
+ * @throws {ZodmonValidationError} When the document fails schema validation.
304
+ *
305
+ * @example
306
+ * ```ts
307
+ * const users = db.use(Users)
308
+ * const user = await users.insertOne({ name: 'Ada' })
309
+ * console.log(user._id) // ObjectId (auto-generated)
310
+ * console.log(user.role) // 'user' (schema default)
311
+ * ```
312
+ */
313
+ async insertOne(doc) {
314
+ return await insertOne(this, doc);
315
+ }
316
+ /**
317
+ * Insert multiple documents into the collection.
318
+ *
319
+ * Validates every document against the collection's Zod schema before
320
+ * writing any to MongoDB. If any document fails validation, none are
321
+ * inserted (fail-fast before the driver call).
322
+ *
323
+ * @param docs - The documents to insert.
324
+ * @returns The inserted documents with `_id` and all defaults applied.
325
+ * @throws {ZodmonValidationError} When any document fails schema validation.
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * const created = await users.insertMany([
330
+ * { name: 'Ada' },
331
+ * { name: 'Bob', role: 'admin' },
332
+ * ])
333
+ * ```
334
+ */
335
+ async insertMany(docs) {
336
+ return await insertMany(this, docs);
337
+ }
338
+ /**
339
+ * Find a single document matching the filter.
340
+ *
341
+ * Queries MongoDB, then validates the fetched document against the collection's
342
+ * Zod schema. Validation mode is resolved from the per-query option, falling
343
+ * back to the collection-level default (which defaults to `'strict'`).
344
+ *
345
+ * @param filter - Type-safe filter to match documents.
346
+ * @param options - Optional projection and validation overrides.
347
+ * @returns The matched document, or `null` if no document matches.
348
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * const users = db.use(Users)
353
+ * const user = await users.findOne({ name: 'Ada' })
354
+ * if (user) console.log(user.role)
355
+ * ```
356
+ */
357
+ async findOne(filter, options) {
358
+ return await findOne(this, filter, options);
359
+ }
360
+ /**
361
+ * Find a single document matching the filter, or throw if none exists.
362
+ *
363
+ * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
364
+ * instead of returning `null` when no document matches the filter.
365
+ *
366
+ * @param filter - Type-safe filter to match documents.
367
+ * @param options - Optional projection and validation overrides.
368
+ * @returns The matched document (never null).
369
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
370
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * const users = db.use(Users)
375
+ * const user = await users.findOneOrThrow({ name: 'Ada' })
376
+ * console.log(user.role) // guaranteed non-null
377
+ * ```
378
+ */
379
+ async findOneOrThrow(filter, options) {
380
+ return await findOneOrThrow(this, filter, options);
381
+ }
382
+ /**
383
+ * Find all documents matching the filter, returning a chainable typed cursor.
384
+ *
385
+ * The cursor is lazy — no query is executed until a terminal method
386
+ * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
387
+ * to shape the query before executing.
388
+ *
389
+ * @param filter - Type-safe filter to match documents.
390
+ * @param options - Optional validation overrides.
391
+ * @returns A typed cursor for chaining query modifiers.
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * const users = db.use(Users)
396
+ * const admins = await users.find({ role: 'admin' })
397
+ * .sort({ name: 1 })
398
+ * .limit(10)
399
+ * .toArray()
400
+ * ```
401
+ */
402
+ find(filter, options) {
403
+ return find(this, filter, options);
404
+ }
69
405
  };
70
406
 
71
407
  // src/client/client.ts
@@ -92,7 +428,10 @@ var Database = class {
92
428
  use(def) {
93
429
  this._collections.set(def.name, def);
94
430
  const native = this._db.collection(def.name);
95
- return new CollectionHandle(def, native);
431
+ return new CollectionHandle(
432
+ def,
433
+ native
434
+ );
96
435
  }
97
436
  /**
98
437
  * Synchronize indexes defined in registered collections with MongoDB.
@@ -140,13 +479,14 @@ function createClient(uri, dbNameOrOptions, maybeOptions) {
140
479
  }
141
480
 
142
481
  // src/collection/collection.ts
143
- var import_zod4 = require("zod");
482
+ var import_mongodb3 = require("mongodb");
483
+ var import_zod7 = require("zod");
144
484
 
145
485
  // src/schema/extensions.ts
146
- var import_zod2 = require("zod");
486
+ var import_zod5 = require("zod");
147
487
 
148
488
  // src/schema/ref.ts
149
- var import_zod = require("zod");
489
+ var import_zod4 = require("zod");
150
490
  var refMetadata = /* @__PURE__ */ new WeakMap();
151
491
  function getRefMetadata(schema) {
152
492
  if (typeof schema !== "object" || schema === null) return void 0;
@@ -154,7 +494,7 @@ function getRefMetadata(schema) {
154
494
  }
155
495
  var REF_GUARD = /* @__PURE__ */ Symbol.for("zodmon_ref");
156
496
  function installRefExtension() {
157
- const proto = import_zod.z.ZodType.prototype;
497
+ const proto = import_zod4.z.ZodType.prototype;
158
498
  if (REF_GUARD in proto) return;
159
499
  Object.defineProperty(proto, "ref", {
160
500
  value(collection2) {
@@ -181,7 +521,7 @@ function getIndexMetadata(schema) {
181
521
  }
182
522
  var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
183
523
  function installExtensions() {
184
- const proto = import_zod2.z.ZodType.prototype;
524
+ const proto = import_zod5.z.ZodType.prototype;
185
525
  if (GUARD in proto) return;
186
526
  Object.defineProperty(proto, "index", {
187
527
  /**
@@ -280,10 +620,10 @@ installExtensions();
280
620
 
281
621
  // src/schema/object-id.ts
282
622
  var import_mongodb2 = require("mongodb");
283
- var import_zod3 = require("zod");
623
+ var import_zod6 = require("zod");
284
624
  var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
285
625
  function objectId() {
286
- return import_zod3.z.custom((val) => {
626
+ return import_zod6.z.custom((val) => {
287
627
  if (val instanceof import_mongodb2.ObjectId) return true;
288
628
  return typeof val === "string" && OBJECT_ID_HEX.test(val);
289
629
  }, "Invalid ObjectId").transform((val) => val instanceof import_mongodb2.ObjectId ? val : import_mongodb2.ObjectId.createFromHexString(val));
@@ -301,8 +641,8 @@ function extractFieldIndexes(shape) {
301
641
  return result;
302
642
  }
303
643
  function collection(name, shape, options) {
304
- const resolvedShape = "_id" in shape ? shape : { _id: objectId(), ...shape };
305
- const schema = import_zod4.z.object(resolvedShape);
644
+ const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new import_mongodb3.ObjectId()), ...shape };
645
+ const schema = import_zod7.z.object(resolvedShape);
306
646
  const fieldIndexes = extractFieldIndexes(shape);
307
647
  const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
308
648
  return {
@@ -353,14 +693,14 @@ function index(fields) {
353
693
  }
354
694
 
355
695
  // src/helpers/oid.ts
356
- var import_mongodb3 = require("mongodb");
696
+ var import_mongodb4 = require("mongodb");
357
697
  function oid(value) {
358
- if (value === void 0) return new import_mongodb3.ObjectId();
359
- if (value instanceof import_mongodb3.ObjectId) return value;
360
- return import_mongodb3.ObjectId.createFromHexString(value);
698
+ if (value === void 0) return new import_mongodb4.ObjectId();
699
+ if (value instanceof import_mongodb4.ObjectId) return value;
700
+ return import_mongodb4.ObjectId.createFromHexString(value);
361
701
  }
362
702
  function isOid(value) {
363
- return value instanceof import_mongodb3.ObjectId;
703
+ return value instanceof import_mongodb4.ObjectId;
364
704
  }
365
705
 
366
706
  // src/query/operators.ts
@@ -402,13 +742,21 @@ var raw = (filter) => filter;
402
742
  CollectionHandle,
403
743
  Database,
404
744
  IndexBuilder,
745
+ TypedFindCursor,
746
+ ZodmonNotFoundError,
747
+ ZodmonValidationError,
405
748
  collection,
406
749
  createClient,
407
750
  extractDbName,
408
751
  extractFieldIndexes,
752
+ find,
753
+ findOne,
754
+ findOneOrThrow,
409
755
  getIndexMetadata,
410
756
  getRefMetadata,
411
757
  index,
758
+ insertMany,
759
+ insertOne,
412
760
  installExtensions,
413
761
  installRefExtension,
414
762
  isOid,