@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.js CHANGED
@@ -1,16 +1,344 @@
1
1
  // src/client/client.ts
2
2
  import { MongoClient } from "mongodb";
3
3
 
4
+ // src/crud/find.ts
5
+ import { z as z2 } from "zod";
6
+
7
+ // src/errors/not-found.ts
8
+ var ZodmonNotFoundError = class extends Error {
9
+ name = "ZodmonNotFoundError";
10
+ /** The MongoDB collection name where the query found no results. */
11
+ collection;
12
+ constructor(collection2) {
13
+ super(`Document not found in "${collection2}"`);
14
+ this.collection = collection2;
15
+ }
16
+ };
17
+
18
+ // src/errors/validation.ts
19
+ var ZodmonValidationError = class extends Error {
20
+ name = "ZodmonValidationError";
21
+ /** The MongoDB collection name where the validation failed. */
22
+ collection;
23
+ /** The original Zod validation error with detailed issue information. */
24
+ zodError;
25
+ constructor(collection2, zodError) {
26
+ const fields = zodError.issues.map((issue) => {
27
+ const path = issue.path.join(".") || "(root)";
28
+ return `${path} (${issue.message})`;
29
+ }).join(", ");
30
+ super(`Validation failed for "${collection2}": ${fields}`);
31
+ this.collection = collection2;
32
+ this.zodError = zodError;
33
+ }
34
+ };
35
+
36
+ // src/query/cursor.ts
37
+ import { z } from "zod";
38
+ var TypedFindCursor = class {
39
+ /** @internal */
40
+ cursor;
41
+ /** @internal */
42
+ schema;
43
+ /** @internal */
44
+ collectionName;
45
+ /** @internal */
46
+ mode;
47
+ /** @internal */
48
+ constructor(cursor, definition, mode) {
49
+ this.cursor = cursor;
50
+ this.schema = definition.schema;
51
+ this.collectionName = definition.name;
52
+ this.mode = mode;
53
+ }
54
+ /**
55
+ * Set the sort order for the query.
56
+ *
57
+ * Only top-level document fields are accepted as sort keys.
58
+ * Values must be `1` (ascending) or `-1` (descending).
59
+ *
60
+ * @param spec - Sort specification mapping field names to sort direction.
61
+ * @returns `this` for chaining.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * find(users, {}).sort({ name: 1, age: -1 }).toArray()
66
+ * ```
67
+ */
68
+ sort(spec) {
69
+ this.cursor.sort(spec);
70
+ return this;
71
+ }
72
+ /**
73
+ * Skip the first `n` documents in the result set.
74
+ *
75
+ * @param n - Number of documents to skip.
76
+ * @returns `this` for chaining.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * find(users, {}).skip(10).limit(10).toArray() // page 2
81
+ * ```
82
+ */
83
+ skip(n) {
84
+ this.cursor.skip(n);
85
+ return this;
86
+ }
87
+ /**
88
+ * Limit the number of documents returned.
89
+ *
90
+ * @param n - Maximum number of documents to return.
91
+ * @returns `this` for chaining.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * find(users, {}).limit(10).toArray() // at most 10 docs
96
+ * ```
97
+ */
98
+ limit(n) {
99
+ this.cursor.limit(n);
100
+ return this;
101
+ }
102
+ /**
103
+ * Execute the query and return all matching documents as an array.
104
+ *
105
+ * Each document is validated against the collection's Zod schema
106
+ * according to the resolved validation mode.
107
+ *
108
+ * @returns Array of validated documents.
109
+ * @throws {ZodmonValidationError} When a document fails schema validation in strict/strip mode.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const admins = await find(users, { role: 'admin' }).toArray()
114
+ * ```
115
+ */
116
+ async toArray() {
117
+ const raw2 = await this.cursor.toArray();
118
+ return raw2.map((doc) => this.validateDoc(doc));
119
+ }
120
+ /**
121
+ * Async iterator for streaming documents one at a time.
122
+ *
123
+ * Each yielded document is validated against the collection's Zod schema.
124
+ * Memory-efficient for large result sets.
125
+ *
126
+ * @yields Validated documents one at a time.
127
+ * @throws {ZodmonValidationError} When a document fails schema validation.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * for await (const user of find(users, {})) {
132
+ * console.log(user.name)
133
+ * }
134
+ * ```
135
+ */
136
+ async *[Symbol.asyncIterator]() {
137
+ for await (const doc of this.cursor) {
138
+ yield this.validateDoc(doc);
139
+ }
140
+ }
141
+ /** @internal Validate a single raw document against the schema. */
142
+ validateDoc(raw2) {
143
+ if (this.mode === false || this.mode === "passthrough") {
144
+ return raw2;
145
+ }
146
+ try {
147
+ return this.schema.parse(raw2);
148
+ } catch (err) {
149
+ if (err instanceof z.ZodError) {
150
+ throw new ZodmonValidationError(this.collectionName, err);
151
+ }
152
+ throw err;
153
+ }
154
+ }
155
+ };
156
+
157
+ // src/crud/find.ts
158
+ async function findOne(handle, filter, options) {
159
+ const findOptions = options?.project ? { projection: options.project } : void 0;
160
+ const raw2 = await handle.native.findOne(filter, findOptions);
161
+ if (!raw2) return null;
162
+ const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
163
+ if (mode === false || mode === "passthrough") {
164
+ return raw2;
165
+ }
166
+ try {
167
+ return handle.definition.schema.parse(raw2);
168
+ } catch (err) {
169
+ if (err instanceof z2.ZodError) {
170
+ throw new ZodmonValidationError(handle.definition.name, err);
171
+ }
172
+ throw err;
173
+ }
174
+ }
175
+ async function findOneOrThrow(handle, filter, options) {
176
+ const doc = await findOne(handle, filter, options);
177
+ if (!doc) {
178
+ throw new ZodmonNotFoundError(handle.definition.name);
179
+ }
180
+ return doc;
181
+ }
182
+ function find(handle, filter, options) {
183
+ const raw2 = handle.native.find(filter);
184
+ const cursor = raw2;
185
+ const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
186
+ return new TypedFindCursor(cursor, handle.definition, mode);
187
+ }
188
+
189
+ // src/crud/insert.ts
190
+ import { z as z3 } from "zod";
191
+ async function insertOne(handle, doc) {
192
+ let parsed;
193
+ try {
194
+ parsed = handle.definition.schema.parse(doc);
195
+ } catch (err) {
196
+ if (err instanceof z3.ZodError) {
197
+ throw new ZodmonValidationError(handle.definition.name, err);
198
+ }
199
+ throw err;
200
+ }
201
+ await handle.native.insertOne(parsed);
202
+ return parsed;
203
+ }
204
+ async function insertMany(handle, docs) {
205
+ if (docs.length === 0) return [];
206
+ const parsed = [];
207
+ for (const doc of docs) {
208
+ try {
209
+ parsed.push(handle.definition.schema.parse(doc));
210
+ } catch (err) {
211
+ if (err instanceof z3.ZodError) {
212
+ throw new ZodmonValidationError(handle.definition.name, err);
213
+ }
214
+ throw err;
215
+ }
216
+ }
217
+ await handle.native.insertMany(parsed);
218
+ return parsed;
219
+ }
220
+
4
221
  // src/client/handle.ts
