@zodmon/core 0.2.0 → 0.4.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
@@ -20,28 +20,321 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ $and: () => $and,
24
+ $eq: () => $eq,
25
+ $exists: () => $exists,
26
+ $gt: () => $gt,
27
+ $gte: () => $gte,
28
+ $in: () => $in,
29
+ $lt: () => $lt,
30
+ $lte: () => $lte,
31
+ $ne: () => $ne,
32
+ $nin: () => $nin,
33
+ $nor: () => $nor,
34
+ $not: () => $not,
35
+ $or: () => $or,
36
+ $regex: () => $regex,
37
+ CollectionHandle: () => CollectionHandle,
38
+ Database: () => Database,
23
39
  IndexBuilder: () => IndexBuilder,
40
+ ZodmonNotFoundError: () => ZodmonNotFoundError,
41
+ ZodmonValidationError: () => ZodmonValidationError,
24
42
  collection: () => collection,
43
+ createClient: () => createClient,
44
+ extractDbName: () => extractDbName,
25
45
  extractFieldIndexes: () => extractFieldIndexes,
46
+ findOne: () => findOne,
47
+ findOneOrThrow: () => findOneOrThrow,
26
48
  getIndexMetadata: () => getIndexMetadata,
27
49
  getRefMetadata: () => getRefMetadata,
28
50
  index: () => index,
51
+ insertMany: () => insertMany,
52
+ insertOne: () => insertOne,
29
53
  installExtensions: () => installExtensions,
30
54
  installRefExtension: () => installRefExtension,
31
55
  isOid: () => isOid,
32
56
  objectId: () => objectId,
33
- oid: () => oid
57
+ oid: () => oid,
58
+ raw: () => raw
34
59
  });
35
60
  module.exports = __toCommonJS(index_exports);
36
61
 
