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/LICENSE +21 -0
- package/README.md +385 -0
- package/dist/index.d.mts +888 -0
- package/dist/index.d.ts +888 -0
- package/dist/index.js +947 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +934 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
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
|