5
222
  var CollectionHandle = class {
6
223
  /** The collection definition containing schema, name, and index metadata. */
7
224
  definition;
8
- /** The underlying MongoDB driver collection, typed to `TDoc`. */
225
+ /** The underlying MongoDB driver collection, typed to the inferred document type. */
9
226
  native;
10
227
  constructor(definition, native) {
11
228
  this.definition = definition;
12
229
  this.native = native;
13
230
  }
231
+ /**
232
+ * Insert a single document into the collection.
233
+ *
234
+ * Validates the input against the collection's Zod schema before writing.
235
+ * Schema defaults (including auto-generated `_id`) are applied during
236
+ * validation. Returns the full document with all defaults filled in.
237
+ *
238
+ * @param doc - The document to insert. Fields with `.default()` are optional.
239
+ * @returns The inserted document with `_id` and all defaults applied.
240
+ * @throws {ZodmonValidationError} When the document fails schema validation.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const users = db.use(Users)
245
+ * const user = await users.insertOne({ name: 'Ada' })
246
+ * console.log(user._id) // ObjectId (auto-generated)
247
+ * console.log(user.role) // 'user' (schema default)
248
+ * ```
249
+ */
250
+ async insertOne(doc) {
251
+ return await insertOne(this, doc);
252
+ }
253
+ /**
254
+ * Insert multiple documents into the collection.
255
+ *
256
+ * Validates every document against the collection's Zod schema before
257
+ * writing any to MongoDB. If any document fails validation, none are
258
+ * inserted (fail-fast before the driver call).
259
+ *
260
+ * @param docs - The documents to insert.
261
+ * @returns The inserted documents with `_id` and all defaults applied.
262
+ * @throws {ZodmonValidationError} When any document fails schema validation.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * const created = await users.insertMany([
267
+ * { name: 'Ada' },
268
+ * { name: 'Bob', role: 'admin' },
269
+ * ])
270
+ * ```
271
+ */
272
+ async insertMany(docs) {
273
+ return await insertMany(this, docs);
274
+ }
275
+ /**
276
+ * Find a single document matching the filter.
277
+ *
278
+ * Queries MongoDB, then validates the fetched document against the collection's
279
+ * Zod schema. Validation mode is resolved from the per-query option, falling
280
+ * back to the collection-level default (which defaults to `'strict'`).
281
+ *
282
+ * @param filter - Type-safe filter to match documents.
283
+ * @param options - Optional projection and validation overrides.
284
+ * @returns The matched document, or `null` if no document matches.
285
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * const users = db.use(Users)
290
+ * const user = await users.findOne({ name: 'Ada' })
291
+ * if (user) console.log(user.role)
292
+ * ```
293
+ */
294
+ async findOne(filter, options) {
295
+ return await findOne(this, filter, options);
296
+ }
297
+ /**
298
+ * Find a single document matching the filter, or throw if none exists.
299
+ *
300
+ * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
301
+ * instead of returning `null` when no document matches the filter.
302
+ *
303
+ * @param filter - Type-safe filter to match documents.
304
+ * @param options - Optional projection and validation overrides.
305
+ * @returns The matched document (never null).
306
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
307
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * const users = db.use(Users)
312
+ * const user = await users.findOneOrThrow({ name: 'Ada' })
313
+ * console.log(user.role) // guaranteed non-null
314
+ * ```
315
+ */
316
+ async findOneOrThrow(filter, options) {
317
+ return await findOneOrThrow(this, filter, options);
318
+ }
319
+ /**
320
+ * Find all documents matching the filter, returning a chainable typed cursor.
321
+ *
322
+ * The cursor is lazy — no query is executed until a terminal method
323
+ * (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
324
+ * to shape the query before executing.
325
+ *
326
+ * @param filter - Type-safe filter to match documents.
327
+ * @param options - Optional validation overrides.
328
+ * @returns A typed cursor for chaining query modifiers.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * const users = db.use(Users)
333
+ * const admins = await users.find({ role: 'admin' })
334
+ * .sort({ name: 1 })
335
+ * .limit(10)
336
+ * .toArray()
337
+ * ```
338
+ */
339
+ find(filter, options) {
340
+ return find(this, filter, options);
341
+ }
14
342
  };