62
+ // src/client/client.ts
63
+ var import_mongodb = require("mongodb");
64
+
65
+ // src/crud/find.ts
66
+ var import_zod = require("zod");
67
+
68
+ // src/errors/not-found.ts
69
+ var ZodmonNotFoundError = class extends Error {
70
+ name = "ZodmonNotFoundError";
71
+ /** The MongoDB collection name where the query found no results. */
72
+ collection;
73
+ constructor(collection2) {
74
+ super(`Document not found in "${collection2}"`);
75
+ this.collection = collection2;
76
+ }
77
+ };
78
+
79
+ // src/errors/validation.ts
80
+ var ZodmonValidationError = class extends Error {
81
+ name = "ZodmonValidationError";
82
+ /** The MongoDB collection name where the validation failed. */
83
+ collection;
84
+ /** The original Zod validation error with detailed issue information. */
85
+ zodError;
86
+ constructor(collection2, zodError) {
87
+ const fields = zodError.issues.map((issue) => {
88
+ const path = issue.path.join(".") || "(root)";
89
+ return `${path} (${issue.message})`;
90
+ }).join(", ");
91
+ super(`Validation failed for "${collection2}": ${fields}`);
92
+ this.collection = collection2;
93
+ this.zodError = zodError;
94
+ }
95
+ };
96
+
97
+ // src/crud/find.ts
98
+ async function findOne(handle, filter, options) {
99
+ const findOptions = options?.project ? { projection: options.project } : void 0;
100
+ const raw2 = await handle.native.findOne(filter, findOptions);
101
+ if (!raw2) return null;
102
+ const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
103
+ if (mode === false || mode === "passthrough") {
104
+ return raw2;
105
+ }
106
+ try {
107
+ return handle.definition.schema.parse(raw2);
108
+ } catch (err) {
109
+ if (err instanceof import_zod.z.ZodError) {
110
+ throw new ZodmonValidationError(handle.definition.name, err);
111
+ }
112
+ throw err;
113
+ }
114
+ }
115
+ async function findOneOrThrow(handle, filter, options) {
116
+ const doc = await findOne(handle, filter, options);
117
+ if (!doc) {
118
+ throw new ZodmonNotFoundError(handle.definition.name);
119
+ }
120
+ return doc;
121
+ }
122
+
123
+ // src/crud/insert.ts
124
+ var import_zod2 = require("zod");
125
+ async function insertOne(handle, doc) {
126
+ let parsed;
127
+ try {
128
+ parsed = handle.definition.schema.parse(doc);
129
+ } catch (err) {
130
+ if (err instanceof import_zod2.z.ZodError) {
131
+ throw new ZodmonValidationError(handle.definition.name, err);
132
+ }
133
+ throw err;
134
+ }
135
+ await handle.native.insertOne(parsed);
136
+ return parsed;
137
+ }
138
+ async function insertMany(handle, docs) {
139
+ if (docs.length === 0) return [];
140
+ const parsed = [];
141
+ for (const doc of docs) {
142
+ try {
143
+ parsed.push(handle.definition.schema.parse(doc));
144
+ } catch (err) {
145
+ if (err instanceof import_zod2.z.ZodError) {
146
+ throw new ZodmonValidationError(handle.definition.name, err);
147
+ }
148
+ throw err;
149
+ }
150
+ }
151
+ await handle.native.insertMany(parsed);
152
+ return parsed;
153
+ }
154
+
155
+ // src/client/handle.ts
156
+ var CollectionHandle = class {
157
+ /** The collection definition containing schema, name, and index metadata. */
158
+ definition;
159
+ /** The underlying MongoDB driver collection, typed to the inferred document type. */
160
+ native;
161
+ constructor(definition, native) {
162
+ this.definition = definition;
163
+ this.native = native;
164
+ }
165
+ /**
166
+ * Insert a single document into the collection.
167
+ *
168
+ * Validates the input against the collection's Zod schema before writing.
169
+ * Schema defaults (including auto-generated `_id`) are applied during
170
+ * validation. Returns the full document with all defaults filled in.
171
+ *
172
+ * @param doc - The document to insert. Fields with `.default()` are optional.
173
+ * @returns The inserted document with `_id` and all defaults applied.
174
+ * @throws {ZodmonValidationError} When the document fails schema validation.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * const users = db.use(Users)
179
+ * const user = await users.insertOne({ name: 'Ada' })
180
+ * console.log(user._id) // ObjectId (auto-generated)
181
+ * console.log(user.role) // 'user' (schema default)
182
+ * ```
183
+ */
184
+ async insertOne(doc) {
185
+ return await insertOne(this, doc);
186
+ }
187
+ /**
188
+ * Insert multiple documents into the collection.
189
+ *
190
+ * Validates every document against the collection's Zod schema before
191
+ * writing any to MongoDB. If any document fails validation, none are
192
+ * inserted (fail-fast before the driver call).
193
+ *
194
+ * @param docs - The documents to insert.
195
+ * @returns The inserted documents with `_id` and all defaults applied.
196
+ * @throws {ZodmonValidationError} When any document fails schema validation.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const created = await users.insertMany([
201
+ * { name: 'Ada' },
202
+ * { name: 'Bob', role: 'admin' },
203
+ * ])
204
+ * ```
205
+ */
206
+ async insertMany(docs) {
207
+ return await insertMany(this, docs);
208
+ }
209
+ /**
210
+ * Find a single document matching the filter.
211
+ *
212
+ * Queries MongoDB, then validates the fetched document against the collection's
213
+ * Zod schema. Validation mode is resolved from the per-query option, falling
214
+ * back to the collection-level default (which defaults to `'strict'`).
215
+ *
216
+ * @param filter - Type-safe filter to match documents.
217
+ * @param options - Optional projection and validation overrides.
218
+ * @returns The matched document, or `null` if no document matches.
219
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * const users = db.use(Users)
224
+ * const user = await users.findOne({ name: 'Ada' })
225
+ * if (user) console.log(user.role)
226
+ * ```
227
+ */
228
+ async findOne(filter, options) {
229
+ return await findOne(this, filter, options);
230
+ }
231
+ /**
232
+ * Find a single document matching the filter, or throw if none exists.
233
+ *
234
+ * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
235
+ * instead of returning `null` when no document matches the filter.
236
+ *
237
+ * @param filter - Type-safe filter to match documents.
238
+ * @param options - Optional projection and validation overrides.
239
+ * @returns The matched document (never null).
240
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
241
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * const users = db.use(Users)
246
+ * const user = await users.findOneOrThrow({ name: 'Ada' })
247
+ * console.log(user.role) // guaranteed non-null
248
+ * ```
249
+ */
250
+ async findOneOrThrow(filter, options) {
251
+ return await findOneOrThrow(this, filter, options);
252
+ }
253
+ };
254
+
255
+ // src/client/client.ts
256
+ var Database = class {
257
+ _client;
258
+ _db;
259
+ /** Registered collection definitions, keyed by name. Used by syncIndexes(). */
260
+ _collections = /* @__PURE__ */ new Map();
261
+ constructor(uri, dbName, options) {
262
+ this._client = new import_mongodb.MongoClient(uri, options);
263
+ this._db = this._client.db(dbName);
264
+ }
265
+ /**
266
+ * Register a collection definition and return a typed {@link CollectionHandle}.
267
+ *
268
+ * The handle's `native` property is a MongoDB `Collection<TDoc>` where `TDoc`
269
+ * is the document type inferred from the definition's Zod schema. Calling
270
+ * `use()` multiple times with the same definition is safe — each call returns
271
+ * a new lightweight handle backed by the same underlying driver collection.
272
+ *
273
+ * @param def - A collection definition created by `collection()`.
274
+ * @returns A typed collection handle for CRUD operations.
275
+ */
276
+ use(def) {
277
+ this._collections.set(def.name, def);
278
+ const native = this._db.collection(def.name);
279
+ return new CollectionHandle(
280
+ def,
281
+ native
282
+ );
283
+ }
284
+ /**
285
+ * Synchronize indexes defined in registered collections with MongoDB.
286
+ *
287
+ * Stub — full implementation in TASK-92.
288
+ */
289
+ syncIndexes() {
290
+ return Promise.resolve();
291
+ }
292
+ /**
293
+ * Execute a function within a MongoDB transaction with auto-commit/rollback.
294
+ *
295
+ * Stub — full implementation in TASK-106.
296
+ */
297
+ transaction(_fn) {
298
+ throw new Error("Not implemented");
299
+ }
300
+ /**
301
+ * Close the underlying `MongoClient` connection. Safe to call even if
302
+ * no connection was established (the driver handles this gracefully).
303
+ */
304
+ async close() {
305
+ await this._client.close();
306
+ }
307
+ };
308
+ function extractDbName(uri) {
309
+ const withoutProtocol = uri.replace(/^mongodb(?:\+srv)?:\/\//, "");
310
+ const withoutQuery = withoutProtocol.split("?")[0];
311
+ const atIndex = withoutQuery.lastIndexOf("@");
312
+ const hostAndPath = atIndex === -1 ? withoutQuery : withoutQuery.slice(atIndex + 1);
313
+ const slashIndex = hostAndPath.indexOf("/");
314
+ if (slashIndex === -1) return void 0;
315
+ const dbName = decodeURIComponent(hostAndPath.slice(slashIndex + 1));
316
+ return dbName || void 0;
317
+ }
318
+ function createClient(uri, dbNameOrOptions, maybeOptions) {
319
+ if (typeof dbNameOrOptions === "string") {
320
+ return new Database(uri, dbNameOrOptions, maybeOptions);
321
+ }
322
+ const parsed = extractDbName(uri);
323
+ if (!parsed) {
324
+ console.warn('[zodmon] No database name provided \u2014 using MongoDB default "test"');
325
+ }
326
+ return new Database(uri, parsed ?? "test", dbNameOrOptions);
327
+ }
328
+
37
329
  // src/collection/collection.ts
38
- var import_zod4 = require("zod");
330
+ var import_mongodb3 = require("mongodb");
331
+ var import_zod6 = require("zod");
39
332
 
40
333
  // src/schema/extensions.ts
41
- var import_zod2 = require("zod");
334
+ var import_zod4 = require("zod");
42
335
 
43
336
  // src/schema/ref.ts
44
- var import_zod = require("zod");
337
+ var import_zod3 = require("zod");
45
338
  var refMetadata = /* @__PURE__ */ new WeakMap();
46
339
  function getRefMetadata(schema) {
47
340
  if (typeof schema !== "object" || schema === null) return void 0;
@@ -49,7 +342,7 @@ function getRefMetadata(schema) {
49
342
  }
50
343
  var REF_GUARD = /* @__PURE__ */ Symbol.for("zodmon_ref");
51
344
  function installRefExtension() {
52
- const proto = import_zod.z.ZodType.prototype;
345
+ const proto = import_zod3.z.ZodType.prototype;
53
346
  if (REF_GUARD in proto) return;
54
347
  Object.defineProperty(proto, "ref", {
55
348
  value(collection2) {
@@ -76,7 +369,7 @@ function getIndexMetadata(schema) {
76
369
  }
77
370
  var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
78
371
  function installExtensions() {
79
- const proto = import_zod2.z.ZodType.prototype;
372
+ const proto = import_zod4.z.ZodType.prototype;
80
373
  if (GUARD in proto) return;
81
374
  Object.defineProperty(proto, "index", {
82
375
  /**
@@ -174,14 +467,14 @@ function installExtensions() {
174
467
  installExtensions();
175
468
 
176
469
  // src/schema/object-id.ts
177
- var import_mongodb = require("mongodb");
178
- var import_zod3 = require("zod");
470
+ var import_mongodb2 = require("mongodb");
471
+ var import_zod5 = require("zod");
179
472
  var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
180
473
  function objectId() {
181
- return import_zod3.z.custom((val) => {
182
- if (val instanceof import_mongodb.ObjectId) return true;
474
+ return import_zod5.z.custom((val) => {
475
+ if (val instanceof import_mongodb2.ObjectId) return true;
183
476
  return typeof val === "string" && OBJECT_ID_HEX.test(val);
184
- }, "Invalid ObjectId").transform((val) => val instanceof import_mongodb.ObjectId ? val : import_mongodb.ObjectId.createFromHexString(val));
477
+ }, "Invalid ObjectId").transform((val) => val instanceof import_mongodb2.ObjectId ? val : import_mongodb2.ObjectId.createFromHexString(val));
185
478
  }
186
479
 
187
480
  // src/collection/collection.ts
@@ -196,8 +489,8 @@ function extractFieldIndexes(shape) {
196
489
  return result;
197
490
  }
198
491
  function collection(name, shape, options) {
199
- const resolvedShape = "_id" in shape ? shape : { _id: objectId(), ...shape };
200
- const schema = import_zod4.z.object(resolvedShape);
492
+ const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new import_mongodb3.ObjectId()), ...shape };
493
+ const schema = import_zod6.z.object(resolvedShape);
201
494
  const fieldIndexes = extractFieldIndexes(shape);
202
495
  const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
203
496
  return {
@@ -248,27 +541,73 @@ function index(fields) {
248
541
  }
249
542
 
250
543
  // src/helpers/oid.ts
251
- var import_mongodb2 = require("mongodb");
544
+ var import_mongodb4 = require("mongodb");
252
545
  function oid(value) {
253
- if (value === void 0) return new import_mongodb2.ObjectId();
254
- if (value instanceof import_mongodb2.ObjectId) return value;
255
- return import_mongodb2.ObjectId.createFromHexString(value);
546
+ if (value === void 0) return new import_mongodb4.ObjectId();
547
+ if (value instanceof import_mongodb4.ObjectId) return value;
548
+ return import_mongodb4.ObjectId.createFromHexString(value);
256
549
  }
257
550
  function isOid(value) {
258
- return value instanceof import_mongodb2.ObjectId;
551
+ return value instanceof import_mongodb4.ObjectId;
259
552
  }
553
+
554
+ // src/query/operators.ts
555
+ var $eq = (value) => ({ $eq: value });
556
+ var $ne = (value) => ({ $ne: value });
557
+ var $gt = (value) => ({ $gt: value });
558
+ var $gte = (value) => ({ $gte: value });
559
+ var $lt = (value) => ({ $lt: value });
560
+ var $lte = (value) => ({ $lte: value });
561
+ var $in = (values) => ({ $in: values });
562
+ var $nin = (values) => ({ $nin: values });
563
+ var $exists = (flag = true) => ({ $exists: flag });
564
+ var $regex = (pattern) => ({
565
+ $regex: pattern
566
+ });
567
+ var $not = (op) => ({
568
+ $not: op
569
+ });
570
+ var $or = (...filters) => ({ $or: filters });
571
+ var $and = (...filters) => ({ $and: filters });
572
+ var $nor = (...filters) => ({ $nor: filters });
573
+ var raw = (filter) => filter;
260
574
  // Annotate the CommonJS export names for ESM import in node:
261
575
  0 && (module.exports = {
576
+ $and,
577
+ $eq,
578
+ $exists,
579
+ $gt,
580
+ $gte,
581
+ $in,
582
+ $lt,
583
+ $lte,
584
+ $ne,
585
+ $nin,
586
+ $nor,
587
+ $not,
588
+ $or,
589
+ $regex,
590
+ CollectionHandle,
591
+ Database,
262
592
  IndexBuilder,
593
+ ZodmonNotFoundError,
594
+ ZodmonValidationError,
263
595
  collection,
596
+ createClient,
597
+ extractDbName,
264
598
  extractFieldIndexes,
599
+ findOne,
600
+ findOneOrThrow,
265
601
  getIndexMetadata,
266
602
  getRefMetadata,
267
603
  index,
604
+ insertMany,
605
+ insertOne,
268
606
  installExtensions,
269
607
  installRefExtension,
270
608
  isOid,
271
609
  objectId,
272
- oid
610
+ oid,
611
+ raw
273
612
  });
274
613
  //# sourceMappingURL=index.cjs.map