@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.js CHANGED
@@ -1,11 +1,279 @@
1
+ // src/client/client.ts
2
+ import { MongoClient } from "mongodb";
3
+
4
+ // src/crud/find.ts
5
+ import { z } 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/crud/find.ts
37
+ async function findOne(handle, filter, options) {
38
+ const findOptions = options?.project ? { projection: options.project } : void 0;
39
+ const raw2 = await handle.native.findOne(filter, findOptions);
40
+ if (!raw2) return null;
41
+ const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
42
+ if (mode === false || mode === "passthrough") {
43
+ return raw2;
44
+ }
45
+ try {
46
+ return handle.definition.schema.parse(raw2);
47
+ } catch (err) {
48
+ if (err instanceof z.ZodError) {
49
+ throw new ZodmonValidationError(handle.definition.name, err);
50
+ }
51
+ throw err;
52
+ }
53
+ }
54
+ async function findOneOrThrow(handle, filter, options) {
55
+ const doc = await findOne(handle, filter, options);
56
+ if (!doc) {
57
+ throw new ZodmonNotFoundError(handle.definition.name);
58
+ }
59
+ return doc;
60
+ }
61
+
62
+ // src/crud/insert.ts
63
+ import { z as z2 } from "zod";
64
+ async function insertOne(handle, doc) {
65
+ let parsed;
66
+ try {
67
+ parsed = handle.definition.schema.parse(doc);
68
+ } catch (err) {
69
+ if (err instanceof z2.ZodError) {
70
+ throw new ZodmonValidationError(handle.definition.name, err);
71
+ }
72
+ throw err;
73
+ }
74
+ await handle.native.insertOne(parsed);
75
+ return parsed;
76
+ }
77
+ async function insertMany(handle, docs) {
78
+ if (docs.length === 0) return [];
79
+ const parsed = [];
80
+ for (const doc of docs) {
81
+ try {
82
+ parsed.push(handle.definition.schema.parse(doc));
83
+ } catch (err) {
84
+ if (err instanceof z2.ZodError) {
85
+ throw new ZodmonValidationError(handle.definition.name, err);
86
+ }
87
+ throw err;
88
+ }
89
+ }
90
+ await handle.native.insertMany(parsed);
91
+ return parsed;
92
+ }
93
+
94
+ // src/client/handle.ts
95
+ var CollectionHandle = class {
96
+ /** The collection definition containing schema, name, and index metadata. */
97
+ definition;
98
+ /** The underlying MongoDB driver collection, typed to the inferred document type. */
99
+ native;
100
+ constructor(definition, native) {
101
+ this.definition = definition;
102
+ this.native = native;
103
+ }
104
+ /**
105
+ * Insert a single document into the collection.
106
+ *
107
+ * Validates the input against the collection's Zod schema before writing.
108
+ * Schema defaults (including auto-generated `_id`) are applied during
109
+ * validation. Returns the full document with all defaults filled in.
110
+ *
111
+ * @param doc - The document to insert. Fields with `.default()` are optional.
112
+ * @returns The inserted document with `_id` and all defaults applied.
113
+ * @throws {ZodmonValidationError} When the document fails schema validation.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const users = db.use(Users)
118
+ * const user = await users.insertOne({ name: 'Ada' })
119
+ * console.log(user._id) // ObjectId (auto-generated)
120
+ * console.log(user.role) // 'user' (schema default)
121
+ * ```
122
+ */
123
+ async insertOne(doc) {
124
+ return await insertOne(this, doc);
125
+ }
126
+ /**
127
+ * Insert multiple documents into the collection.
128
+ *
129
+ * Validates every document against the collection's Zod schema before
130
+ * writing any to MongoDB. If any document fails validation, none are
131
+ * inserted (fail-fast before the driver call).
132
+ *
133
+ * @param docs - The documents to insert.
134
+ * @returns The inserted documents with `_id` and all defaults applied.
135
+ * @throws {ZodmonValidationError} When any document fails schema validation.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const created = await users.insertMany([
140
+ * { name: 'Ada' },
141
+ * { name: 'Bob', role: 'admin' },
142
+ * ])
143
+ * ```
144
+ */
145
+ async insertMany(docs) {
146
+ return await insertMany(this, docs);
147
+ }
148
+ /**
149
+ * Find a single document matching the filter.
150
+ *
151
+ * Queries MongoDB, then validates the fetched document against the collection's
152
+ * Zod schema. Validation mode is resolved from the per-query option, falling
153
+ * back to the collection-level default (which defaults to `'strict'`).
154
+ *
155
+ * @param filter - Type-safe filter to match documents.
156
+ * @param options - Optional projection and validation overrides.
157
+ * @returns The matched document, or `null` if no document matches.
158
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const users = db.use(Users)
163
+ * const user = await users.findOne({ name: 'Ada' })
164
+ * if (user) console.log(user.role)
165
+ * ```
166
+ */
167
+ async findOne(filter, options) {
168
+ return await findOne(this, filter, options);
169
+ }
170
+ /**
171
+ * Find a single document matching the filter, or throw if none exists.
172
+ *
173
+ * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
174
+ * instead of returning `null` when no document matches the filter.
175
+ *
176
+ * @param filter - Type-safe filter to match documents.
177
+ * @param options - Optional projection and validation overrides.
178
+ * @returns The matched document (never null).
179
+ * @throws {ZodmonNotFoundError} When no document matches the filter.
180
+ * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * const users = db.use(Users)
185
+ * const user = await users.findOneOrThrow({ name: 'Ada' })
186
+ * console.log(user.role) // guaranteed non-null
187
+ * ```
188
+ */
189
+ async findOneOrThrow(filter, options) {
190
+ return await findOneOrThrow(this, filter, options);
191
+ }
192
+ };
193
+
194
+ // src/client/client.ts
195
+ var Database = class {
196
+ _client;
197
+ _db;
198
+ /** Registered collection definitions, keyed by name. Used by syncIndexes(). */
199
+ _collections = /* @__PURE__ */ new Map();
200
+ constructor(uri, dbName, options) {
201
+ this._client = new MongoClient(uri, options);
202
+ this._db = this._client.db(dbName);
203
+ }
204
+ /**
205
+ * Register a collection definition and return a typed {@link CollectionHandle}.
206
+ *
207
+ * The handle's `native` property is a MongoDB `Collection<TDoc>` where `TDoc`
208
+ * is the document type inferred from the definition's Zod schema. Calling
209
+ * `use()` multiple times with the same definition is safe — each call returns
210
+ * a new lightweight handle backed by the same underlying driver collection.
211
+ *
212
+ * @param def - A collection definition created by `collection()`.
213
+ * @returns A typed collection handle for CRUD operations.
214
+ */
215
+ use(def) {
216
+ this._collections.set(def.name, def);
217
+ const native = this._db.collection(def.name);
218
+ return new CollectionHandle(
219
+ def,
220
+ native
221
+ );
222
+ }
223
+ /**
224
+ * Synchronize indexes defined in registered collections with MongoDB.
225
+ *
226
+ * Stub — full implementation in TASK-92.
227
+ */
228
+ syncIndexes() {
229
+ return Promise.resolve();
230
+ }
231
+ /**
232
+ * Execute a function within a MongoDB transaction with auto-commit/rollback.
233
+ *
234
+ * Stub — full implementation in TASK-106.
235
+ */
236
+ transaction(_fn) {
237
+ throw new Error("Not implemented");
238
+ }
239
+ /**
240
+ * Close the underlying `MongoClient` connection. Safe to call even if
241
+ * no connection was established (the driver handles this gracefully).
242
+ */
243
+ async close() {
244
+ await this._client.close();
245
+ }
246
+ };
247
+ function extractDbName(uri) {
248
+ const withoutProtocol = uri.replace(/^mongodb(?:\+srv)?:\/\//, "");
249
+ const withoutQuery = withoutProtocol.split("?")[0];
250
+ const atIndex = withoutQuery.lastIndexOf("@");
251
+ const hostAndPath = atIndex === -1 ? withoutQuery : withoutQuery.slice(atIndex + 1);
252
+ const slashIndex = hostAndPath.indexOf("/");
253
+ if (slashIndex === -1) return void 0;
254
+ const dbName = decodeURIComponent(hostAndPath.slice(slashIndex + 1));
255
+ return dbName || void 0;
256
+ }
257
+ function createClient(uri, dbNameOrOptions, maybeOptions) {
258
+ if (typeof dbNameOrOptions === "string") {
259
+ return new Database(uri, dbNameOrOptions, maybeOptions);
260
+ }
261
+ const parsed = extractDbName(uri);
262
+ if (!parsed) {
263
+ console.warn('[zodmon] No database name provided \u2014 using MongoDB default "test"');
264
+ }
265
+ return new Database(uri, parsed ?? "test", dbNameOrOptions);
266
+ }
267
+
1
268
  // src/collection/collection.ts
2
- import { z as z4 } from "zod";
269
+ import { ObjectId as ObjectId2 } from "mongodb";
270
+ import { z as z6 } from "zod";
3
271
 
4
272
  // src/schema/extensions.ts
5
- import { z as z2 } from "zod";
273
+ import { z as z4 } from "zod";
6
274
 
7
275
  // src/schema/ref.ts
8
- import { z } from "zod";
276
+ import { z as z3 } from "zod";
9
277
  var refMetadata = /* @__PURE__ */ new WeakMap();
10
278
  function getRefMetadata(schema) {
11
279
  if (typeof schema !== "object" || schema === null) return void 0;
@@ -13,7 +281,7 @@ function getRefMetadata(schema) {
13
281
  }
14
282
  var REF_GUARD = /* @__PURE__ */ Symbol.for("zodmon_ref");
15
283
  function installRefExtension() {
16
- const proto = z.ZodType.prototype;
284
+ const proto = z3.ZodType.prototype;
17
285
  if (REF_GUARD in proto) return;
18
286
  Object.defineProperty(proto, "ref", {
19
287
  value(collection2) {
@@ -40,7 +308,7 @@ function getIndexMetadata(schema) {
40
308
  }
41
309
  var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
42
310
  function installExtensions() {
43
- const proto = z2.ZodType.prototype;
311
+ const proto = z4.ZodType.prototype;
44
312
  if (GUARD in proto) return;
45
313
  Object.defineProperty(proto, "index", {
46
314
  /**
@@ -139,10 +407,10 @@ installExtensions();
139
407
 
140
408
  // src/schema/object-id.ts
141
409
  import { ObjectId } from "mongodb";
142
- import { z as z3 } from "zod";
410
+ import { z as z5 } from "zod";
143
411
  var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
144
412
  function objectId() {
145
- return z3.custom((val) => {
413
+ return z5.custom((val) => {
146
414
  if (val instanceof ObjectId) return true;
147
415
  return typeof val === "string" && OBJECT_ID_HEX.test(val);
148
416
  }, "Invalid ObjectId").transform((val) => val instanceof ObjectId ? val : ObjectId.createFromHexString(val));
@@ -160,8 +428,8 @@ function extractFieldIndexes(shape) {
160
428
  return result;
161
429
  }
162
430
  function collection(name, shape, options) {
163
- const resolvedShape = "_id" in shape ? shape : { _id: objectId(), ...shape };
164
- const schema = z4.object(resolvedShape);
431
+ const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new ObjectId2()), ...shape };
432
+ const schema = z6.object(resolvedShape);
165
433
  const fieldIndexes = extractFieldIndexes(shape);
166
434
  const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
167
435
  return {
@@ -212,26 +480,72 @@ function index(fields) {
212
480
  }
213
481
 
214
482
  // src/helpers/oid.ts
215
- import { ObjectId as ObjectId2 } from "mongodb";
483
+ import { ObjectId as ObjectId3 } from "mongodb";
216
484
  function oid(value) {
217
- if (value === void 0) return new ObjectId2();
218
- if (value instanceof ObjectId2) return value;
219
- return ObjectId2.createFromHexString(value);
485
+ if (value === void 0) return new ObjectId3();
486
+ if (value instanceof ObjectId3) return value;
487
+ return ObjectId3.createFromHexString(value);
220
488
  }
221
489
  function isOid(value) {
222
- return value instanceof ObjectId2;
490
+ return value instanceof ObjectId3;
223
491
  }
492
+
493
+ // src/query/operators.ts
494
+ var $eq = (value) => ({ $eq: value });
495
+ var $ne = (value) => ({ $ne: value });
496
+ var $gt = (value) => ({ $gt: value });
497
+ var $gte = (value) => ({ $gte: value });
498
+ var $lt = (value) => ({ $lt: value });
499
+ var $lte = (value) => ({ $lte: value });
500
+ var $in = (values) => ({ $in: values });
501
+ var $nin = (values) => ({ $nin: values });
502
+ var $exists = (flag = true) => ({ $exists: flag });
503
+ var $regex = (pattern) => ({
504
+ $regex: pattern
505
+ });
506
+ var $not = (op) => ({
507
+ $not: op
508
+ });
509
+ var $or = (...filters) => ({ $or: filters });
510
+ var $and = (...filters) => ({ $and: filters });
511
+ var $nor = (...filters) => ({ $nor: filters });
512
+ var raw = (filter) => filter;
224
513
  export {
514
+ $and,
515
+ $eq,
516
+ $exists,
517
+ $gt,
518
+ $gte,
519
+ $in,
520
+ $lt,
521
+ $lte,
522
+ $ne,
523
+ $nin,
524
+ $nor,
525
+ $not,
526
+ $or,
527
+ $regex,
528
+ CollectionHandle,
529
+ Database,
225
530
  IndexBuilder,
531
+ ZodmonNotFoundError,
532
+ ZodmonValidationError,
226
533
  collection,
534
+ createClient,
535
+ extractDbName,
227
536
  extractFieldIndexes,
537
+ findOne,
538
+ findOneOrThrow,
228
539
  getIndexMetadata,
229
540
  getRefMetadata,
230
541
  index,
542
+ insertMany,
543
+ insertOne,
231
544
  installExtensions,
232
545
  installRefExtension,
233
546
  isOid,
234
547
  objectId,
235
- oid
548
+ oid,
549
+ raw
236
550
  };
237
551
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/collection/collection.ts","../src/schema/extensions.ts","../src/schema/ref.ts","../src/schema/object-id.ts","../src/collection/index-def.ts","../src/helpers/oid.ts"],"sourcesContent":["import { z } from 'zod'\nimport { getIndexMetadata } from '../schema/extensions'\nimport { objectId } from '../schema/object-id'\nimport type {\n\tCollectionDefinition,\n\tCollectionOptions,\n\tFieldIndexDefinition,\n\tResolvedShape,\n} from './types'\n\n/**\n * Walk a Zod shape and extract field-level index metadata from each field.\n *\n * Returns an array of {@link FieldIndexDefinition} for every field that has\n * been marked with `.index()`, `.unique()`, `.text()`, or `.expireAfter()`.\n * Fields without index metadata are silently skipped.\n *\n * @param shape - A Zod shape object (the value passed to `z.object()`).\n * @returns An array of field index definitions with the field name attached.\n */\nexport function extractFieldIndexes(shape: z.core.$ZodShape): FieldIndexDefinition[] {\n\tconst result: FieldIndexDefinition[] = []\n\tfor (const [field, schema] of Object.entries(shape)) {\n\t\tconst meta = getIndexMetadata(schema)\n\t\tif (meta) {\n\t\t\tresult.push({ field, ...meta })\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Define a MongoDB collection with a Zod schema.\n *\n * Creates a {@link CollectionDefinition} that:\n * - Adds `_id: objectId()` if the shape doesn't already include `_id`\n * - Uses the user-provided `_id` schema if one is present (e.g. nanoid, UUID)\n * - Extracts field-level index metadata from the shape\n * - Separates compound indexes from the rest of the options\n * - Returns an immutable definition object\n *\n * @param name - The MongoDB collection name.\n * @param shape - A Zod shape object defining the document fields. May include a custom `_id`.\n * @param options - Optional collection-level configuration including compound indexes.\n * @returns A {@link CollectionDefinition} ready for use with `createClient()`.\n *\n * @example\n * ```ts\n * const Users = collection('users', {\n * email: z.string().unique(),\n * name: z.string().index(),\n * age: z.number().optional(),\n * })\n * ```\n */\nexport function collection<TShape extends z.core.$ZodShape>(\n\tname: string,\n\tshape: TShape,\n\toptions?: CollectionOptions<Extract<keyof TShape, string>>,\n): CollectionDefinition<TShape> {\n\t// TypeScript cannot narrow generic conditional types through control flow,\n\t// so the assertion is needed here. Both branches are provably correct:\n\t// - when _id is in shape: ResolvedShape<TShape> = TShape\n\t// - when _id is absent: ResolvedShape<TShape> = { _id: ZodObjectId } & TShape\n\tconst resolvedShape = (\n\t\t'_id' in shape ? shape : { _id: objectId(), ...shape }\n\t) as ResolvedShape<TShape>\n\tconst schema = z.object(resolvedShape)\n\n\tconst fieldIndexes = extractFieldIndexes(shape)\n\n\tconst { indexes: compoundIndexes, validation, ...rest } = options ?? {}\n\n\treturn {\n\t\tname,\n\t\t// Zod v4's z.object() returns ZodObject<{ -readonly [P in keyof T]: T[P] }> which\n\t\t// strips readonly modifiers. With exactOptionalPropertyTypes this mapped type is\n\t\t// not assignable to ZodObject<ResolvedShape<TShape>>. The cast is safe because\n\t\t// the runtime shape is correct — only the readonly modifier differs.\n\t\tschema: schema as CollectionDefinition<TShape>['schema'],\n\t\tshape,\n\t\tfieldIndexes,\n\t\tcompoundIndexes: compoundIndexes ?? [],\n\t\toptions: {\n\t\t\tvalidation: validation ?? 'strict',\n\t\t\t...rest,\n\t\t},\n\t}\n}\n","import { z } from 'zod'\nimport { installRefExtension } from './ref'\n\n/**\n * Options controlling how a field-level MongoDB index is created.\n *\n * Passed to the `.index()` Zod extension method. Every property is optional;\n * omitting all of them creates a standard ascending, non-unique index.\n */\nexport type IndexOptions = {\n\t/** When `true`, MongoDB enforces a unique constraint on this field. */\n\tunique?: boolean\n\t/**\n\t * When `true`, the index skips documents where the field is `null` or missing.\n\t * Useful for optional fields that should be indexed only when present.\n\t */\n\tsparse?: boolean\n\t/** When `true`, creates a MongoDB text index for full-text search on this field. */\n\ttext?: boolean\n\t/**\n\t * When `true`, the index is created in descending order (`-1`).\n\t * Defaults to ascending (`1`) when omitted.\n\t */\n\tdescending?: boolean\n\t/**\n\t * TTL in seconds. MongoDB will automatically delete documents once the\n\t * indexed `Date` field is older than this many seconds. Only valid on\n\t * fields whose runtime type is `Date`.\n\t */\n\texpireAfter?: number\n\t/**\n\t * A partial filter expression. Only documents matching this filter are\n\t * included in the index. Maps directly to MongoDB's `partialFilterExpression`.\n\t */\n\tpartial?: Record<string, unknown>\n}\n\n/**\n * Metadata stored in the WeakMap sidecar for every schema that has been\n * marked with `.index()`. Always contains `indexed: true` plus any\n * {@link IndexOptions} the caller provided.\n */\nexport type IndexMetadata = {\n\t/** Always `true` — acts as a discriminator for \"this field is indexed\". */\n\tindexed: true\n} & IndexOptions\n\ndeclare module 'zod' {\n\tinterface ZodType {\n\t\t/**\n\t\t * Mark this field for indexing when `syncIndexes()` is called.\n\t\t *\n\t\t * Stores {@link IndexOptions} in a WeakMap sidecar so the collection\n\t\t * factory can later introspect each field and build the appropriate\n\t\t * MongoDB index specification.\n\t\t *\n\t\t * @param options - Optional index configuration (unique, sparse, etc.).\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const UserSchema = z.object({\n\t\t * email: z.string().index({ unique: true }),\n\t\t * age: z.number().index({ sparse: true }),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tindex(options?: IndexOptions): this\n\n\t\t/**\n\t\t * Shorthand for `.index({ unique: true })`.\n\t\t *\n\t\t * Creates a unique index on this field, causing MongoDB to reject\n\t\t * duplicate values.\n\t\t *\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const UserSchema = z.object({\n\t\t * email: z.string().unique(),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tunique(): this\n\n\t\t/**\n\t\t * Shorthand for `.index({ text: true })`.\n\t\t *\n\t\t * Creates a MongoDB text index on this field, enabling `$text` queries\n\t\t * for full-text search.\n\t\t *\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const PostSchema = z.object({\n\t\t * body: z.string().text(),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\ttext(): this\n\n\t\t/**\n\t\t * Shorthand for `.index({ expireAfter: seconds })`.\n\t\t *\n\t\t * Creates a TTL (Time-To-Live) index. MongoDB will automatically\n\t\t * remove documents once the indexed `Date` field is older than\n\t\t * the specified number of seconds.\n\t\t *\n\t\t * @param seconds - Number of seconds after which documents expire.\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const SessionSchema = z.object({\n\t\t * createdAt: z.date().expireAfter(3600), // 1 hour TTL\n\t\t * })\n\t\t * ```\n\t\t */\n\t\texpireAfter(seconds: number): this\n\t}\n}\n\n/**\n * WeakMap sidecar that associates a Zod schema instance with its\n * {@link IndexMetadata}.\n *\n * A WeakMap is used so that index metadata does not prevent garbage collection\n * of schema instances. The keys are the Zod schema objects themselves, and\n * the values are the corresponding `IndexMetadata` descriptors.\n */\nconst indexMetadata = new WeakMap<object, IndexMetadata>()\n\n/**\n * Retrieve the index metadata attached to a Zod schema, if any.\n *\n * Returns `undefined` when the schema was never marked with an index\n * extension (`.index()`, `.unique()`, `.text()`, or `.expireAfter()`).\n *\n * @param schema - The Zod schema to inspect. Accepts `unknown` for\n * convenience; non-object values safely return `undefined`.\n * @returns The {@link IndexMetadata} for the schema, or `undefined`.\n *\n * @example\n * ```ts\n * const email = z.string().index({ unique: true })\n * const meta = getIndexMetadata(email)\n * // => { indexed: true, unique: true }\n * ```\n */\nexport function getIndexMetadata(schema: unknown): IndexMetadata | undefined {\n\tif (typeof schema !== 'object' || schema === null) return undefined\n\treturn indexMetadata.get(schema)\n}\n\n/**\n * Symbol used as a guard property on `ZodType.prototype` to prevent\n * double-registration of Zodmon extension methods. The symbol is created\n * with `Symbol.for` so it is shared across realms / duplicate module loads.\n */\nconst GUARD = Symbol.for('zodmon_extensions')\n\n/**\n * Monkey-patch Zod's `ZodType.prototype` with Zodmon extension methods\n * (`.index()`, `.unique()`, `.text()`, `.expireAfter()`).\n *\n * In Zod v4, methods are copied from `ZodType.prototype` to each instance\n * during construction via the internal `init` loop (`Object.keys(proto)` ->\n * copy to instance). Extension methods use `enumerable: true` so they are\n * picked up by this loop for every schema created after installation.\n *\n * The function is idempotent: calling it more than once is a safe no-op,\n * guarded by a non-enumerable `Symbol.for('zodmon_extensions')` property\n * on the prototype.\n *\n * This function is called at module level when `extensions.ts` is first\n * imported, so consumers never need to call it manually. It is exported\n * primarily for use in tests.\n *\n * @example\n * ```ts\n * import { installExtensions } from '@zodmon/core/schema/extensions'\n * installExtensions() // safe to call multiple times\n *\n * const indexed = z.string().index({ unique: true })\n * ```\n */\nexport function installExtensions(): void {\n\tconst proto = z.ZodType.prototype\n\tif (GUARD in proto) return\n\n\tObject.defineProperty(proto, 'index', {\n\t\t/**\n\t\t * Declares a MongoDB index on this field. Accepts optional\n\t\t * {@link IndexOptions} to configure uniqueness, sparseness, text search,\n\t\t * sort direction, TTL, or partial filters.\n\t\t *\n\t\t * @param options - Index configuration. Omit for a standard ascending index.\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const name = z.string().index()\n\t\t * const email = z.string().index({ unique: true, sparse: true })\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto, options?: IndexOptions): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, ...options })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, 'unique', {\n\t\t/**\n\t\t * Shorthand for `.index({ unique: true })`. Marks this field as requiring\n\t\t * a unique index in MongoDB, preventing duplicate values.\n\t\t *\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const email = z.string().unique()\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, unique: true })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, 'text', {\n\t\t/**\n\t\t * Shorthand for `.index({ text: true })`. Creates a MongoDB text index on\n\t\t * this field, enabling full-text search queries with `$text`.\n\t\t *\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const bio = z.string().text()\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, text: true })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, 'expireAfter', {\n\t\t/**\n\t\t * Shorthand for `.index({ expireAfter: seconds })`. Creates a TTL index on\n\t\t * a `Date` field. MongoDB will automatically remove documents once the field\n\t\t * value is older than the specified number of seconds.\n\t\t *\n\t\t * @param seconds - TTL in seconds after which documents expire.\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const expiresAt = z.date().expireAfter(86400) // 24 hours\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto, seconds: number): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, expireAfter: seconds })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tinstallRefExtension()\n\n\tObject.defineProperty(proto, GUARD, {\n\t\tvalue: true,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t})\n}\n\ninstallExtensions()\n","import { z } from 'zod'\nimport type { AnyCollection, InferDocument } from '../collection/types'\n\n/**\n * Type-level marker that carries the target collection type through the\n * type system. Intersected with the schema return type by `.ref()` so\n * that `RefFields<T>` (future) can extract ref relationships.\n *\n * This is a phantom brand — no runtime value has this property.\n */\nexport type RefMarker<TCollection extends AnyCollection = AnyCollection> = {\n\treadonly _ref: TCollection\n}\n\n/**\n * Metadata stored in the WeakMap sidecar for schemas marked with `.ref()`.\n * Holds a reference to the target collection definition object.\n */\nexport type RefMetadata = {\n\treadonly collection: AnyCollection\n}\n\n/**\n * Module augmentation: adds `.ref()` to all `ZodType` schemas.\n *\n * The intersection constraint `this['_zod']['output'] extends InferDocument<TCollection>['_id']`\n * ensures compile-time type safety: the field's output type must match the\n * target collection's `_id` type. Mismatches produce a type error.\n *\n * Supports both default ObjectId `_id` and custom `_id` types (string, nanoid, etc.):\n * - `objectId().ref(Users)` compiles when Users has ObjectId `_id`\n * - `z.string().ref(Orgs)` compiles when Orgs has string `_id`\n * - `z.string().ref(Users)` is a type error (string ≠ ObjectId)\n * - `objectId().ref(Orgs)` is a type error (ObjectId ≠ string)\n */\ndeclare module 'zod' {\n\tinterface ZodType {\n\t\t/**\n\t\t * Declare a typed foreign key reference to another collection.\n\t\t *\n\t\t * Stores the target collection definition in metadata for runtime\n\t\t * populate resolution, and brands the return type with\n\t\t * `RefMarker<TCollection>` so `RefFields<T>` can extract refs\n\t\t * at the type level.\n\t\t *\n\t\t * The field's output type must match the target collection's `_id` type.\n\t\t * Mismatched types produce a compile error.\n\t\t *\n\t\t * Apply `.ref()` before wrapper methods like `.optional()` or `.nullable()`:\n\t\t * `objectId().ref(Users).optional()` — not `objectId().optional().ref(Users)`.\n\t\t *\n\t\t * @param collection - The target collection definition object.\n\t\t * @returns The same schema instance, branded with the ref marker.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const Posts = collection('posts', {\n\t\t * authorId: objectId().ref(Users),\n\t\t * title: z.string(),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tref<TCollection extends AnyCollection>(\n\t\t\tcollection: TCollection &\n\t\t\t\t(this['_zod']['output'] extends InferDocument<TCollection>['_id'] ? unknown : never),\n\t\t): this & RefMarker<TCollection>\n\t}\n}\n\n/**\n * WeakMap sidecar that associates a Zod schema instance with its\n * {@link RefMetadata}. Uses WeakMap so ref metadata does not prevent\n * garbage collection of schema instances.\n */\nconst refMetadata = new WeakMap<object, RefMetadata>()\n\n/**\n * Retrieve the ref metadata attached to a Zod schema, if any.\n *\n * Returns `undefined` when the schema was never marked with `.ref()`.\n *\n * @param schema - The Zod schema to inspect. Accepts `unknown` for\n * convenience; non-object values safely return `undefined`.\n * @returns The {@link RefMetadata} for the schema, or `undefined`.\n *\n * @example\n * ```ts\n * const authorId = objectId().ref(Users)\n * const meta = getRefMetadata(authorId)\n * // => { collection: Users }\n * ```\n */\nexport function getRefMetadata(schema: unknown): RefMetadata | undefined {\n\tif (typeof schema !== 'object' || schema === null) return undefined\n\treturn refMetadata.get(schema)\n}\n\n/**\n * Symbol guard to prevent double-registration of the `.ref()` extension.\n * Uses `Symbol.for` so it is shared across realms / duplicate module loads.\n */\nconst REF_GUARD = Symbol.for('zodmon_ref')\n\n/**\n * Install the `.ref()` extension method on `ZodType.prototype`.\n *\n * Idempotent — safe to call multiple times.\n */\nexport function installRefExtension(): void {\n\tconst proto = z.ZodType.prototype\n\tif (REF_GUARD in proto) return\n\n\tObject.defineProperty(proto, 'ref', {\n\t\tvalue(this: typeof proto, collection: AnyCollection): typeof proto {\n\t\t\trefMetadata.set(this, { collection })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, REF_GUARD, {\n\t\tvalue: true,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t})\n}\n","import { ObjectId } from 'mongodb'\nimport { type ZodCustom, type ZodPipe, type ZodTransform, z } from 'zod'\n\n/** Matches a 24-character hexadecimal string (case-insensitive). */\nconst OBJECT_ID_HEX = /^[a-f\\d]{24}$/i\n\n/**\n * The Zod type produced by {@link objectId}. A pipeline that validates an\n * input as either a `string` (24-char hex) or an `ObjectId` instance, then\n * transforms it into a concrete `ObjectId`.\n *\n * Use `z.infer<ZodObjectId>` to extract the output type (`ObjectId`) and\n * `z.input<ZodObjectId>` for the input type (`string | ObjectId`).\n */\nexport type ZodObjectId = ZodPipe<\n\tZodCustom<string | ObjectId, string | ObjectId>,\n\tZodTransform<ObjectId, string | ObjectId>\n>\n\n/**\n * Create a Zod schema that validates and coerces values into MongoDB\n * `ObjectId` instances.\n *\n * Accepts either:\n * - An existing `ObjectId` instance (passed through unchanged).\n * - A 24-character hexadecimal string (coerced to `ObjectId`).\n *\n * All other inputs are rejected with the message `\"Invalid ObjectId\"`.\n *\n * @returns A {@link ZodObjectId} schema.\n *\n * @example\n * ```ts\n * const schema = objectId()\n *\n * schema.parse(new ObjectId()) // OK — pass-through\n * schema.parse('64f1a2b3c4d5e6f7a8b9c0d1') // OK — coerced to ObjectId\n * schema.parse('not-valid') // throws ZodError\n * ```\n *\n * @example Inside a z.object() shape:\n * ```ts\n * const UserSchema = z.object({\n * _id: objectId(),\n * name: z.string(),\n * })\n * ```\n */\nexport function objectId(): ZodObjectId {\n\treturn z\n\t\t.custom<string | ObjectId>((val): val is string | ObjectId => {\n\t\t\tif (val instanceof ObjectId) return true\n\t\t\treturn typeof val === 'string' && OBJECT_ID_HEX.test(val)\n\t\t}, 'Invalid ObjectId')\n\t\t.transform((val) => (val instanceof ObjectId ? val : ObjectId.createFromHexString(val)))\n}\n","/**\n * A builder for compound index definitions.\n *\n * Provides a fluent API for declaring compound indexes with options like\n * `unique`, `sparse`, and custom `name`. Each method returns a new\n * IndexBuilder instance (immutable pattern — the original is never mutated).\n *\n * IndexBuilder is structurally compatible with {@link CompoundIndexDefinition}\n * so instances can be used directly in `CollectionOptions.indexes`.\n *\n * Dot-notation paths like `'address.city'` are accepted at the value level\n * (any string satisfies `TKeys`), but type-level validation against nested\n * schema paths is deferred to a future release.\n *\n * @example\n * ```ts\n * index({ email: 1, role: -1 }).unique().name('email_role_idx')\n * ```\n */\n\nimport type { CompoundIndexDefinition } from './types'\n\ntype IndexDirection = 1 | -1\n\ntype CompoundIndexOptions = NonNullable<CompoundIndexDefinition['options']>\n\nexport class IndexBuilder<TKeys extends string> {\n\t// Typed as Partial for structural compatibility with CompoundIndexDefinition.\n\t// The constructor guarantees all keys are present at runtime.\n\treadonly fields: Partial<Record<TKeys, IndexDirection>>\n\treadonly options: CompoundIndexOptions\n\n\tconstructor(fields: Record<TKeys, IndexDirection>) {\n\t\tthis.fields = fields\n\t\tthis.options = {}\n\t}\n\n\tprivate _clone(options: CompoundIndexOptions): IndexBuilder<TKeys> {\n\t\t// Object.create returns `any`; cast is safe because we assign the correct shape\n\t\treturn Object.assign(Object.create(IndexBuilder.prototype) as IndexBuilder<TKeys>, {\n\t\t\tfields: this.fields,\n\t\t\toptions,\n\t\t})\n\t}\n\n\tunique(): IndexBuilder<TKeys> {\n\t\treturn this._clone({ ...this.options, unique: true })\n\t}\n\n\tsparse(): IndexBuilder<TKeys> {\n\t\treturn this._clone({ ...this.options, sparse: true })\n\t}\n\n\tname(name: string): IndexBuilder<TKeys> {\n\t\treturn this._clone({ ...this.options, name })\n\t}\n}\n\n/**\n * Create a compound index definition with a fluent builder API.\n *\n * Returns an {@link IndexBuilder} that is structurally compatible with\n * `CompoundIndexDefinition`, so it can be used directly in\n * `CollectionOptions.indexes` alongside plain objects.\n *\n * @param fields - An object mapping field names to sort direction (1 or -1).\n * @returns An {@link IndexBuilder} instance.\n *\n * @example\n * ```ts\n * collection('users', { email: z.string(), role: z.string() }, {\n * indexes: [\n * index({ email: 1, role: -1 }).unique(),\n * { fields: { role: 1 } },\n * ],\n * })\n * ```\n */\nexport function index<TKeys extends string>(\n\tfields: Record<TKeys, IndexDirection>,\n): IndexBuilder<TKeys> {\n\treturn new IndexBuilder(fields)\n}\n","import { ObjectId } from 'mongodb'\n\n/**\n * Create or coerce a MongoDB `ObjectId`.\n *\n * - Called with **no arguments**: generates a brand-new `ObjectId`.\n * - Called with a **hex string**: coerces it to an `ObjectId` via\n * `ObjectId.createFromHexString`.\n * - Called with an **existing `ObjectId`**: returns it unchanged.\n *\n * This is a convenience wrapper that removes the need for `new ObjectId()`\n * boilerplate throughout application code.\n *\n * @param value - Optional hex string or `ObjectId` to coerce. Omit to\n * generate a new `ObjectId`.\n * @returns An `ObjectId` instance.\n *\n * @example\n * ```ts\n * oid() // new random ObjectId\n * oid('64f1a2b3c4d5e6f7a8b9c0d1') // coerce hex string\n * oid(existingId) // pass-through\n * ```\n */\nexport function oid(): ObjectId\nexport function oid(value: string): ObjectId\nexport function oid(value: ObjectId): ObjectId\nexport function oid(value?: string | ObjectId): ObjectId {\n\tif (value === undefined) return new ObjectId()\n\tif (value instanceof ObjectId) return value\n\treturn ObjectId.createFromHexString(value)\n}\n\n/**\n * Type guard that narrows an `unknown` value to `ObjectId`.\n *\n * Uses `instanceof` internally, so it works with any value without risk\n * of throwing.\n *\n * @param value - The value to check.\n * @returns `true` if `value` is an `ObjectId` instance.\n *\n * @example\n * ```ts\n * const raw: unknown = getFromDb()\n * if (isOid(raw)) {\n * console.log(raw.toHexString()) // raw is narrowed to ObjectId\n * }\n * ```\n */\nexport function isOid(value: unknown): value is ObjectId {\n\treturn value instanceof ObjectId\n}\n"],"mappings":"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","names":["z","z","collection","z","z","z","ObjectId"]}
1
+ {"version":3,"sources":["../src/client/client.ts","../src/crud/find.ts","../src/errors/not-found.ts","../src/errors/validation.ts","../src/crud/insert.ts","../src/client/handle.ts","../src/collection/collection.ts","../src/schema/extensions.ts","../src/schema/ref.ts","../src/schema/object-id.ts","../src/collection/index-def.ts","../src/helpers/oid.ts","../src/query/operators.ts"],"sourcesContent":["import type { Db, MongoClientOptions } from 'mongodb'\nimport { MongoClient } from 'mongodb'\nimport type { z } from 'zod'\nimport type { CollectionDefinition, InferDocument } from '../collection/types'\nimport { CollectionHandle } from './handle'\n\n/**\n * Wraps a MongoDB `MongoClient` and `Db`, providing typed collection access\n * through {@link CollectionHandle}s.\n *\n * Connection is lazy — the driver connects on the first operation, not at\n * construction time. Call {@link close} for graceful shutdown.\n *\n * @example\n * ```ts\n * const db = createClient('mongodb://localhost:27017', 'myapp')\n * const users = db.use(UsersCollection)\n * await users.native.insertOne({ _id: oid(), name: 'Ada' })\n * await db.close()\n * ```\n */\nexport class Database {\n\tprivate readonly _client: MongoClient\n\tprivate readonly _db: Db\n\t/** Registered collection definitions, keyed by name. Used by syncIndexes(). */\n\tprivate readonly _collections = new Map<string, CollectionDefinition>()\n\n\tconstructor(uri: string, dbName: string, options?: MongoClientOptions) {\n\t\tthis._client = new MongoClient(uri, options)\n\t\tthis._db = this._client.db(dbName)\n\t}\n\n\t/**\n\t * Register a collection definition and return a typed {@link CollectionHandle}.\n\t *\n\t * The handle's `native` property is a MongoDB `Collection<TDoc>` where `TDoc`\n\t * is the document type inferred from the definition's Zod schema. Calling\n\t * `use()` multiple times with the same definition is safe — each call returns\n\t * a new lightweight handle backed by the same underlying driver collection.\n\t *\n\t * @param def - A collection definition created by `collection()`.\n\t * @returns A typed collection handle for CRUD operations.\n\t */\n\tuse<TShape extends z.core.$ZodShape>(\n\t\tdef: CollectionDefinition<TShape>,\n\t): CollectionHandle<CollectionDefinition<TShape>> {\n\t\t// Safe cast: erasing TShape for internal collection tracking.\n\t\t// Stored definitions are only iterated in syncIndexes() where\n\t\t// index metadata is accessed structurally.\n\t\tthis._collections.set(def.name, def as unknown as CollectionDefinition)\n\t\tconst native = this._db.collection<InferDocument<CollectionDefinition<TShape>>>(def.name)\n\t\t// Safe cast: CollectionDefinition<TShape> → AnyCollection for the constructor.\n\t\t// The generic is preserved through the return type annotation.\n\t\treturn new CollectionHandle(\n\t\t\tdef as unknown as CollectionDefinition<TShape>,\n\t\t\tnative,\n\t\t) as CollectionHandle<CollectionDefinition<TShape>>\n\t}\n\n\t/**\n\t * Synchronize indexes defined in registered collections with MongoDB.\n\t *\n\t * Stub — full implementation in TASK-92.\n\t */\n\tsyncIndexes(): Promise<void> {\n\t\treturn Promise.resolve()\n\t}\n\n\t/**\n\t * Execute a function within a MongoDB transaction with auto-commit/rollback.\n\t *\n\t * Stub — full implementation in TASK-106.\n\t */\n\ttransaction<T>(_fn: () => Promise<T>): Promise<T> {\n\t\tthrow new Error('Not implemented')\n\t}\n\n\t/**\n\t * Close the underlying `MongoClient` connection. Safe to call even if\n\t * no connection was established (the driver handles this gracefully).\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this._client.close()\n\t}\n}\n\n/**\n * Extract the database name from a MongoDB connection URI.\n *\n * Handles standard URIs, multi-host/replica set, SRV (`mongodb+srv://`),\n * auth credentials, query parameters, and percent-encoded database names.\n * Returns `undefined` when no database name is present.\n */\nexport function extractDbName(uri: string): string | undefined {\n\tconst withoutProtocol = uri.replace(/^mongodb(?:\\+srv)?:\\/\\//, '')\n\t// Safe cast: split() always returns at least one element, but noUncheckedIndexedAccess\n\t// types [0] as string | undefined.\n\tconst withoutQuery = withoutProtocol.split('?')[0] as string\n\t// Skip past auth credentials (user:pass@) before searching for the path separator\n\tconst atIndex = withoutQuery.lastIndexOf('@')\n\tconst hostAndPath = atIndex === -1 ? withoutQuery : withoutQuery.slice(atIndex + 1)\n\tconst slashIndex = hostAndPath.indexOf('/')\n\tif (slashIndex === -1) return undefined\n\tconst dbName = decodeURIComponent(hostAndPath.slice(slashIndex + 1))\n\treturn dbName || undefined\n}\n\n/**\n * Create a new {@link Database} instance wrapping a MongoDB connection.\n *\n * The connection is lazy — the driver connects on the first operation.\n * Pass any `MongoClientOptions` to configure connection pooling, timeouts, etc.\n *\n * When `dbName` is omitted, the database name is extracted from the URI path\n * (e.g. `mongodb://localhost:27017/myapp` → `'myapp'`). If no database name\n * is found in either the arguments or the URI, a warning is logged and\n * MongoDB's default `'test'` database is used.\n *\n * @param uri - MongoDB connection string (e.g. `mongodb://localhost:27017`).\n * @param dbName - The database name to use.\n * @param options - Optional MongoDB driver client options.\n * @returns A new `Database` instance.\n */\nexport function createClient(uri: string, dbName: string, options?: MongoClientOptions): Database\nexport function createClient(uri: string, options?: MongoClientOptions): Database\nexport function createClient(\n\turi: string,\n\tdbNameOrOptions?: string | MongoClientOptions,\n\tmaybeOptions?: MongoClientOptions,\n): Database {\n\tif (typeof dbNameOrOptions === 'string') {\n\t\treturn new Database(uri, dbNameOrOptions, maybeOptions)\n\t}\n\tconst parsed = extractDbName(uri)\n\tif (!parsed) {\n\t\tconsole.warn('[zodmon] No database name provided — using MongoDB default \"test\"')\n\t}\n\treturn new Database(uri, parsed ?? 'test', dbNameOrOptions)\n}\n","import { z } from 'zod'\nimport type { CollectionHandle } from '../client/handle'\nimport type { AnyCollection, InferDocument, ValidationMode } from '../collection/types'\nimport { ZodmonNotFoundError } from '../errors/not-found'\nimport { ZodmonValidationError } from '../errors/validation'\nimport type { TypedFilter } from '../query/filter'\n\n/**\n * Options for {@link findOne} and {@link findOneOrThrow}.\n */\nexport type FindOneOptions = {\n\t/** MongoDB projection — include (`1`) or exclude (`0`) fields. Typed projections deferred to v1.0. */\n\tproject?: Record<string, 0 | 1>\n\t/** Override the collection-level validation mode, or `false` to skip validation entirely. */\n\tvalidate?: ValidationMode | false\n}\n\n/**\n * Find a single document matching the filter.\n *\n * Queries MongoDB, then validates the fetched document against the collection's\n * Zod schema. Validation mode is resolved from the per-query option, falling\n * back to the collection-level default (which defaults to `'strict'`).\n *\n * @param handle - The collection handle to query.\n * @param filter - Type-safe filter to match documents.\n * @param options - Optional projection and validation overrides.\n * @returns The matched document, or `null` if no document matches.\n * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.\n *\n * @example\n * ```ts\n * const user = await findOne(users, { name: 'Ada' })\n * if (user) console.log(user.role) // typed as 'admin' | 'user'\n * ```\n */\nexport async function findOne<TDef extends AnyCollection>(\n\thandle: CollectionHandle<TDef>,\n\tfilter: TypedFilter<InferDocument<TDef>>,\n\toptions?: FindOneOptions,\n): Promise<InferDocument<TDef> | null> {\n\tconst findOptions = options?.project ? { projection: options.project } : undefined\n\t// Safe cast: TypedFilter<InferDocument<TDef>> is a strict subset of\n\t// MongoDB's Filter<T>, but the intersection-based mapped type cannot\n\t// be structurally matched by the driver's looser Filter type.\n\t// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter\n\tconst raw = await handle.native.findOne(filter as any, findOptions)\n\tif (!raw) return null\n\n\tconst mode =\n\t\toptions?.validate !== undefined ? options.validate : handle.definition.options.validation\n\n\tif (mode === false || mode === 'passthrough') {\n\t\t// Safe cast: raw document from MongoDB matches the collection's document\n\t\t// shape at runtime. Skipping validation per user request.\n\t\treturn raw as InferDocument<TDef>\n\t}\n\n\ttry {\n\t\t// Safe cast: schema.parse() returns z.infer<schema> which equals\n\t\t// InferDocument<TDef>, but TypeScript cannot prove this for\n\t\t// generic TDef. The runtime type is guaranteed correct by Zod.\n\t\treturn handle.definition.schema.parse(raw) as InferDocument<TDef>\n\t} catch (err) {\n\t\tif (err instanceof z.ZodError) {\n\t\t\tthrow new ZodmonValidationError(handle.definition.name, err)\n\t\t}\n\t\tthrow err\n\t}\n}\n\n/**\n * Find a single document matching the filter, or throw if none exists.\n *\n * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}\n * instead of returning `null` when no document matches the filter.\n *\n * @param handle - The collection handle to query.\n * @param filter - Type-safe filter to match documents.\n * @param options - Optional projection and validation overrides.\n * @returns The matched document (never null).\n * @throws {ZodmonNotFoundError} When no document matches the filter.\n * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.\n *\n * @example\n * ```ts\n * const user = await findOneOrThrow(users, { name: 'Ada' })\n * console.log(user.role) // typed as 'admin' | 'user', guaranteed non-null\n * ```\n */\nexport async function findOneOrThrow<TDef extends AnyCollection>(\n\thandle: CollectionHandle<TDef>,\n\tfilter: TypedFilter<InferDocument<TDef>>,\n\toptions?: FindOneOptions,\n): Promise<InferDocument<TDef>> {\n\tconst doc = await findOne(handle, filter, options)\n\tif (!doc) {\n\t\tthrow new ZodmonNotFoundError(handle.definition.name)\n\t}\n\treturn doc\n}\n","/**\n * Thrown when a query expected to find a document returns no results.\n *\n * Used by {@link findOneOrThrow} when no document matches the provided filter.\n * Callers can inspect `.collection` to identify which collection the query targeted.\n *\n * @example\n * ```ts\n * try {\n * await users.findOneOrThrow({ name: 'nonexistent' })\n * } catch (err) {\n * if (err instanceof ZodmonNotFoundError) {\n * console.log(err.message) // => 'Document not found in \"users\"'\n * console.log(err.collection) // => 'users'\n * }\n * }\n * ```\n */\nexport class ZodmonNotFoundError extends Error {\n\toverride readonly name = 'ZodmonNotFoundError'\n\n\t/** The MongoDB collection name where the query found no results. */\n\treadonly collection: string\n\n\tconstructor(collection: string) {\n\t\tsuper(`Document not found in \"${collection}\"`)\n\t\tthis.collection = collection\n\t}\n}\n","import type { z } from 'zod'\n\n/**\n * Thrown when a document fails Zod schema validation before a MongoDB write.\n *\n * Wraps the original `ZodError` with the collection name and a human-readable\n * message listing each invalid field and its error. Callers can inspect\n * `.zodError.issues` for programmatic access to individual failures.\n *\n * @example\n * ```ts\n * try {\n * await users.insertOne({ name: 123 })\n * } catch (err) {\n * if (err instanceof ZodmonValidationError) {\n * console.log(err.message)\n * // => 'Validation failed for \"users\": name (Expected string, received number)'\n * console.log(err.collection) // => 'users'\n * console.log(err.zodError) // => ZodError with .issues array\n * }\n * }\n * ```\n */\nexport class ZodmonValidationError extends Error {\n\toverride readonly name = 'ZodmonValidationError'\n\n\t/** The MongoDB collection name where the validation failed. */\n\treadonly collection: string\n\n\t/** The original Zod validation error with detailed issue information. */\n\treadonly zodError: z.ZodError\n\n\tconstructor(collection: string, zodError: z.ZodError) {\n\t\tconst fields = zodError.issues\n\t\t\t.map((issue) => {\n\t\t\t\tconst path = issue.path.join('.') || '(root)'\n\t\t\t\treturn `${path} (${issue.message})`\n\t\t\t})\n\t\t\t.join(', ')\n\t\tsuper(`Validation failed for \"${collection}\": ${fields}`)\n\t\tthis.collection = collection\n\t\tthis.zodError = zodError\n\t}\n}\n","import { z } from 'zod'\nimport type { CollectionHandle } from '../client/handle'\nimport type { AnyCollection, InferDocument, InferInsert } from '../collection/types'\nimport { ZodmonValidationError } from '../errors/validation'\n\n/**\n * Insert a single document into the collection.\n *\n * Validates the input against the collection's Zod schema before writing.\n * Schema defaults (including auto-generated `_id`) are applied during\n * validation. Returns the full document with all defaults filled in.\n *\n * @param handle - The collection handle to insert into.\n * @param doc - The document to insert. Fields with `.default()` are optional.\n * @returns The inserted document with `_id` and all defaults applied.\n * @throws {ZodmonValidationError} When the document fails schema validation.\n *\n * @example\n * ```ts\n * const user = await insertOne(users, { name: 'Ada' })\n * console.log(user._id) // ObjectId (auto-generated)\n * console.log(user.role) // 'user' (schema default)\n * ```\n */\nexport async function insertOne<TDef extends AnyCollection>(\n\thandle: CollectionHandle<TDef>,\n\tdoc: InferInsert<TDef>,\n): Promise<InferDocument<TDef>> {\n\tlet parsed: InferDocument<TDef>\n\ttry {\n\t\t// Safe cast: schema.parse() returns z.infer<schema> which equals\n\t\t// InferDocument<TDef>, but TypeScript cannot prove this for\n\t\t// generic TDef. The runtime type is guaranteed correct by Zod.\n\t\tparsed = handle.definition.schema.parse(doc) as InferDocument<TDef>\n\t} catch (err) {\n\t\tif (err instanceof z.ZodError) {\n\t\t\tthrow new ZodmonValidationError(handle.definition.name, err)\n\t\t}\n\t\tthrow err\n\t}\n\t// Safe cast: parsed is a full document with _id, matching the collection's\n\t// document type. MongoDB driver's OptionalUnlessRequiredId makes _id optional\n\t// for generic types, but we always have _id after parse.\n\t// biome-ignore lint/suspicious/noExplicitAny: MongoDB driver's insertOne parameter type uses OptionalUnlessRequiredId which is not directly assignable from our generic InferDocument\n\tawait handle.native.insertOne(parsed as any)\n\treturn parsed\n}\n\n/**\n * Insert multiple documents into the collection.\n *\n * Validates every document against the collection's Zod schema before\n * writing any to MongoDB. If any document fails validation, none are\n * inserted (fail-fast before the driver call).\n *\n * @param handle - The collection handle to insert into.\n * @param docs - The documents to insert.\n * @returns The inserted documents with `_id` and all defaults applied.\n * @throws {ZodmonValidationError} When any document fails schema validation.\n *\n * @example\n * ```ts\n * const users = await insertMany(handle, [\n * { name: 'Ada' },\n * { name: 'Bob', role: 'admin' },\n * ])\n * ```\n */\nexport async function insertMany<TDef extends AnyCollection>(\n\thandle: CollectionHandle<TDef>,\n\tdocs: InferInsert<TDef>[],\n): Promise<InferDocument<TDef>[]> {\n\tif (docs.length === 0) return []\n\tconst parsed: InferDocument<TDef>[] = []\n\tfor (const doc of docs) {\n\t\ttry {\n\t\t\t// Safe cast: schema.parse() returns z.infer<schema> which equals\n\t\t\t// InferDocument<TDef>, but TypeScript cannot prove this for\n\t\t\t// generic TDef. The runtime type is guaranteed correct by Zod.\n\t\t\tparsed.push(handle.definition.schema.parse(doc) as InferDocument<TDef>)\n\t\t} catch (err) {\n\t\t\tif (err instanceof z.ZodError) {\n\t\t\t\tthrow new ZodmonValidationError(handle.definition.name, err)\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\t// biome-ignore lint/suspicious/noExplicitAny: MongoDB driver's insertMany parameter type is not directly assignable from our generic InferDocument\n\tawait handle.native.insertMany(parsed as any)\n\treturn parsed\n}\n","import type { Collection } from 'mongodb'\nimport type { AnyCollection, InferDocument, InferInsert } from '../collection/types'\nimport type { FindOneOptions } from '../crud/find'\nimport { findOne as _findOne, findOneOrThrow as _findOneOrThrow } from '../crud/find'\nimport { insertMany as _insertMany, insertOne as _insertOne } from '../crud/insert'\nimport type { TypedFilter } from '../query/filter'\n\n/**\n * Typed wrapper around a MongoDB driver `Collection`.\n *\n * Created by {@link Database.use}. Holds the original `CollectionDefinition`\n * (for runtime schema validation and index metadata) alongside the native\n * driver collection parameterized with the inferred document type.\n *\n * @typeParam TDef - The collection definition type. Used to derive both\n * the document type (`InferDocument`) and the insert type (`InferInsert`).\n */\nexport class CollectionHandle<TDef extends AnyCollection = AnyCollection> {\n\t/** The collection definition containing schema, name, and index metadata. */\n\treadonly definition: TDef\n\n\t/** The underlying MongoDB driver collection, typed to the inferred document type. */\n\treadonly native: Collection<InferDocument<TDef>>\n\n\tconstructor(definition: TDef, native: Collection<InferDocument<TDef>>) {\n\t\tthis.definition = definition\n\t\tthis.native = native\n\t}\n\n\t/**\n\t * Insert a single document into the collection.\n\t *\n\t * Validates the input against the collection's Zod schema before writing.\n\t * Schema defaults (including auto-generated `_id`) are applied during\n\t * validation. Returns the full document with all defaults filled in.\n\t *\n\t * @param doc - The document to insert. Fields with `.default()` are optional.\n\t * @returns The inserted document with `_id` and all defaults applied.\n\t * @throws {ZodmonValidationError} When the document fails schema validation.\n\t *\n\t * @example\n\t * ```ts\n\t * const users = db.use(Users)\n\t * const user = await users.insertOne({ name: 'Ada' })\n\t * console.log(user._id) // ObjectId (auto-generated)\n\t * console.log(user.role) // 'user' (schema default)\n\t * ```\n\t */\n\tasync insertOne(doc: InferInsert<TDef>): Promise<InferDocument<TDef>> {\n\t\treturn await _insertOne(this, doc)\n\t}\n\n\t/**\n\t * Insert multiple documents into the collection.\n\t *\n\t * Validates every document against the collection's Zod schema before\n\t * writing any to MongoDB. If any document fails validation, none are\n\t * inserted (fail-fast before the driver call).\n\t *\n\t * @param docs - The documents to insert.\n\t * @returns The inserted documents with `_id` and all defaults applied.\n\t * @throws {ZodmonValidationError} When any document fails schema validation.\n\t *\n\t * @example\n\t * ```ts\n\t * const created = await users.insertMany([\n\t * { name: 'Ada' },\n\t * { name: 'Bob', role: 'admin' },\n\t * ])\n\t * ```\n\t */\n\tasync insertMany(docs: InferInsert<TDef>[]): Promise<InferDocument<TDef>[]> {\n\t\treturn await _insertMany(this, docs)\n\t}\n\n\t/**\n\t * Find a single document matching the filter.\n\t *\n\t * Queries MongoDB, then validates the fetched document against the collection's\n\t * Zod schema. Validation mode is resolved from the per-query option, falling\n\t * back to the collection-level default (which defaults to `'strict'`).\n\t *\n\t * @param filter - Type-safe filter to match documents.\n\t * @param options - Optional projection and validation overrides.\n\t * @returns The matched document, or `null` if no document matches.\n\t * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.\n\t *\n\t * @example\n\t * ```ts\n\t * const users = db.use(Users)\n\t * const user = await users.findOne({ name: 'Ada' })\n\t * if (user) console.log(user.role)\n\t * ```\n\t */\n\tasync findOne(\n\t\tfilter: TypedFilter<InferDocument<TDef>>,\n\t\toptions?: FindOneOptions,\n\t): Promise<InferDocument<TDef> | null> {\n\t\treturn await _findOne(this, filter, options)\n\t}\n\n\t/**\n\t * Find a single document matching the filter, or throw if none exists.\n\t *\n\t * Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}\n\t * instead of returning `null` when no document matches the filter.\n\t *\n\t * @param filter - Type-safe filter to match documents.\n\t * @param options - Optional projection and validation overrides.\n\t * @returns The matched document (never null).\n\t * @throws {ZodmonNotFoundError} When no document matches the filter.\n\t * @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.\n\t *\n\t * @example\n\t * ```ts\n\t * const users = db.use(Users)\n\t * const user = await users.findOneOrThrow({ name: 'Ada' })\n\t * console.log(user.role) // guaranteed non-null\n\t * ```\n\t */\n\tasync findOneOrThrow(\n\t\tfilter: TypedFilter<InferDocument<TDef>>,\n\t\toptions?: FindOneOptions,\n\t): Promise<InferDocument<TDef>> {\n\t\treturn await _findOneOrThrow(this, filter, options)\n\t}\n}\n","import { ObjectId } from 'mongodb'\nimport { z } from 'zod'\nimport { getIndexMetadata } from '../schema/extensions'\nimport { objectId } from '../schema/object-id'\nimport type {\n\tCollectionDefinition,\n\tCollectionOptions,\n\tFieldIndexDefinition,\n\tResolvedShape,\n} from './types'\n\n/**\n * Walk a Zod shape and extract field-level index metadata from each field.\n *\n * Returns an array of {@link FieldIndexDefinition} for every field that has\n * been marked with `.index()`, `.unique()`, `.text()`, or `.expireAfter()`.\n * Fields without index metadata are silently skipped.\n *\n * @param shape - A Zod shape object (the value passed to `z.object()`).\n * @returns An array of field index definitions with the field name attached.\n */\nexport function extractFieldIndexes(shape: z.core.$ZodShape): FieldIndexDefinition[] {\n\tconst result: FieldIndexDefinition[] = []\n\tfor (const [field, schema] of Object.entries(shape)) {\n\t\tconst meta = getIndexMetadata(schema)\n\t\tif (meta) {\n\t\t\tresult.push({ field, ...meta })\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Define a MongoDB collection with a Zod schema.\n *\n * Creates a {@link CollectionDefinition} that:\n * - Adds `_id: objectId()` if the shape doesn't already include `_id`\n * - Uses the user-provided `_id` schema if one is present (e.g. nanoid, UUID)\n * - Extracts field-level index metadata from the shape\n * - Separates compound indexes from the rest of the options\n * - Returns an immutable definition object\n *\n * @param name - The MongoDB collection name.\n * @param shape - A Zod shape object defining the document fields. May include a custom `_id`.\n * @param options - Optional collection-level configuration including compound indexes.\n * @returns A {@link CollectionDefinition} ready for use with `createClient()`.\n *\n * @example\n * ```ts\n * const Users = collection('users', {\n * email: z.string().unique(),\n * name: z.string().index(),\n * age: z.number().optional(),\n * })\n * ```\n */\nexport function collection<TShape extends z.core.$ZodShape>(\n\tname: string,\n\tshape: TShape,\n\toptions?: CollectionOptions<Extract<keyof TShape, string>>,\n): CollectionDefinition<TShape> {\n\t// TypeScript cannot narrow generic conditional types through control flow,\n\t// so the assertion is needed here. Both branches are provably correct:\n\t// - when _id is in shape: ResolvedShape<TShape> = TShape\n\t// - when _id is absent: ResolvedShape<TShape> = { _id: ZodObjectId } & TShape\n\tconst resolvedShape = (\n\t\t'_id' in shape ? shape : { _id: objectId().default(() => new ObjectId()), ...shape }\n\t) as ResolvedShape<TShape>\n\tconst schema = z.object(resolvedShape)\n\n\tconst fieldIndexes = extractFieldIndexes(shape)\n\n\tconst { indexes: compoundIndexes, validation, ...rest } = options ?? {}\n\n\treturn {\n\t\tname,\n\t\t// Zod v4's z.object() returns ZodObject<{ -readonly [P in keyof T]: T[P] }> which\n\t\t// strips readonly modifiers. With exactOptionalPropertyTypes this mapped type is\n\t\t// not assignable to ZodObject<ResolvedShape<TShape>>. The cast is safe because\n\t\t// the runtime shape is correct — only the readonly modifier differs.\n\t\tschema: schema as CollectionDefinition<TShape>['schema'],\n\t\tshape,\n\t\tfieldIndexes,\n\t\tcompoundIndexes: compoundIndexes ?? [],\n\t\toptions: {\n\t\t\tvalidation: validation ?? 'strict',\n\t\t\t...rest,\n\t\t},\n\t}\n}\n","import { z } from 'zod'\nimport { installRefExtension } from './ref'\n\n/**\n * Options controlling how a field-level MongoDB index is created.\n *\n * Passed to the `.index()` Zod extension method. Every property is optional;\n * omitting all of them creates a standard ascending, non-unique index.\n */\nexport type IndexOptions = {\n\t/** When `true`, MongoDB enforces a unique constraint on this field. */\n\tunique?: boolean\n\t/**\n\t * When `true`, the index skips documents where the field is `null` or missing.\n\t * Useful for optional fields that should be indexed only when present.\n\t */\n\tsparse?: boolean\n\t/** When `true`, creates a MongoDB text index for full-text search on this field. */\n\ttext?: boolean\n\t/**\n\t * When `true`, the index is created in descending order (`-1`).\n\t * Defaults to ascending (`1`) when omitted.\n\t */\n\tdescending?: boolean\n\t/**\n\t * TTL in seconds. MongoDB will automatically delete documents once the\n\t * indexed `Date` field is older than this many seconds. Only valid on\n\t * fields whose runtime type is `Date`.\n\t */\n\texpireAfter?: number\n\t/**\n\t * A partial filter expression. Only documents matching this filter are\n\t * included in the index. Maps directly to MongoDB's `partialFilterExpression`.\n\t */\n\tpartial?: Record<string, unknown>\n}\n\n/**\n * Metadata stored in the WeakMap sidecar for every schema that has been\n * marked with `.index()`. Always contains `indexed: true` plus any\n * {@link IndexOptions} the caller provided.\n */\nexport type IndexMetadata = {\n\t/** Always `true` — acts as a discriminator for \"this field is indexed\". */\n\tindexed: true\n} & IndexOptions\n\ndeclare module 'zod' {\n\tinterface ZodType {\n\t\t/**\n\t\t * Mark this field for indexing when `syncIndexes()` is called.\n\t\t *\n\t\t * Stores {@link IndexOptions} in a WeakMap sidecar so the collection\n\t\t * factory can later introspect each field and build the appropriate\n\t\t * MongoDB index specification.\n\t\t *\n\t\t * @param options - Optional index configuration (unique, sparse, etc.).\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const UserSchema = z.object({\n\t\t * email: z.string().index({ unique: true }),\n\t\t * age: z.number().index({ sparse: true }),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tindex(options?: IndexOptions): this\n\n\t\t/**\n\t\t * Shorthand for `.index({ unique: true })`.\n\t\t *\n\t\t * Creates a unique index on this field, causing MongoDB to reject\n\t\t * duplicate values.\n\t\t *\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const UserSchema = z.object({\n\t\t * email: z.string().unique(),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tunique(): this\n\n\t\t/**\n\t\t * Shorthand for `.index({ text: true })`.\n\t\t *\n\t\t * Creates a MongoDB text index on this field, enabling `$text` queries\n\t\t * for full-text search.\n\t\t *\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const PostSchema = z.object({\n\t\t * body: z.string().text(),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\ttext(): this\n\n\t\t/**\n\t\t * Shorthand for `.index({ expireAfter: seconds })`.\n\t\t *\n\t\t * Creates a TTL (Time-To-Live) index. MongoDB will automatically\n\t\t * remove documents once the indexed `Date` field is older than\n\t\t * the specified number of seconds.\n\t\t *\n\t\t * @param seconds - Number of seconds after which documents expire.\n\t\t * @returns The same schema instance for chaining.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const SessionSchema = z.object({\n\t\t * createdAt: z.date().expireAfter(3600), // 1 hour TTL\n\t\t * })\n\t\t * ```\n\t\t */\n\t\texpireAfter(seconds: number): this\n\t}\n}\n\n/**\n * WeakMap sidecar that associates a Zod schema instance with its\n * {@link IndexMetadata}.\n *\n * A WeakMap is used so that index metadata does not prevent garbage collection\n * of schema instances. The keys are the Zod schema objects themselves, and\n * the values are the corresponding `IndexMetadata` descriptors.\n */\nconst indexMetadata = new WeakMap<object, IndexMetadata>()\n\n/**\n * Retrieve the index metadata attached to a Zod schema, if any.\n *\n * Returns `undefined` when the schema was never marked with an index\n * extension (`.index()`, `.unique()`, `.text()`, or `.expireAfter()`).\n *\n * @param schema - The Zod schema to inspect. Accepts `unknown` for\n * convenience; non-object values safely return `undefined`.\n * @returns The {@link IndexMetadata} for the schema, or `undefined`.\n *\n * @example\n * ```ts\n * const email = z.string().index({ unique: true })\n * const meta = getIndexMetadata(email)\n * // => { indexed: true, unique: true }\n * ```\n */\nexport function getIndexMetadata(schema: unknown): IndexMetadata | undefined {\n\tif (typeof schema !== 'object' || schema === null) return undefined\n\treturn indexMetadata.get(schema)\n}\n\n/**\n * Symbol used as a guard property on `ZodType.prototype` to prevent\n * double-registration of Zodmon extension methods. The symbol is created\n * with `Symbol.for` so it is shared across realms / duplicate module loads.\n */\nconst GUARD = Symbol.for('zodmon_extensions')\n\n/**\n * Monkey-patch Zod's `ZodType.prototype` with Zodmon extension methods\n * (`.index()`, `.unique()`, `.text()`, `.expireAfter()`).\n *\n * In Zod v4, methods are copied from `ZodType.prototype` to each instance\n * during construction via the internal `init` loop (`Object.keys(proto)` ->\n * copy to instance). Extension methods use `enumerable: true` so they are\n * picked up by this loop for every schema created after installation.\n *\n * The function is idempotent: calling it more than once is a safe no-op,\n * guarded by a non-enumerable `Symbol.for('zodmon_extensions')` property\n * on the prototype.\n *\n * This function is called at module level when `extensions.ts` is first\n * imported, so consumers never need to call it manually. It is exported\n * primarily for use in tests.\n *\n * @example\n * ```ts\n * import { installExtensions } from '@zodmon/core/schema/extensions'\n * installExtensions() // safe to call multiple times\n *\n * const indexed = z.string().index({ unique: true })\n * ```\n */\nexport function installExtensions(): void {\n\tconst proto = z.ZodType.prototype\n\tif (GUARD in proto) return\n\n\tObject.defineProperty(proto, 'index', {\n\t\t/**\n\t\t * Declares a MongoDB index on this field. Accepts optional\n\t\t * {@link IndexOptions} to configure uniqueness, sparseness, text search,\n\t\t * sort direction, TTL, or partial filters.\n\t\t *\n\t\t * @param options - Index configuration. Omit for a standard ascending index.\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const name = z.string().index()\n\t\t * const email = z.string().index({ unique: true, sparse: true })\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto, options?: IndexOptions): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, ...options })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, 'unique', {\n\t\t/**\n\t\t * Shorthand for `.index({ unique: true })`. Marks this field as requiring\n\t\t * a unique index in MongoDB, preventing duplicate values.\n\t\t *\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const email = z.string().unique()\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, unique: true })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, 'text', {\n\t\t/**\n\t\t * Shorthand for `.index({ text: true })`. Creates a MongoDB text index on\n\t\t * this field, enabling full-text search queries with `$text`.\n\t\t *\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const bio = z.string().text()\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, text: true })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, 'expireAfter', {\n\t\t/**\n\t\t * Shorthand for `.index({ expireAfter: seconds })`. Creates a TTL index on\n\t\t * a `Date` field. MongoDB will automatically remove documents once the field\n\t\t * value is older than the specified number of seconds.\n\t\t *\n\t\t * @param seconds - TTL in seconds after which documents expire.\n\t\t * @returns The same schema instance for chainability.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const expiresAt = z.date().expireAfter(86400) // 24 hours\n\t\t * ```\n\t\t */\n\t\tvalue(this: typeof proto, seconds: number): typeof proto {\n\t\t\tindexMetadata.set(this, { indexed: true, expireAfter: seconds })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tinstallRefExtension()\n\n\tObject.defineProperty(proto, GUARD, {\n\t\tvalue: true,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t})\n}\n\ninstallExtensions()\n","import { z } from 'zod'\nimport type { AnyCollection, InferDocument } from '../collection/types'\n\n/**\n * Type-level marker that carries the target collection type through the\n * type system. Intersected with the schema return type by `.ref()` so\n * that `RefFields<T>` (future) can extract ref relationships.\n *\n * This is a phantom brand — no runtime value has this property.\n */\nexport type RefMarker<TCollection extends AnyCollection = AnyCollection> = {\n\treadonly _ref: TCollection\n}\n\n/**\n * Metadata stored in the WeakMap sidecar for schemas marked with `.ref()`.\n * Holds a reference to the target collection definition object.\n */\nexport type RefMetadata = {\n\treadonly collection: AnyCollection\n}\n\n/**\n * Module augmentation: adds `.ref()` to all `ZodType` schemas.\n *\n * The intersection constraint `this['_zod']['output'] extends InferDocument<TCollection>['_id']`\n * ensures compile-time type safety: the field's output type must match the\n * target collection's `_id` type. Mismatches produce a type error.\n *\n * Supports both default ObjectId `_id` and custom `_id` types (string, nanoid, etc.):\n * - `objectId().ref(Users)` compiles when Users has ObjectId `_id`\n * - `z.string().ref(Orgs)` compiles when Orgs has string `_id`\n * - `z.string().ref(Users)` is a type error (string ≠ ObjectId)\n * - `objectId().ref(Orgs)` is a type error (ObjectId ≠ string)\n */\ndeclare module 'zod' {\n\tinterface ZodType {\n\t\t/**\n\t\t * Declare a typed foreign key reference to another collection.\n\t\t *\n\t\t * Stores the target collection definition in metadata for runtime\n\t\t * populate resolution, and brands the return type with\n\t\t * `RefMarker<TCollection>` so `RefFields<T>` can extract refs\n\t\t * at the type level.\n\t\t *\n\t\t * The field's output type must match the target collection's `_id` type.\n\t\t * Mismatched types produce a compile error.\n\t\t *\n\t\t * Apply `.ref()` before wrapper methods like `.optional()` or `.nullable()`:\n\t\t * `objectId().ref(Users).optional()` — not `objectId().optional().ref(Users)`.\n\t\t *\n\t\t * @param collection - The target collection definition object.\n\t\t * @returns The same schema instance, branded with the ref marker.\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const Posts = collection('posts', {\n\t\t * authorId: objectId().ref(Users),\n\t\t * title: z.string(),\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tref<TCollection extends AnyCollection>(\n\t\t\tcollection: TCollection &\n\t\t\t\t(this['_zod']['output'] extends InferDocument<TCollection>['_id'] ? unknown : never),\n\t\t): this & RefMarker<TCollection>\n\t}\n}\n\n/**\n * WeakMap sidecar that associates a Zod schema instance with its\n * {@link RefMetadata}. Uses WeakMap so ref metadata does not prevent\n * garbage collection of schema instances.\n */\nconst refMetadata = new WeakMap<object, RefMetadata>()\n\n/**\n * Retrieve the ref metadata attached to a Zod schema, if any.\n *\n * Returns `undefined` when the schema was never marked with `.ref()`.\n *\n * @param schema - The Zod schema to inspect. Accepts `unknown` for\n * convenience; non-object values safely return `undefined`.\n * @returns The {@link RefMetadata} for the schema, or `undefined`.\n *\n * @example\n * ```ts\n * const authorId = objectId().ref(Users)\n * const meta = getRefMetadata(authorId)\n * // => { collection: Users }\n * ```\n */\nexport function getRefMetadata(schema: unknown): RefMetadata | undefined {\n\tif (typeof schema !== 'object' || schema === null) return undefined\n\treturn refMetadata.get(schema)\n}\n\n/**\n * Symbol guard to prevent double-registration of the `.ref()` extension.\n * Uses `Symbol.for` so it is shared across realms / duplicate module loads.\n */\nconst REF_GUARD = Symbol.for('zodmon_ref')\n\n/**\n * Install the `.ref()` extension method on `ZodType.prototype`.\n *\n * Idempotent — safe to call multiple times.\n */\nexport function installRefExtension(): void {\n\tconst proto = z.ZodType.prototype\n\tif (REF_GUARD in proto) return\n\n\tObject.defineProperty(proto, 'ref', {\n\t\tvalue(this: typeof proto, collection: AnyCollection): typeof proto {\n\t\t\trefMetadata.set(this, { collection })\n\t\t\treturn this\n\t\t},\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n\n\tObject.defineProperty(proto, REF_GUARD, {\n\t\tvalue: true,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t})\n}\n","import { ObjectId } from 'mongodb'\nimport { type ZodCustom, type ZodPipe, type ZodTransform, z } from 'zod'\n\n/** Matches a 24-character hexadecimal string (case-insensitive). */\nconst OBJECT_ID_HEX = /^[a-f\\d]{24}$/i\n\n/**\n * The Zod type produced by {@link objectId}. A pipeline that validates an\n * input as either a `string` (24-char hex) or an `ObjectId` instance, then\n * transforms it into a concrete `ObjectId`.\n *\n * Use `z.infer<ZodObjectId>` to extract the output type (`ObjectId`) and\n * `z.input<ZodObjectId>` for the input type (`string | ObjectId`).\n */\nexport type ZodObjectId = ZodPipe<\n\tZodCustom<string | ObjectId, string | ObjectId>,\n\tZodTransform<ObjectId, string | ObjectId>\n>\n\n/**\n * Create a Zod schema that validates and coerces values into MongoDB\n * `ObjectId` instances.\n *\n * Accepts either:\n * - An existing `ObjectId` instance (passed through unchanged).\n * - A 24-character hexadecimal string (coerced to `ObjectId`).\n *\n * All other inputs are rejected with the message `\"Invalid ObjectId\"`.\n *\n * @returns A {@link ZodObjectId} schema.\n *\n * @example\n * ```ts\n * const schema = objectId()\n *\n * schema.parse(new ObjectId()) // OK — pass-through\n * schema.parse('64f1a2b3c4d5e6f7a8b9c0d1') // OK — coerced to ObjectId\n * schema.parse('not-valid') // throws ZodError\n * ```\n *\n * @example Inside a z.object() shape:\n * ```ts\n * const UserSchema = z.object({\n * _id: objectId(),\n * name: z.string(),\n * })\n * ```\n */\nexport function objectId(): ZodObjectId {\n\treturn z\n\t\t.custom<string | ObjectId>((val): val is string | ObjectId => {\n\t\t\tif (val instanceof ObjectId) return true\n\t\t\treturn typeof val === 'string' && OBJECT_ID_HEX.test(val)\n\t\t}, 'Invalid ObjectId')\n\t\t.transform((val) => (val instanceof ObjectId ? val : ObjectId.createFromHexString(val)))\n}\n","/**\n * A builder for compound index definitions.\n *\n * Provides a fluent API for declaring compound indexes with options like\n * `unique`, `sparse`, and custom `name`. Each method returns a new\n * IndexBuilder instance (immutable pattern — the original is never mutated).\n *\n * IndexBuilder is structurally compatible with {@link CompoundIndexDefinition}\n * so instances can be used directly in `CollectionOptions.indexes`.\n *\n * Dot-notation paths like `'address.city'` are accepted at the value level\n * (any string satisfies `TKeys`), but type-level validation against nested\n * schema paths is deferred to a future release.\n *\n * @example\n * ```ts\n * index({ email: 1, role: -1 }).unique().name('email_role_idx')\n * ```\n */\n\nimport type { CompoundIndexDefinition } from './types'\n\ntype IndexDirection = 1 | -1\n\ntype CompoundIndexOptions = NonNullable<CompoundIndexDefinition['options']>\n\nexport class IndexBuilder<TKeys extends string> {\n\t// Typed as Partial for structural compatibility with CompoundIndexDefinition.\n\t// The constructor guarantees all keys are present at runtime.\n\treadonly fields: Partial<Record<TKeys, IndexDirection>>\n\treadonly options: CompoundIndexOptions\n\n\tconstructor(fields: Record<TKeys, IndexDirection>) {\n\t\tthis.fields = fields\n\t\tthis.options = {}\n\t}\n\n\tprivate _clone(options: CompoundIndexOptions): IndexBuilder<TKeys> {\n\t\t// Object.create returns `any`; cast is safe because we assign the correct shape\n\t\treturn Object.assign(Object.create(IndexBuilder.prototype) as IndexBuilder<TKeys>, {\n\t\t\tfields: this.fields,\n\t\t\toptions,\n\t\t})\n\t}\n\n\tunique(): IndexBuilder<TKeys> {\n\t\treturn this._clone({ ...this.options, unique: true })\n\t}\n\n\tsparse(): IndexBuilder<TKeys> {\n\t\treturn this._clone({ ...this.options, sparse: true })\n\t}\n\n\tname(name: string): IndexBuilder<TKeys> {\n\t\treturn this._clone({ ...this.options, name })\n\t}\n}\n\n/**\n * Create a compound index definition with a fluent builder API.\n *\n * Returns an {@link IndexBuilder} that is structurally compatible with\n * `CompoundIndexDefinition`, so it can be used directly in\n * `CollectionOptions.indexes` alongside plain objects.\n *\n * @param fields - An object mapping field names to sort direction (1 or -1).\n * @returns An {@link IndexBuilder} instance.\n *\n * @example\n * ```ts\n * collection('users', { email: z.string(), role: z.string() }, {\n * indexes: [\n * index({ email: 1, role: -1 }).unique(),\n * { fields: { role: 1 } },\n * ],\n * })\n * ```\n */\nexport function index<TKeys extends string>(\n\tfields: Record<TKeys, IndexDirection>,\n): IndexBuilder<TKeys> {\n\treturn new IndexBuilder(fields)\n}\n","import { ObjectId } from 'mongodb'\n\n/**\n * Create or coerce a MongoDB `ObjectId`.\n *\n * - Called with **no arguments**: generates a brand-new `ObjectId`.\n * - Called with a **hex string**: coerces it to an `ObjectId` via\n * `ObjectId.createFromHexString`.\n * - Called with an **existing `ObjectId`**: returns it unchanged.\n *\n * This is a convenience wrapper that removes the need for `new ObjectId()`\n * boilerplate throughout application code.\n *\n * @param value - Optional hex string or `ObjectId` to coerce. Omit to\n * generate a new `ObjectId`.\n * @returns An `ObjectId` instance.\n *\n * @example\n * ```ts\n * oid() // new random ObjectId\n * oid('64f1a2b3c4d5e6f7a8b9c0d1') // coerce hex string\n * oid(existingId) // pass-through\n * ```\n */\nexport function oid(): ObjectId\nexport function oid(value: string): ObjectId\nexport function oid(value: ObjectId): ObjectId\nexport function oid(value?: string | ObjectId): ObjectId {\n\tif (value === undefined) return new ObjectId()\n\tif (value instanceof ObjectId) return value\n\treturn ObjectId.createFromHexString(value)\n}\n\n/**\n * Type guard that narrows an `unknown` value to `ObjectId`.\n *\n * Uses `instanceof` internally, so it works with any value without risk\n * of throwing.\n *\n * @param value - The value to check.\n * @returns `true` if `value` is an `ObjectId` instance.\n *\n * @example\n * ```ts\n * const raw: unknown = getFromDb()\n * if (isOid(raw)) {\n * console.log(raw.toHexString()) // raw is narrowed to ObjectId\n * }\n * ```\n */\nexport function isOid(value: unknown): value is ObjectId {\n\treturn value instanceof ObjectId\n}\n","import type { TypedFilter } from './filter'\n\n// ── Value operators ──────────────────────────────────────────────────────────\n\n/**\n * Matches values equal to the specified value.\n *\n * @example\n * ```ts\n * // Explicit equality (equivalent to { name: 'Alice' })\n * users.find({ name: $eq('Alice') })\n * ```\n */\nexport const $eq = <V>(value: V): { $eq: V } => ({ $eq: value })\n\n/**\n * Matches values not equal to the specified value.\n *\n * @example\n * ```ts\n * users.find({ role: $ne('banned') })\n * ```\n */\nexport const $ne = <V>(value: V): { $ne: V } => ({ $ne: value })\n\n/**\n * Matches values greater than the specified value.\n *\n * @example\n * ```ts\n * users.find({ age: $gt(18) })\n * ```\n */\nexport const $gt = <V>(value: V): { $gt: V } => ({ $gt: value })\n\n/**\n * Matches values greater than or equal to the specified value.\n *\n * @example\n * ```ts\n * users.find({ age: $gte(18) })\n * ```\n */\nexport const $gte = <V>(value: V): { $gte: V } => ({ $gte: value })\n\n/**\n * Matches values less than the specified value.\n *\n * @example\n * ```ts\n * users.find({ age: $lt(65) })\n * ```\n */\nexport const $lt = <V>(value: V): { $lt: V } => ({ $lt: value })\n\n/**\n * Matches values less than or equal to the specified value.\n *\n * @example\n * ```ts\n * users.find({ age: $lte(65) })\n * ```\n */\nexport const $lte = <V>(value: V): { $lte: V } => ({ $lte: value })\n\n/**\n * Matches any value in the specified array.\n *\n * @example\n * ```ts\n * users.find({ role: $in(['admin', 'moderator']) })\n * ```\n */\nexport const $in = <V>(values: V[]): { $in: V[] } => ({ $in: values })\n\n/**\n * Matches none of the values in the specified array.\n *\n * @example\n * ```ts\n * users.find({ role: $nin(['banned', 'suspended']) })\n * ```\n */\nexport const $nin = <V>(values: V[]): { $nin: V[] } => ({ $nin: values })\n\n/**\n * Matches documents where the field exists (or does not exist).\n * Defaults to `true` when called with no arguments.\n *\n * @example\n * ```ts\n * // Field must exist\n * users.find({ email: $exists() })\n *\n * // Field must not exist\n * users.find({ deletedAt: $exists(false) })\n * ```\n */\nexport const $exists = (flag = true): { $exists: boolean } => ({ $exists: flag })\n\n/**\n * Matches string values against a regular expression pattern.\n * Only valid on string fields.\n *\n * @example\n * ```ts\n * users.find({ name: $regex(/^A/i) })\n * users.find({ email: $regex('^admin@') })\n * ```\n */\nexport const $regex = (pattern: RegExp | string): { $regex: RegExp | string } => ({\n\t$regex: pattern,\n})\n\n/**\n * Negates a comparison operator. Wraps the given operator object\n * in a `$not` condition.\n *\n * @example\n * ```ts\n * // Age is NOT greater than 65\n * users.find({ age: $not($gt(65)) })\n *\n * // Name does NOT match pattern\n * users.find({ name: $not($regex(/^test/)) })\n * ```\n */\nexport const $not = <O extends Record<string, unknown>>(op: O): { $not: O } => ({\n\t$not: op,\n})\n\n// ── Logical operators ────────────────────────────────────────────────────────\n\n/**\n * Joins filter clauses with a logical OR. Matches documents that satisfy\n * at least one of the provided filters.\n *\n * @example\n * ```ts\n * users.find($or({ role: 'admin' }, { age: $gte(18) }))\n *\n * // Dynamic composition\n * const conditions: TypedFilter<User>[] = []\n * if (name) conditions.push({ name })\n * if (role) conditions.push({ role })\n * users.find($or(...conditions))\n * ```\n */\nexport const $or = <T>(...filters: TypedFilter<T>[]): TypedFilter<T> =>\n\t({ $or: filters }) as TypedFilter<T>\n\n/**\n * Joins filter clauses with a logical AND. Matches documents that satisfy\n * all of the provided filters. Useful for dynamic filter building where\n * multiple conditions on the same field would conflict in an object literal.\n *\n * @example\n * ```ts\n * users.find($and(\n * $or({ role: 'admin' }, { role: 'moderator' }),\n * { age: $gte(18) },\n * { email: $exists() },\n * ))\n * ```\n */\nexport const $and = <T>(...filters: TypedFilter<T>[]): TypedFilter<T> =>\n\t({ $and: filters }) as TypedFilter<T>\n\n/**\n * Joins filter clauses with a logical NOR. Matches documents that fail\n * all of the provided filters.\n *\n * @example\n * ```ts\n * // Exclude banned and suspended users\n * users.find($nor({ role: 'banned' }, { role: 'suspended' }))\n * ```\n */\nexport const $nor = <T>(...filters: TypedFilter<T>[]): TypedFilter<T> =>\n\t({ $nor: filters }) as TypedFilter<T>\n\n// ── Escape hatch ─────────────────────────────────────────────────────────────\n\n/**\n * Escape hatch for unsupported or raw MongoDB filter operators.\n * Wraps an untyped filter object so it can be passed where `TypedFilter<T>` is expected.\n * Use when you need operators not covered by the type system (e.g., `$text`, `$geoNear`).\n *\n * @example\n * ```ts\n * users.find(raw({ $text: { $search: 'mongodb tutorial' } }))\n * ```\n */\n// biome-ignore lint/suspicious/noExplicitAny: intentional escape hatch for raw MongoDB filters\nexport const raw = <T = any>(filter: Record<string, unknown>): TypedFilter<T> =>\n\tfilter as TypedFilter<T>\n"],"mappings":"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","names":["collection","collection","raw","z","z","ObjectId","z","z","z","collection","z","z","ObjectId","z","ObjectId"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodmon/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript-native MongoDB ODM powered by Zod — schema DSL, CRUD, aggregation, populate, transactions",
5
5
  "type": "module",
6
6
  "exports": {