15
343
 
16
344
  // src/client/client.ts
@@ -37,7 +365,10 @@ var Database = class {
37
365
  use(def) {
38
366
  this._collections.set(def.name, def);
39
367
  const native = this._db.collection(def.name);
40
- return new CollectionHandle(def, native);
368
+ return new CollectionHandle(
369
+ def,
370
+ native
371
+ );
41
372
  }
42
373
  /**
43
374
  * Synchronize indexes defined in registered collections with MongoDB.
@@ -85,13 +416,14 @@ function createClient(uri, dbNameOrOptions, maybeOptions) {
85
416
  }
86
417
 
87
418
  // src/collection/collection.ts
88
- import { z as z4 } from "zod";
419
+ import { ObjectId as ObjectId2 } from "mongodb";
420
+ import { z as z7 } from "zod";
89
421
 
90
422
  // src/schema/extensions.ts
91
- import { z as z2 } from "zod";
423
+ import { z as z5 } from "zod";
92
424
 
93
425
  // src/schema/ref.ts
94
- import { z } from "zod";
426
+ import { z as z4 } from "zod";
95
427
  var refMetadata = /* @__PURE__ */ new WeakMap();
96
428
  function getRefMetadata(schema) {
97
429
  if (typeof schema !== "object" || schema === null) return void 0;
@@ -99,7 +431,7 @@ function getRefMetadata(schema) {
99
431
  }
100
432
  var REF_GUARD = /* @__PURE__ */ Symbol.for("zodmon_ref");
101
433
  function installRefExtension() {
102
- const proto = z.ZodType.prototype;
434
+ const proto = z4.ZodType.prototype;
103
435
  if (REF_GUARD in proto) return;
104
436
  Object.defineProperty(proto, "ref", {
105
437
  value(collection2) {
@@ -126,7 +458,7 @@ function getIndexMetadata(schema) {
126
458
  }
127
459
  var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
128
460
  function installExtensions() {
129
- const proto = z2.ZodType.prototype;
461
+ const proto = z5.ZodType.prototype;
130
462
  if (GUARD in proto) return;
131
463
  Object.defineProperty(proto, "index", {
132
464
  /**
@@ -225,10 +557,10 @@ installExtensions();
225
557
 
226
558
  // src/schema/object-id.ts
227
559
  import { ObjectId } from "mongodb";
228
- import { z as z3 } from "zod";
560
+ import { z as z6 } from "zod";
229
561
  var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
230
562
  function objectId() {
231
- return z3.custom((val) => {
563
+ return z6.custom((val) => {
232
564
  if (val instanceof ObjectId) return true;
233
565
  return typeof val === "string" && OBJECT_ID_HEX.test(val);
234
566
  }, "Invalid ObjectId").transform((val) => val instanceof ObjectId ? val : ObjectId.createFromHexString(val));
@@ -246,8 +578,8 @@ function extractFieldIndexes(shape) {
246
578
  return result;
247
579
  }
248
580
  function collection(name, shape, options) {
249
- const resolvedShape = "_id" in shape ? shape : { _id: objectId(), ...shape };
250
- const schema = z4.object(resolvedShape);
581
+ const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new ObjectId2()), ...shape };
582
+ const schema = z7.object(resolvedShape);
251
583
  const fieldIndexes = extractFieldIndexes(shape);
252
584
  const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
253
585
  return {
@@ -298,14 +630,14 @@ function index(fields) {
298
630
  }
299
631
 
300
632
  // src/helpers/oid.ts
301
- import { ObjectId as ObjectId2 } from "mongodb";
633
+ import { ObjectId as ObjectId3 } from "mongodb";
302
634
  function oid(value) {
303
- if (value === void 0) return new ObjectId2();
304
- if (value instanceof ObjectId2) return value;
305
- return ObjectId2.createFromHexString(value);
635
+ if (value === void 0) return new ObjectId3();
636
+ if (value instanceof ObjectId3) return value;
637
+ return ObjectId3.createFromHexString(value);
306
638
  }
307
639
  function isOid(value) {
308
- return value instanceof ObjectId2;
640
+ return value instanceof ObjectId3;
309
641
  }
310
642
 
311
643
  // src/query/operators.ts
@@ -346,13 +678,21 @@ export {
346
678
  CollectionHandle,
347
679
  Database,
348
680
  IndexBuilder,
681
+ TypedFindCursor,
682
+ ZodmonNotFoundError,
683
+ ZodmonValidationError,
349
684
  collection,
350
685
  createClient,
351
686
  extractDbName,
352
687
  extractFieldIndexes,
688
+ find,
689
+ findOne,
690
+ findOneOrThrow,
353
691
  getIndexMetadata,
354
692
  getRefMetadata,
355
693
  index,
694
+ insertMany,
695
+ insertOne,
356
696
  installExtensions,
357
697
  installRefExtension,
358
698
  isOid,