mondel 0.1.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 ADDED
@@ -0,0 +1,947 @@
1
+ 'use strict';
2
+
3
+ var mongodb = require('mongodb');
4
+ var zod = require('zod');
5
+
6
+ // src/schema/field-builder.ts
7
+ var FieldBuilder = class {
8
+ definition;
9
+ constructor(type) {
10
+ this.definition = {
11
+ type,
12
+ required: false,
13
+ unique: false
14
+ };
15
+ }
16
+ required() {
17
+ this.definition.required = true;
18
+ return this;
19
+ }
20
+ unique() {
21
+ this.definition.unique = true;
22
+ return this;
23
+ }
24
+ default(value) {
25
+ this.definition.default = value;
26
+ return this;
27
+ }
28
+ index(options) {
29
+ this.definition.index = options ?? { type: 1 };
30
+ return this;
31
+ }
32
+ build() {
33
+ return { ...this.definition };
34
+ }
35
+ };
36
+ var StringFieldBuilder = class extends FieldBuilder {
37
+ constructor() {
38
+ super("string");
39
+ }
40
+ min(length) {
41
+ this.definition.minLength = length;
42
+ return this;
43
+ }
44
+ max(length) {
45
+ this.definition.maxLength = length;
46
+ return this;
47
+ }
48
+ pattern(regex) {
49
+ this.definition.pattern = regex;
50
+ return this;
51
+ }
52
+ email() {
53
+ this.definition.email = true;
54
+ return this;
55
+ }
56
+ url() {
57
+ this.definition.url = true;
58
+ return this;
59
+ }
60
+ text() {
61
+ return this.index({ type: "text" });
62
+ }
63
+ enum(values) {
64
+ const builder = new EnumFieldBuilder(values);
65
+ const def = this.build();
66
+ if (def.required) builder.required();
67
+ if (def.unique) builder.unique();
68
+ if (def.index) builder.index(def.index);
69
+ return builder;
70
+ }
71
+ };
72
+ var EnumFieldBuilder = class extends FieldBuilder {
73
+ constructor(values) {
74
+ super("string");
75
+ this.definition.enum = values;
76
+ }
77
+ };
78
+ var NumberFieldBuilder = class extends FieldBuilder {
79
+ constructor() {
80
+ super("number");
81
+ }
82
+ min(value) {
83
+ this.definition.min = value;
84
+ return this;
85
+ }
86
+ max(value) {
87
+ this.definition.max = value;
88
+ return this;
89
+ }
90
+ };
91
+ var BooleanFieldBuilder = class extends FieldBuilder {
92
+ constructor() {
93
+ super("boolean");
94
+ }
95
+ };
96
+ var DateFieldBuilder = class extends FieldBuilder {
97
+ constructor() {
98
+ super("date");
99
+ }
100
+ };
101
+ var ObjectIdFieldBuilder = class extends FieldBuilder {
102
+ constructor() {
103
+ super("objectId");
104
+ }
105
+ };
106
+ var ArrayFieldBuilder = class extends FieldBuilder {
107
+ constructor(items) {
108
+ super("array");
109
+ this.definition.items = items;
110
+ }
111
+ };
112
+ var ObjectFieldBuilder = class extends FieldBuilder {
113
+ constructor(properties) {
114
+ super("object");
115
+ this.definition.properties = properties;
116
+ }
117
+ };
118
+ var JsonFieldBuilder = class extends FieldBuilder {
119
+ constructor() {
120
+ super("json");
121
+ }
122
+ };
123
+ var LiteralFieldBuilder = class extends FieldBuilder {
124
+ constructor(value) {
125
+ super("literal");
126
+ this.definition.literal = value;
127
+ }
128
+ };
129
+
130
+ // src/schema/schema-builder.ts
131
+ function resolveFields(fields) {
132
+ const resolved = {};
133
+ for (const [key, builder] of Object.entries(fields)) {
134
+ resolved[key] = builder.build();
135
+ }
136
+ return resolved;
137
+ }
138
+ var s = {
139
+ string() {
140
+ return new StringFieldBuilder();
141
+ },
142
+ number() {
143
+ return new NumberFieldBuilder();
144
+ },
145
+ boolean() {
146
+ return new BooleanFieldBuilder();
147
+ },
148
+ date() {
149
+ return new DateFieldBuilder();
150
+ },
151
+ objectId() {
152
+ return new ObjectIdFieldBuilder();
153
+ },
154
+ array(items) {
155
+ return new ArrayFieldBuilder(items.build());
156
+ },
157
+ object(properties) {
158
+ const resolvedProps = {};
159
+ for (const [key, builder] of Object.entries(properties)) {
160
+ resolvedProps[key] = builder.build();
161
+ }
162
+ return new ObjectFieldBuilder(resolvedProps);
163
+ },
164
+ json() {
165
+ return new JsonFieldBuilder();
166
+ },
167
+ literal(value) {
168
+ return new LiteralFieldBuilder(value);
169
+ },
170
+ enum(values) {
171
+ return new EnumFieldBuilder(values);
172
+ }
173
+ };
174
+
175
+ // src/schema/define-schema.ts
176
+ function schema(name, definition) {
177
+ const resolvedFields = resolveFields(definition.fields);
178
+ const timestamps = definition.timestamps === true ? { createdAt: "createdAt", updatedAt: "updatedAt" } : definition.timestamps === false || definition.timestamps === void 0 ? false : definition.timestamps;
179
+ return {
180
+ name,
181
+ collection: definition.collection ?? name,
182
+ timestamps,
183
+ validation: definition.validation ?? { enabled: false, mode: "off" },
184
+ connection: definition.connection,
185
+ fields: resolvedFields,
186
+ indexes: definition.indexes ?? []
187
+ };
188
+ }
189
+ var defineSchema = schema;
190
+ function getFieldIndexes(schema2) {
191
+ const indexes = [];
192
+ for (const [fieldName, fieldDef] of Object.entries(schema2.fields)) {
193
+ if (fieldDef.index) {
194
+ indexes.push({ field: fieldName, options: fieldDef.index });
195
+ }
196
+ if (fieldDef.unique && !fieldDef.index) {
197
+ indexes.push({ field: fieldName, options: { unique: true } });
198
+ }
199
+ }
200
+ return indexes;
201
+ }
202
+ function isObjectId(value) {
203
+ if (typeof value === "object" && value !== null) {
204
+ return "toHexString" in value && typeof value.toHexString === "function";
205
+ }
206
+ if (typeof value === "string") {
207
+ return /^[a-fA-F0-9]{24}$/.test(value);
208
+ }
209
+ return false;
210
+ }
211
+ function buildStringSchema(field) {
212
+ if (field.enum) {
213
+ return zod.z.enum(field.enum);
214
+ }
215
+ let schema2 = zod.z.string();
216
+ if (field.minLength !== void 0) schema2 = schema2.min(field.minLength);
217
+ if (field.maxLength !== void 0) schema2 = schema2.max(field.maxLength);
218
+ if (field.pattern) schema2 = schema2.regex(field.pattern);
219
+ if (field.email) schema2 = schema2.email();
220
+ if (field.url) schema2 = schema2.url();
221
+ return schema2;
222
+ }
223
+ function buildNumberSchema(field) {
224
+ let schema2 = zod.z.number();
225
+ if (field.min !== void 0) schema2 = schema2.min(field.min);
226
+ if (field.max !== void 0) schema2 = schema2.max(field.max);
227
+ return schema2;
228
+ }
229
+ function buildArraySchema(field) {
230
+ return field.items ? zod.z.array(fieldToZod(field.items)) : zod.z.array(zod.z.unknown());
231
+ }
232
+ function buildObjectSchema(field) {
233
+ if (!field.properties) {
234
+ return zod.z.record(zod.z.unknown());
235
+ }
236
+ const shape = {};
237
+ for (const [key, prop] of Object.entries(field.properties)) {
238
+ shape[key] = fieldToZod(prop);
239
+ }
240
+ return zod.z.object(shape);
241
+ }
242
+ var typeBuilders = {
243
+ string: buildStringSchema,
244
+ number: buildNumberSchema,
245
+ boolean: () => zod.z.boolean(),
246
+ date: () => zod.z.date(),
247
+ objectId: () => zod.z.union([zod.z.string(), zod.z.custom((val) => isObjectId(val))]),
248
+ array: buildArraySchema,
249
+ object: buildObjectSchema,
250
+ json: () => zod.z.unknown(),
251
+ literal: (field) => zod.z.literal(field.literal)
252
+ };
253
+ function fieldToZod(field) {
254
+ const builder = typeBuilders[field.type];
255
+ let schema2 = builder ? builder(field) : zod.z.unknown();
256
+ if (!field.required) {
257
+ schema2 = schema2.optional().nullable();
258
+ }
259
+ if (field.default !== void 0 && field.default !== "auto") {
260
+ schema2 = schema2.default(field.default);
261
+ }
262
+ return schema2;
263
+ }
264
+ function zodSchema(schema2) {
265
+ const shape = {};
266
+ for (const [fieldName, fieldDef] of Object.entries(schema2.fields)) {
267
+ shape[fieldName] = fieldToZod(fieldDef);
268
+ }
269
+ if (schema2.timestamps) {
270
+ const ts = schema2.timestamps;
271
+ if (ts.createdAt) {
272
+ shape[ts.createdAt] = zod.z.date().optional();
273
+ }
274
+ if (ts.updatedAt) {
275
+ shape[ts.updatedAt] = zod.z.date().optional();
276
+ }
277
+ }
278
+ return zod.z.object(shape);
279
+ }
280
+ function zodCreateSchema(schema2) {
281
+ const fullSchema = zodSchema(schema2);
282
+ const shape = { ...fullSchema.shape };
283
+ delete shape["_id"];
284
+ if (schema2.timestamps) {
285
+ const ts = schema2.timestamps;
286
+ if (ts.createdAt) delete shape[ts.createdAt];
287
+ if (ts.updatedAt) delete shape[ts.updatedAt];
288
+ }
289
+ return zod.z.object(shape);
290
+ }
291
+ function zodUpdateSchema(schema2) {
292
+ const createSchema = zodCreateSchema(schema2);
293
+ return createSchema.partial();
294
+ }
295
+ function validate(schema2, data) {
296
+ const result = schema2.safeParse(data);
297
+ if (result.success) {
298
+ return { success: true, data: result.data };
299
+ }
300
+ return { success: false, errors: result.error };
301
+ }
302
+
303
+ // src/manager/collection-proxy.ts
304
+ var CollectionProxy = class {
305
+ collection;
306
+ schema;
307
+ validationMode;
308
+ constructor(db, schema2, validationMode = "strict") {
309
+ this.collection = db.collection(schema2.collection);
310
+ this.schema = schema2;
311
+ this.validationMode = validationMode;
312
+ }
313
+ validateCreate(data) {
314
+ if (this.validationMode === "off") return;
315
+ const zodSchema2 = zodCreateSchema(this.schema);
316
+ const result = zodSchema2.safeParse(data);
317
+ if (!result.success) {
318
+ if (this.validationMode === "strict") {
319
+ throw new Error(`Validation failed: ${result.error.message}`);
320
+ }
321
+ console.warn(`Validation warning: ${result.error.message}`);
322
+ }
323
+ }
324
+ validateUpdate(data) {
325
+ if (this.validationMode === "off") return;
326
+ const zodSchema2 = zodUpdateSchema(this.schema);
327
+ const result = zodSchema2.safeParse(data);
328
+ if (!result.success) {
329
+ if (this.validationMode === "strict") {
330
+ throw new Error(`Validation failed: ${result.error.message}`);
331
+ }
332
+ console.warn(`Validation warning: ${result.error.message}`);
333
+ }
334
+ }
335
+ /**
336
+ * Find a single document matching the filter.
337
+ *
338
+ * @param where - MongoDB filter query
339
+ * @param options - Find options (select, sort, skip, limit, session)
340
+ * @returns The matching document or null
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * // Find by email
345
+ * const user = await db.users.findOne({ email: "john@example.com" });
346
+ *
347
+ * // With field selection
348
+ * const user = await db.users.findOne(
349
+ * { email: "john@example.com" },
350
+ * { select: { _id: true, email: true, name: true } }
351
+ * );
352
+ *
353
+ * // With session (for transactions)
354
+ * const user = await db.users.findOne(
355
+ * { email: "john@example.com" },
356
+ * { session }
357
+ * );
358
+ * ```
359
+ */
360
+ async findOne(where, options) {
361
+ const mongoOptions = {};
362
+ if (options?.select) {
363
+ mongoOptions.projection = options.select;
364
+ }
365
+ if (options?.sort) {
366
+ mongoOptions.sort = options.sort;
367
+ }
368
+ return this.collection.findOne(where, mongoOptions);
369
+ }
370
+ /**
371
+ * Find multiple documents matching the filter.
372
+ *
373
+ * @param where - MongoDB filter query (optional, defaults to {})
374
+ * @param options - Find options (select, sort, skip, limit, session)
375
+ * @returns Array of matching documents
376
+ *
377
+ * @example
378
+ * ```typescript
379
+ * // Find all active users
380
+ * const users = await db.users.findMany({ isActive: true });
381
+ *
382
+ * // With pagination and sorting
383
+ * const users = await db.users.findMany(
384
+ * { role: "ADMIN" },
385
+ * {
386
+ * select: { _id: true, email: true, name: true },
387
+ * sort: { createdAt: -1 },
388
+ * skip: 0,
389
+ * limit: 10
390
+ * }
391
+ * );
392
+ *
393
+ * // Using MongoDB operators
394
+ * const users = await db.users.findMany({
395
+ * age: { $gte: 18, $lte: 65 },
396
+ * role: { $in: ["ADMIN", "MODERATOR"] }
397
+ * });
398
+ * ```
399
+ */
400
+ async findMany(where = {}, options) {
401
+ let cursor = this.collection.find(where);
402
+ if (options?.select) {
403
+ cursor = cursor.project(options.select);
404
+ }
405
+ if (options?.sort) {
406
+ cursor = cursor.sort(options.sort);
407
+ }
408
+ if (options?.skip !== void 0) {
409
+ cursor = cursor.skip(options.skip);
410
+ }
411
+ if (options?.limit !== void 0) {
412
+ cursor = cursor.limit(options.limit);
413
+ }
414
+ return cursor.toArray();
415
+ }
416
+ /**
417
+ * Find a document by its _id.
418
+ *
419
+ * @param id - ObjectId or string representation
420
+ * @param options - Find options (select, session)
421
+ * @returns The matching document or null
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * // Find by string ID
426
+ * const user = await db.users.findById("507f1f77bcf86cd799439011");
427
+ *
428
+ * // Find by ObjectId
429
+ * const user = await db.users.findById(new ObjectId("507f1f77bcf86cd799439011"));
430
+ *
431
+ * // With field selection
432
+ * const user = await db.users.findById(userId, {
433
+ * select: { email: true, name: true }
434
+ * });
435
+ * ```
436
+ */
437
+ async findById(id, options) {
438
+ const objectId = this.parseObjectId(id);
439
+ const mongoOptions = { ...options };
440
+ if (options?.select) {
441
+ mongoOptions.projection = options.select;
442
+ delete mongoOptions.select;
443
+ }
444
+ return this.collection.findOne({ _id: objectId }, mongoOptions);
445
+ }
446
+ /**
447
+ * Create a new document.
448
+ * Automatically adds timestamps if enabled in schema.
449
+ *
450
+ * @param data - Document data (validated against schema)
451
+ * @param options - Create options (timestamps, session)
452
+ * @returns Insert result with insertedId
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * // Create a user
457
+ * const result = await db.users.create({
458
+ * email: "john@example.com",
459
+ * name: "John Doe",
460
+ * role: "USER"
461
+ * });
462
+ * console.log(result.insertedId); // ObjectId
463
+ *
464
+ * // Disable automatic timestamps
465
+ * await db.users.create(userData, { timestamps: false });
466
+ *
467
+ * // With session (for transactions)
468
+ * await db.users.create(userData, { session });
469
+ * ```
470
+ */
471
+ async create(data, options) {
472
+ this.validateCreate(data);
473
+ const doc = this.applyTimestamps(data, "create", options?.timestamps);
474
+ return this.collection.insertOne(doc);
475
+ }
476
+ /**
477
+ * Create multiple documents in a single operation.
478
+ * Automatically adds timestamps if enabled in schema.
479
+ *
480
+ * @param data - Array of document data
481
+ * @param options - Create options (timestamps, ordered, session)
482
+ * @returns Insert result with insertedIds
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * // Create multiple users
487
+ * const result = await db.users.createMany([
488
+ * { email: "user1@example.com", name: "User 1" },
489
+ * { email: "user2@example.com", name: "User 2" },
490
+ * { email: "user3@example.com", name: "User 3" }
491
+ * ]);
492
+ * console.log(result.insertedIds); // { 0: ObjectId, 1: ObjectId, 2: ObjectId }
493
+ *
494
+ * // With session (for transactions)
495
+ * await db.users.createMany(usersData, { session });
496
+ * ```
497
+ */
498
+ async createMany(data, options) {
499
+ for (const item of data) {
500
+ this.validateCreate(item);
501
+ }
502
+ const docs = data.map((d) => this.applyTimestamps(d, "create", options?.timestamps));
503
+ const { timestamps: _timestamps, ...mongoOptions } = options || {};
504
+ return this.collection.insertMany(docs, mongoOptions);
505
+ }
506
+ /**
507
+ * Update a single document matching the filter.
508
+ * Supports both simple updates and MongoDB update operators.
509
+ *
510
+ * @param where - MongoDB filter query
511
+ * @param data - Update data or MongoDB update operators ($set, $inc, etc.)
512
+ * @param options - Update options (upsert, timestamps, session)
513
+ * @returns Update result with matchedCount, modifiedCount, upsertedId
514
+ *
515
+ * @example
516
+ * ```typescript
517
+ * // Simple update (automatically wrapped in $set)
518
+ * await db.users.updateOne(
519
+ * { email: "john@example.com" },
520
+ * { name: "John Smith" }
521
+ * );
522
+ *
523
+ * // Using MongoDB operators
524
+ * await db.users.updateOne(
525
+ * { _id: userId },
526
+ * { $set: { name: "John" }, $inc: { loginCount: 1 } }
527
+ * );
528
+ *
529
+ * // Upsert - create if not exists
530
+ * await db.users.updateOne(
531
+ * { email: "john@example.com" },
532
+ * { $set: { name: "John", isActive: true } },
533
+ * { upsert: true }
534
+ * );
535
+ *
536
+ * // With session (for transactions)
537
+ * await db.users.updateOne(filter, update, { session });
538
+ * ```
539
+ */
540
+ async updateOne(where, data, options) {
541
+ const hasOperators = Object.keys(data).some((k) => k.startsWith("$"));
542
+ if (!hasOperators) {
543
+ this.validateUpdate(data);
544
+ }
545
+ const update = this.applyUpdateTimestamps(data, hasOperators, options);
546
+ const { timestamps: _timestamps, ...mongoOptions } = options || {};
547
+ return this.collection.updateOne(where, update, mongoOptions);
548
+ }
549
+ /**
550
+ * Update multiple documents matching the filter.
551
+ * Supports both simple updates and MongoDB update operators.
552
+ *
553
+ * @param where - MongoDB filter query
554
+ * @param data - Update data or MongoDB update operators
555
+ * @param options - Update options (upsert, timestamps, session)
556
+ * @returns Update result with matchedCount, modifiedCount
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * // Deactivate all users with expired subscriptions
561
+ * await db.users.updateMany(
562
+ * { subscriptionExpiry: { $lt: new Date() } },
563
+ * { $set: { isActive: false } }
564
+ * );
565
+ *
566
+ * // Increment login count for all admins
567
+ * await db.users.updateMany(
568
+ * { role: "ADMIN" },
569
+ * { $inc: { loginCount: 1 } }
570
+ * );
571
+ * ```
572
+ */
573
+ async updateMany(where, data, options) {
574
+ const hasOperators = Object.keys(data).some((k) => k.startsWith("$"));
575
+ if (!hasOperators) {
576
+ this.validateUpdate(data);
577
+ }
578
+ const update = this.applyUpdateTimestamps(data, hasOperators, options);
579
+ const { timestamps: _timestamps, ...mongoOptions } = options || {};
580
+ return this.collection.updateMany(where, update, mongoOptions);
581
+ }
582
+ /**
583
+ * Update a document by its _id.
584
+ * Convenience method that wraps updateOne with _id filter.
585
+ *
586
+ * @param id - ObjectId or string representation
587
+ * @param data - Update data or MongoDB update operators
588
+ * @param options - Update options (upsert, timestamps, session)
589
+ * @returns Update result with matchedCount, modifiedCount
590
+ *
591
+ * @example
592
+ * ```typescript
593
+ * // Update by ID
594
+ * await db.users.updateById(userId, { name: "New Name" });
595
+ *
596
+ * // Using operators
597
+ * await db.users.updateById(userId, {
598
+ * $set: { name: "New Name" },
599
+ * $push: { tags: "premium" }
600
+ * });
601
+ * ```
602
+ */
603
+ async updateById(id, data, options) {
604
+ const objectId = this.parseObjectId(id);
605
+ return this.updateOne({ _id: objectId }, data, options);
606
+ }
607
+ /**
608
+ * Delete a single document matching the filter.
609
+ *
610
+ * @param where - MongoDB filter query
611
+ * @param options - Delete options (session)
612
+ * @returns Delete result with deletedCount
613
+ *
614
+ * @example
615
+ * ```typescript
616
+ * // Delete by email
617
+ * await db.users.deleteOne({ email: "john@example.com" });
618
+ *
619
+ * // With session (for transactions)
620
+ * await db.users.deleteOne({ _id: userId }, { session });
621
+ * ```
622
+ */
623
+ async deleteOne(where, options) {
624
+ const { soft: _soft, ...mongoOptions } = options || {};
625
+ return this.collection.deleteOne(where, mongoOptions);
626
+ }
627
+ /**
628
+ * Delete multiple documents matching the filter.
629
+ *
630
+ * @param where - MongoDB filter query
631
+ * @param options - Delete options (session)
632
+ * @returns Delete result with deletedCount
633
+ *
634
+ * @example
635
+ * ```typescript
636
+ * // Delete all inactive users
637
+ * const result = await db.users.deleteMany({ isActive: false });
638
+ * console.log(`Deleted ${result.deletedCount} users`);
639
+ *
640
+ * // Delete users created before a date
641
+ * await db.users.deleteMany({
642
+ * createdAt: { $lt: new Date("2023-01-01") }
643
+ * });
644
+ * ```
645
+ */
646
+ async deleteMany(where, options) {
647
+ const { soft: _soft, ...mongoOptions } = options || {};
648
+ return this.collection.deleteMany(where, mongoOptions);
649
+ }
650
+ /**
651
+ * Delete a document by its _id.
652
+ *
653
+ * @param id - ObjectId or string representation
654
+ * @param options - Delete options (session)
655
+ * @returns Delete result with deletedCount
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * await db.users.deleteById("507f1f77bcf86cd799439011");
660
+ * await db.users.deleteById(userId, { session });
661
+ * ```
662
+ */
663
+ async deleteById(id, options) {
664
+ const objectId = this.parseObjectId(id);
665
+ return this.deleteOne({ _id: objectId }, options);
666
+ }
667
+ /**
668
+ * Count documents matching the filter.
669
+ *
670
+ * @param where - MongoDB filter query (optional)
671
+ * @param options - Count options (limit, skip, session)
672
+ * @returns Number of matching documents
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * // Count all users
677
+ * const total = await db.users.count();
678
+ *
679
+ * // Count active users
680
+ * const activeCount = await db.users.count({ isActive: true });
681
+ *
682
+ * // Count with limit (for existence check optimization)
683
+ * const hasAdmins = await db.users.count({ role: "ADMIN" }, { limit: 1 }) > 0;
684
+ * ```
685
+ */
686
+ async count(where = {}, options) {
687
+ return this.collection.countDocuments(where, options);
688
+ }
689
+ /**
690
+ * Check if any document matches the filter.
691
+ * Optimized to stop after finding first match.
692
+ *
693
+ * @param where - MongoDB filter query
694
+ * @param options - Count options (session)
695
+ * @returns true if at least one document matches
696
+ *
697
+ * @example
698
+ * ```typescript
699
+ * // Check if email is taken
700
+ * const emailTaken = await db.users.exists({ email: "john@example.com" });
701
+ *
702
+ * // Check if user has admin role
703
+ * const isAdmin = await db.users.exists({ _id: userId, role: "ADMIN" });
704
+ * ```
705
+ */
706
+ async exists(where, options) {
707
+ const count = await this.collection.countDocuments(where, { ...options, limit: 1 });
708
+ return count > 0;
709
+ }
710
+ /**
711
+ * Run an aggregation pipeline.
712
+ * Provides full access to MongoDB aggregation framework.
713
+ *
714
+ * @param pipeline - Array of aggregation stages
715
+ * @param options - Aggregation options (allowDiskUse, session)
716
+ * @returns Array of aggregation results
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * // Group users by role and count
721
+ * const stats = await db.users.aggregate([
722
+ * { $match: { isActive: true } },
723
+ * { $group: { _id: "$role", count: { $sum: 1 } } },
724
+ * { $sort: { count: -1 } }
725
+ * ]);
726
+ *
727
+ * // Lookup related documents
728
+ * const usersWithPosts = await db.users.aggregate([
729
+ * { $lookup: {
730
+ * from: "posts",
731
+ * localField: "_id",
732
+ * foreignField: "authorId",
733
+ * as: "posts"
734
+ * }}
735
+ * ]);
736
+ *
737
+ * // With options
738
+ * const bigResult = await db.users.aggregate(pipeline, {
739
+ * allowDiskUse: true
740
+ * });
741
+ * ```
742
+ */
743
+ async aggregate(pipeline, options) {
744
+ return this.collection.aggregate(pipeline, options).toArray();
745
+ }
746
+ /**
747
+ * Get the underlying MongoDB Collection instance.
748
+ * Use for advanced operations not covered by the proxy.
749
+ *
750
+ * @returns MongoDB Collection instance
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * const collection = db.users.getCollection();
755
+ *
756
+ * // Use for watch, bulkWrite, or other advanced operations
757
+ * const changeStream = collection.watch();
758
+ * ```
759
+ */
760
+ getCollection() {
761
+ return this.collection;
762
+ }
763
+ applyTimestamps(data, operation, enabled) {
764
+ if (enabled === false || !this.schema.timestamps) {
765
+ return data;
766
+ }
767
+ const timestamps = this.schema.timestamps;
768
+ const now = /* @__PURE__ */ new Date();
769
+ const result = { ...data };
770
+ if (operation === "create" && timestamps.createdAt) {
771
+ result[timestamps.createdAt] = now;
772
+ }
773
+ if (timestamps.updatedAt) {
774
+ result[timestamps.updatedAt] = now;
775
+ }
776
+ return result;
777
+ }
778
+ parseObjectId(id) {
779
+ return typeof id === "string" ? new mongodb.ObjectId(id) : id;
780
+ }
781
+ applyUpdateTimestamps(data, hasOperators, options) {
782
+ const schemaTimestamps = this.schema.timestamps;
783
+ if (!schemaTimestamps) {
784
+ return hasOperators ? data : { $set: data };
785
+ }
786
+ const timestamps = schemaTimestamps;
787
+ const now = /* @__PURE__ */ new Date();
788
+ const update = hasOperators ? { ...data } : { $set: { ...data } };
789
+ if (timestamps.updatedAt) {
790
+ update.$set = update.$set || {};
791
+ if (!(timestamps.updatedAt in update.$set)) {
792
+ update.$set[timestamps.updatedAt] = now;
793
+ }
794
+ }
795
+ if (options?.upsert && timestamps.createdAt) {
796
+ update.$setOnInsert = update.$setOnInsert || {};
797
+ if (!(timestamps.createdAt in update.$setOnInsert)) {
798
+ update.$setOnInsert[timestamps.createdAt] = now;
799
+ }
800
+ }
801
+ return update;
802
+ }
803
+ };
804
+
805
+ // src/client/client.ts
806
+ async function syncIndexes(db, schemas) {
807
+ for (const schema2 of schemas) {
808
+ const collection = db.collection(schema2.collection);
809
+ const fieldIndexes = getFieldIndexes(schema2);
810
+ for (const { field, options } of fieldIndexes) {
811
+ const indexSpec = {
812
+ [field]: options.type ?? 1
813
+ };
814
+ const indexOptions = {};
815
+ if (options.name) indexOptions.name = options.name;
816
+ if (options.unique) indexOptions.unique = options.unique;
817
+ if (options.sparse) indexOptions.sparse = options.sparse;
818
+ if (options.expireAfterSeconds !== void 0) {
819
+ indexOptions.expireAfterSeconds = options.expireAfterSeconds;
820
+ }
821
+ if (options.partialFilterExpression) {
822
+ indexOptions.partialFilterExpression = options.partialFilterExpression;
823
+ }
824
+ await collection.createIndex(indexSpec, indexOptions);
825
+ }
826
+ for (const index of schema2.indexes) {
827
+ const indexSpec = index.fields;
828
+ const indexOptions = {};
829
+ if (index.options?.name) indexOptions.name = index.options.name;
830
+ if (index.options?.unique) indexOptions.unique = index.options.unique;
831
+ if (index.options?.sparse) indexOptions.sparse = index.options.sparse;
832
+ if (index.options?.partialFilterExpression) {
833
+ indexOptions.partialFilterExpression = index.options.partialFilterExpression;
834
+ }
835
+ if (index.options?.weights) {
836
+ indexOptions.weights = index.options.weights;
837
+ }
838
+ await collection.createIndex(indexSpec, indexOptions);
839
+ }
840
+ }
841
+ }
842
+ function createClientProxy(client, db, schemas, validationMode) {
843
+ const schemaMap = /* @__PURE__ */ new Map();
844
+ const proxyCache = /* @__PURE__ */ new Map();
845
+ for (const schema2 of schemas) {
846
+ schemaMap.set(schema2.name, schema2);
847
+ }
848
+ const baseClient = {
849
+ async close() {
850
+ await client.close();
851
+ },
852
+ getDb() {
853
+ return db;
854
+ },
855
+ startSession(options) {
856
+ return client.startSession(options);
857
+ }
858
+ };
859
+ const IGNORED_PROPERTIES = /* @__PURE__ */ new Set([
860
+ "then",
861
+ "catch",
862
+ "finally",
863
+ "inspect",
864
+ "nodeType",
865
+ "toJSON",
866
+ "constructor",
867
+ "prototype",
868
+ "__proto__"
869
+ ]);
870
+ return new Proxy(baseClient, {
871
+ get(target, prop) {
872
+ if (typeof prop === "symbol") {
873
+ return Reflect.get(target, prop);
874
+ }
875
+ if (IGNORED_PROPERTIES.has(prop)) {
876
+ return void 0;
877
+ }
878
+ if (prop in target) {
879
+ const value = target[prop];
880
+ if (typeof value === "function") {
881
+ return value.bind(target);
882
+ }
883
+ return value;
884
+ }
885
+ const schema2 = schemaMap.get(prop);
886
+ if (schema2) {
887
+ let proxy = proxyCache.get(prop);
888
+ if (!proxy) {
889
+ proxy = new CollectionProxy(db, schema2, validationMode);
890
+ proxyCache.set(prop, proxy);
891
+ }
892
+ return proxy;
893
+ }
894
+ return void 0;
895
+ },
896
+ has(target, prop) {
897
+ if (typeof prop === "symbol") return false;
898
+ if (IGNORED_PROPERTIES.has(prop)) return false;
899
+ return prop in target || schemaMap.has(prop);
900
+ },
901
+ ownKeys(target) {
902
+ const baseKeys = Reflect.ownKeys(target);
903
+ const schemaNames = Array.from(schemaMap.keys());
904
+ return [.../* @__PURE__ */ new Set([...baseKeys, ...schemaNames])];
905
+ }
906
+ });
907
+ }
908
+ function createClient(config) {
909
+ const { schemas, syncIndexes: shouldSyncIndexes = false, validation = "strict" } = config;
910
+ if (config.serverless === true) {
911
+ return async (uri, options) => {
912
+ const client = new mongodb.MongoClient(uri, options);
913
+ await client.connect();
914
+ const db = client.db();
915
+ if (shouldSyncIndexes) {
916
+ await syncIndexes(db, schemas);
917
+ }
918
+ return createClientProxy(client, db, schemas, validation);
919
+ };
920
+ } else {
921
+ return (async () => {
922
+ const client = new mongodb.MongoClient(config.uri, config.options);
923
+ await client.connect();
924
+ const db = client.db();
925
+ if (shouldSyncIndexes) {
926
+ await syncIndexes(db, schemas);
927
+ }
928
+ return createClientProxy(client, db, schemas, validation);
929
+ })();
930
+ }
931
+ }
932
+
933
+ Object.defineProperty(exports, "ObjectId", {
934
+ enumerable: true,
935
+ get: function () { return mongodb.ObjectId; }
936
+ });
937
+ exports.CollectionProxy = CollectionProxy;
938
+ exports.createClient = createClient;
939
+ exports.defineSchema = defineSchema;
940
+ exports.s = s;
941
+ exports.schema = schema;
942
+ exports.validate = validate;
943
+ exports.zodCreateSchema = zodCreateSchema;
944
+ exports.zodSchema = zodSchema;
945
+ exports.zodUpdateSchema = zodUpdateSchema;
946
+ //# sourceMappingURL=index.js.map
947
+ //# sourceMappingURL=index.js.map