@zodmon/core 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +703 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1375 -444
- package/dist/index.d.ts +1375 -444
- package/dist/index.js +686 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
$: () => $,
|
|
23
24
|
$and: () => $and,
|
|
24
25
|
$eq: () => $eq,
|
|
25
26
|
$exists: () => $exists,
|
|
@@ -40,43 +41,205 @@ __export(index_exports, {
|
|
|
40
41
|
TypedFindCursor: () => TypedFindCursor,
|
|
41
42
|
ZodmonNotFoundError: () => ZodmonNotFoundError,
|
|
42
43
|
ZodmonValidationError: () => ZodmonValidationError,
|
|
44
|
+
checkUnindexedFields: () => checkUnindexedFields,
|
|
43
45
|
collection: () => collection,
|
|
44
46
|
createClient: () => createClient,
|
|
47
|
+
deleteMany: () => deleteMany,
|
|
48
|
+
deleteOne: () => deleteOne,
|
|
49
|
+
extractComparableOptions: () => extractComparableOptions,
|
|
45
50
|
extractDbName: () => extractDbName,
|
|
46
51
|
extractFieldIndexes: () => extractFieldIndexes,
|
|
47
52
|
find: () => find,
|
|
48
53
|
findOne: () => findOne,
|
|
54
|
+
findOneAndDelete: () => findOneAndDelete,
|
|
55
|
+
findOneAndUpdate: () => findOneAndUpdate,
|
|
49
56
|
findOneOrThrow: () => findOneOrThrow,
|
|
57
|
+
generateIndexName: () => generateIndexName,
|
|
50
58
|
getIndexMetadata: () => getIndexMetadata,
|
|
51
59
|
getRefMetadata: () => getRefMetadata,
|
|
52
60
|
index: () => index,
|
|
53
61
|
insertMany: () => insertMany,
|
|
54
62
|
insertOne: () => insertOne,
|
|
55
|
-
installExtensions: () => installExtensions,
|
|
56
|
-
installRefExtension: () => installRefExtension,
|
|
57
63
|
isOid: () => isOid,
|
|
58
64
|
objectId: () => objectId,
|
|
59
65
|
oid: () => oid,
|
|
60
|
-
raw: () => raw
|
|
66
|
+
raw: () => raw,
|
|
67
|
+
serializeIndexKey: () => serializeIndexKey,
|
|
68
|
+
syncIndexes: () => syncIndexes,
|
|
69
|
+
toCompoundIndexSpec: () => toCompoundIndexSpec,
|
|
70
|
+
toFieldIndexSpec: () => toFieldIndexSpec,
|
|
71
|
+
updateMany: () => updateMany,
|
|
72
|
+
updateOne: () => updateOne
|
|
61
73
|
});
|
|
62
74
|
module.exports = __toCommonJS(index_exports);
|
|
63
75
|
|
|
64
76
|
// src/client/client.ts
|
|
65
|
-
var
|
|
77
|
+
var import_mongodb2 = require("mongodb");
|
|
66
78
|
|
|
67
|
-
// src/
|
|
68
|
-
|
|
79
|
+
// src/indexes/spec.ts
|
|
80
|
+
function toFieldIndexSpec(def) {
|
|
81
|
+
const direction = def.text ? "text" : def.descending ? -1 : 1;
|
|
82
|
+
const key = { [def.field]: direction };
|
|
83
|
+
const options = {};
|
|
84
|
+
if (def.unique) options["unique"] = true;
|
|
85
|
+
if (def.sparse) options["sparse"] = true;
|
|
86
|
+
if (def.expireAfter !== void 0) options["expireAfterSeconds"] = def.expireAfter;
|
|
87
|
+
if (def.partial) options["partialFilterExpression"] = def.partial;
|
|
88
|
+
return { key, options };
|
|
89
|
+
}
|
|
90
|
+
function toCompoundIndexSpec(def) {
|
|
91
|
+
const key = { ...def.fields };
|
|
92
|
+
const options = {};
|
|
93
|
+
if (def.options?.unique) options["unique"] = true;
|
|
94
|
+
if (def.options?.sparse) options["sparse"] = true;
|
|
95
|
+
if (def.options?.name) options["name"] = def.options.name;
|
|
96
|
+
if (def.options?.partial) options["partialFilterExpression"] = def.options.partial;
|
|
97
|
+
return { key, options };
|
|
98
|
+
}
|
|
99
|
+
function serializeIndexKey(key) {
|
|
100
|
+
return Object.entries(key).map(([field, dir]) => `${field}:${dir}`).join(",");
|
|
101
|
+
}
|
|
69
102
|
|
|
70
|
-
// src/
|
|
71
|
-
var
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
// src/indexes/sync.ts
|
|
104
|
+
var COMPARABLE_OPTION_KEYS = [
|
|
105
|
+
"unique",
|
|
106
|
+
"sparse",
|
|
107
|
+
"expireAfterSeconds",
|
|
108
|
+
"partialFilterExpression"
|
|
109
|
+
];
|
|
110
|
+
function extractComparableOptions(info) {
|
|
111
|
+
const result = {};
|
|
112
|
+
for (const key of COMPARABLE_OPTION_KEYS) {
|
|
113
|
+
if (info[key] !== void 0) {
|
|
114
|
+
result[key] = info[key];
|
|
115
|
+
}
|
|
78
116
|
}
|
|
79
|
-
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function generateIndexName(key) {
|
|
120
|
+
return Object.entries(key).map(([field, dir]) => `${field}_${dir}`).join("_");
|
|
121
|
+
}
|
|
122
|
+
function sortKeys(obj) {
|
|
123
|
+
const sorted = {};
|
|
124
|
+
for (const key of Object.keys(obj).sort()) {
|
|
125
|
+
sorted[key] = obj[key];
|
|
126
|
+
}
|
|
127
|
+
return sorted;
|
|
128
|
+
}
|
|
129
|
+
function resolveSpecName(spec) {
|
|
130
|
+
const specName = spec.options["name"];
|
|
131
|
+
return typeof specName === "string" ? specName : generateIndexName(spec.key);
|
|
132
|
+
}
|
|
133
|
+
function resolveExistingName(info, key) {
|
|
134
|
+
const infoName = info["name"];
|
|
135
|
+
if (typeof infoName === "string") return infoName;
|
|
136
|
+
return generateIndexName(key);
|
|
137
|
+
}
|
|
138
|
+
function optionsMatch(a, b) {
|
|
139
|
+
return JSON.stringify(sortKeys(stripName(a))) === JSON.stringify(sortKeys(stripName(b)));
|
|
140
|
+
}
|
|
141
|
+
function stripName(obj) {
|
|
142
|
+
const { name: _, ...rest } = obj;
|
|
143
|
+
return rest;
|
|
144
|
+
}
|
|
145
|
+
async function processDesiredSpec(spec, existingByKey, native, dryRun, dropOrphaned, acc) {
|
|
146
|
+
const serialized = serializeIndexKey(spec.key);
|
|
147
|
+
const existing = existingByKey.get(serialized);
|
|
148
|
+
if (!existing) {
|
|
149
|
+
if (!dryRun) await native.createIndex(spec.key, spec.options);
|
|
150
|
+
acc.created.push(resolveSpecName(spec));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
acc.matchedKeys.add(serialized);
|
|
154
|
+
const existingName = resolveExistingName(existing, spec.key);
|
|
155
|
+
const existingOpts = extractComparableOptions(existing);
|
|
156
|
+
if (optionsMatch(existingOpts, spec.options)) {
|
|
157
|
+
acc.skipped.push(existingName);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (dropOrphaned) {
|
|
161
|
+
if (!dryRun) {
|
|
162
|
+
await native.dropIndex(existingName);
|
|
163
|
+
await native.createIndex(spec.key, spec.options);
|
|
164
|
+
}
|
|
165
|
+
acc.dropped.push(existingName);
|
|
166
|
+
acc.created.push(resolveSpecName(spec));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
acc.stale.push({
|
|
170
|
+
name: existingName,
|
|
171
|
+
key: spec.key,
|
|
172
|
+
existing: existingOpts,
|
|
173
|
+
desired: spec.options
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async function processOrphanedIndexes(existingIndexes, desiredKeys, matchedKeys, native, dryRun, dropOrphaned, dropped) {
|
|
177
|
+
for (const idx of existingIndexes) {
|
|
178
|
+
const rawName = idx["name"];
|
|
179
|
+
const name = typeof rawName === "string" ? rawName : "";
|
|
180
|
+
if (name === "_id_") continue;
|
|
181
|
+
const serialized = serializeIndexKey(idx["key"]);
|
|
182
|
+
if (matchedKeys.has(serialized) || desiredKeys.has(serialized)) continue;
|
|
183
|
+
if (dropOrphaned) {
|
|
184
|
+
if (!dryRun) await native.dropIndex(name);
|
|
185
|
+
dropped.push(name);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function listIndexesSafe(native) {
|
|
190
|
+
try {
|
|
191
|
+
return await native.listIndexes().toArray();
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err instanceof Error && err.message.includes("ns does not exist")) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function syncIndexes(handle, options) {
|
|
200
|
+
const { dryRun = false, dropOrphaned = false } = options ?? {};
|
|
201
|
+
const native = handle.native;
|
|
202
|
+
const def = handle.definition;
|
|
203
|
+
const desiredSpecs = [
|
|
204
|
+
...def.fieldIndexes.map(toFieldIndexSpec),
|
|
205
|
+
...def.compoundIndexes.map(toCompoundIndexSpec)
|
|
206
|
+
];
|
|
207
|
+
const existingIndexes = await listIndexesSafe(native);
|
|
208
|
+
const existingByKey = /* @__PURE__ */ new Map();
|
|
209
|
+
for (const idx of existingIndexes) {
|
|
210
|
+
const serialized = serializeIndexKey(idx["key"]);
|
|
211
|
+
existingByKey.set(serialized, idx);
|
|
212
|
+
}
|
|
213
|
+
const acc = {
|
|
214
|
+
created: [],
|
|
215
|
+
dropped: [],
|
|
216
|
+
skipped: [],
|
|
217
|
+
stale: [],
|
|
218
|
+
matchedKeys: /* @__PURE__ */ new Set()
|
|
219
|
+
};
|
|
220
|
+
for (const spec of desiredSpecs) {
|
|
221
|
+
await processDesiredSpec(spec, existingByKey, native, dryRun, dropOrphaned, acc);
|
|
222
|
+
}
|
|
223
|
+
const desiredKeys = new Set(desiredSpecs.map((s) => serializeIndexKey(s.key)));
|
|
224
|
+
await processOrphanedIndexes(
|
|
225
|
+
existingIndexes,
|
|
226
|
+
desiredKeys,
|
|
227
|
+
acc.matchedKeys,
|
|
228
|
+
native,
|
|
229
|
+
dryRun,
|
|
230
|
+
dropOrphaned,
|
|
231
|
+
acc.dropped
|
|
232
|
+
);
|
|
233
|
+
return {
|
|
234
|
+
created: acc.created,
|
|
235
|
+
dropped: acc.dropped,
|
|
236
|
+
skipped: acc.skipped,
|
|
237
|
+
stale: acc.stale
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/crud/delete.ts
|
|
242
|
+
var import_zod = require("zod");
|
|
80
243
|
|
|
81
244
|
// src/errors/validation.ts
|
|
82
245
|
var ZodmonValidationError = class extends Error {
|
|
@@ -96,8 +259,136 @@ var ZodmonValidationError = class extends Error {
|
|
|
96
259
|
}
|
|
97
260
|
};
|
|
98
261
|
|
|
262
|
+
// src/crud/delete.ts
|
|
263
|
+
async function deleteOne(handle, filter) {
|
|
264
|
+
return await handle.native.deleteOne(filter);
|
|
265
|
+
}
|
|
266
|
+
async function deleteMany(handle, filter) {
|
|
267
|
+
return await handle.native.deleteMany(filter);
|
|
268
|
+
}
|
|
269
|
+
async function findOneAndDelete(handle, filter, options) {
|
|
270
|
+
const result = await handle.native.findOneAndDelete(
|
|
271
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
272
|
+
filter,
|
|
273
|
+
{ includeResultMetadata: false }
|
|
274
|
+
);
|
|
275
|
+
if (!result) return null;
|
|
276
|
+
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
277
|
+
if (mode === false || mode === "passthrough") {
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
return handle.definition.schema.parse(result);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (err instanceof import_zod.z.ZodError) {
|
|
284
|
+
throw new ZodmonValidationError(handle.definition.name, err);
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/crud/find.ts
|
|
291
|
+
var import_zod3 = require("zod");
|
|
292
|
+
|
|
293
|
+
// src/errors/not-found.ts
|
|
294
|
+
var ZodmonNotFoundError = class extends Error {
|
|
295
|
+
name = "ZodmonNotFoundError";
|
|
296
|
+
/** The MongoDB collection name where the query found no results. */
|
|
297
|
+
collection;
|
|
298
|
+
constructor(collection2) {
|
|
299
|
+
super(`Document not found in "${collection2}"`);
|
|
300
|
+
this.collection = collection2;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// src/indexes/warn.ts
|
|
305
|
+
var SKIP_OPERATORS = /* @__PURE__ */ new Set(["$or", "$and", "$nor", "$text", "$where", "$expr", "$comment"]);
|
|
306
|
+
function checkUnindexedFields(definition, filter) {
|
|
307
|
+
if (definition.options.warnUnindexedQueries !== true) return;
|
|
308
|
+
const covered = /* @__PURE__ */ new Set();
|
|
309
|
+
for (const fi of definition.fieldIndexes) {
|
|
310
|
+
covered.add(fi.field);
|
|
311
|
+
}
|
|
312
|
+
for (const ci of definition.compoundIndexes) {
|
|
313
|
+
const firstField = Object.keys(ci.fields)[0];
|
|
314
|
+
if (firstField !== void 0) {
|
|
315
|
+
covered.add(firstField);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (const key of Object.keys(filter)) {
|
|
319
|
+
if (key === "_id") continue;
|
|
320
|
+
if (SKIP_OPERATORS.has(key)) continue;
|
|
321
|
+
if (!covered.has(key)) {
|
|
322
|
+
console.warn(`[zodmon] warn: query on '${definition.name}' uses unindexed field '${key}'`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/query/cursor.ts
|
|
328
|
+
var import_zod2 = require("zod");
|
|
329
|
+
|
|
330
|
+
// src/crud/paginate.ts
|
|
331
|
+
var import_mongodb = require("mongodb");
|
|
332
|
+
function serializeValue(value) {
|
|
333
|
+
if (value instanceof import_mongodb.ObjectId) return { $oid: value.toHexString() };
|
|
334
|
+
if (value instanceof Date) return { $date: value.getTime() };
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
function deserializeValue(value) {
|
|
338
|
+
if (value != null && typeof value === "object") {
|
|
339
|
+
if ("$oid" in value) return new import_mongodb.ObjectId(value.$oid);
|
|
340
|
+
if ("$date" in value) return new Date(value.$date);
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
}
|
|
344
|
+
function encodeCursor(doc, sortKeys2, direction) {
|
|
345
|
+
const values = sortKeys2.map(([field]) => serializeValue(doc[field]));
|
|
346
|
+
return btoa(JSON.stringify([direction, ...values]));
|
|
347
|
+
}
|
|
348
|
+
function decodeCursor(cursor) {
|
|
349
|
+
let parsed;
|
|
350
|
+
try {
|
|
351
|
+
parsed = JSON.parse(atob(cursor));
|
|
352
|
+
} catch {
|
|
353
|
+
throw new Error("Invalid cursor: malformed encoding");
|
|
354
|
+
}
|
|
355
|
+
if (!Array.isArray(parsed) || parsed.length < 1) {
|
|
356
|
+
throw new Error("Invalid cursor: expected non-empty array");
|
|
357
|
+
}
|
|
358
|
+
const [direction, ...rawValues] = parsed;
|
|
359
|
+
if (direction !== "f" && direction !== "b") {
|
|
360
|
+
throw new Error("Invalid cursor: unknown direction flag");
|
|
361
|
+
}
|
|
362
|
+
return { direction, values: rawValues.map(deserializeValue) };
|
|
363
|
+
}
|
|
364
|
+
function buildCursorFilter(sortKeys2, values, isBackward) {
|
|
365
|
+
const clauses = [];
|
|
366
|
+
for (let i = 0; i < sortKeys2.length; i++) {
|
|
367
|
+
const clause = {};
|
|
368
|
+
for (let j = 0; j < i; j++) {
|
|
369
|
+
clause[sortKeys2[j][0]] = values[j];
|
|
370
|
+
}
|
|
371
|
+
const [field, direction] = sortKeys2[i];
|
|
372
|
+
const isAsc = direction === 1;
|
|
373
|
+
const op = isAsc !== isBackward ? "$gt" : "$lt";
|
|
374
|
+
if (values[i] === null) {
|
|
375
|
+
clause[field] = { $ne: null };
|
|
376
|
+
} else {
|
|
377
|
+
clause[field] = { [op]: values[i] };
|
|
378
|
+
}
|
|
379
|
+
clauses.push(clause);
|
|
380
|
+
}
|
|
381
|
+
return { $or: clauses };
|
|
382
|
+
}
|
|
383
|
+
function resolveSortKeys(sortSpec) {
|
|
384
|
+
const entries = sortSpec ? Object.entries(sortSpec) : [];
|
|
385
|
+
if (!entries.some(([field]) => field === "_id")) {
|
|
386
|
+
entries.push(["_id", 1]);
|
|
387
|
+
}
|
|
388
|
+
return entries;
|
|
389
|
+
}
|
|
390
|
+
|
|
99
391
|
// src/query/cursor.ts
|
|
100
|
-
var import_zod = require("zod");
|
|
101
392
|
var TypedFindCursor = class {
|
|
102
393
|
/** @internal */
|
|
103
394
|
cursor;
|
|
@@ -108,11 +399,21 @@ var TypedFindCursor = class {
|
|
|
108
399
|
/** @internal */
|
|
109
400
|
mode;
|
|
110
401
|
/** @internal */
|
|
111
|
-
|
|
402
|
+
nativeCollection;
|
|
403
|
+
/** @internal */
|
|
404
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter is not assignable to MongoDB's Filter; stored opaquely for paginate
|
|
405
|
+
filter;
|
|
406
|
+
/** @internal */
|
|
407
|
+
sortSpec;
|
|
408
|
+
/** @internal */
|
|
409
|
+
constructor(cursor, definition, mode, nativeCollection, filter) {
|
|
112
410
|
this.cursor = cursor;
|
|
113
411
|
this.schema = definition.schema;
|
|
114
412
|
this.collectionName = definition.name;
|
|
115
413
|
this.mode = mode;
|
|
414
|
+
this.nativeCollection = nativeCollection;
|
|
415
|
+
this.filter = filter;
|
|
416
|
+
this.sortSpec = null;
|
|
116
417
|
}
|
|
117
418
|
/**
|
|
118
419
|
* Set the sort order for the query.
|
|
@@ -129,6 +430,7 @@ var TypedFindCursor = class {
|
|
|
129
430
|
* ```
|
|
130
431
|
*/
|
|
131
432
|
sort(spec) {
|
|
433
|
+
this.sortSpec = spec;
|
|
132
434
|
this.cursor.sort(spec);
|
|
133
435
|
return this;
|
|
134
436
|
}
|
|
@@ -162,6 +464,80 @@ var TypedFindCursor = class {
|
|
|
162
464
|
this.cursor.limit(n);
|
|
163
465
|
return this;
|
|
164
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Force the query optimizer to use the specified index.
|
|
469
|
+
*
|
|
470
|
+
* Only accepts index names that were declared via `.name()` in the
|
|
471
|
+
* collection definition. If no named indexes exist, any string is accepted.
|
|
472
|
+
*
|
|
473
|
+
* @param indexName - The name of a declared compound index.
|
|
474
|
+
* @returns `this` for chaining.
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```ts
|
|
478
|
+
* const Users = collection('users', { email: z.string(), role: z.string() }, {
|
|
479
|
+
* indexes: [index({ email: 1, role: -1 }).name('email_role_idx')],
|
|
480
|
+
* })
|
|
481
|
+
* const admins = await users.find({ role: 'admin' })
|
|
482
|
+
* .hint('email_role_idx')
|
|
483
|
+
* .toArray()
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
hint(indexName) {
|
|
487
|
+
this.cursor.hint(indexName);
|
|
488
|
+
return this;
|
|
489
|
+
}
|
|
490
|
+
async paginate(opts) {
|
|
491
|
+
const sortRecord = this.sortSpec ? this.sortSpec : null;
|
|
492
|
+
const sortKeys2 = resolveSortKeys(sortRecord);
|
|
493
|
+
const sort = Object.fromEntries(sortKeys2);
|
|
494
|
+
if ("page" in opts) {
|
|
495
|
+
return await this.offsetPaginate(sortKeys2, sort, opts);
|
|
496
|
+
}
|
|
497
|
+
return await this.cursorPaginate(sortKeys2, sort, opts);
|
|
498
|
+
}
|
|
499
|
+
/** @internal Offset pagination implementation. */
|
|
500
|
+
async offsetPaginate(_sortKeys, sort, opts) {
|
|
501
|
+
const [total, raw2] = await Promise.all([
|
|
502
|
+
this.nativeCollection.countDocuments(this.filter),
|
|
503
|
+
this.nativeCollection.find(this.filter).sort(sort).skip((opts.page - 1) * opts.perPage).limit(opts.perPage).toArray()
|
|
504
|
+
]);
|
|
505
|
+
const docs = raw2.map((doc) => this.validateDoc(doc));
|
|
506
|
+
const totalPages = Math.ceil(total / opts.perPage);
|
|
507
|
+
return {
|
|
508
|
+
docs,
|
|
509
|
+
total,
|
|
510
|
+
page: opts.page,
|
|
511
|
+
perPage: opts.perPage,
|
|
512
|
+
totalPages,
|
|
513
|
+
hasNext: opts.page < totalPages,
|
|
514
|
+
hasPrev: opts.page > 1
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
/** @internal Cursor pagination implementation. */
|
|
518
|
+
async cursorPaginate(sortKeys2, sort, opts) {
|
|
519
|
+
let isBackward = false;
|
|
520
|
+
let combinedFilter = this.filter;
|
|
521
|
+
if (opts.cursor) {
|
|
522
|
+
const decoded = decodeCursor(opts.cursor);
|
|
523
|
+
isBackward = decoded.direction === "b";
|
|
524
|
+
const cursorFilter = buildCursorFilter(sortKeys2, decoded.values, isBackward);
|
|
525
|
+
combinedFilter = this.filter && Object.keys(this.filter).length > 0 ? { $and: [this.filter, cursorFilter] } : cursorFilter;
|
|
526
|
+
}
|
|
527
|
+
const effectiveSort = isBackward ? Object.fromEntries(sortKeys2.map(([f, d]) => [f, d === 1 ? -1 : 1])) : sort;
|
|
528
|
+
const raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
|
|
529
|
+
const hasMore = raw2.length > opts.limit;
|
|
530
|
+
if (hasMore) raw2.pop();
|
|
531
|
+
if (isBackward) raw2.reverse();
|
|
532
|
+
const docs = raw2.map((doc) => this.validateDoc(doc));
|
|
533
|
+
return {
|
|
534
|
+
docs,
|
|
535
|
+
hasNext: isBackward ? true : hasMore,
|
|
536
|
+
hasPrev: isBackward ? hasMore : opts.cursor != null,
|
|
537
|
+
startCursor: docs.length > 0 ? encodeCursor(docs[0], sortKeys2, "b") : null,
|
|
538
|
+
endCursor: docs.length > 0 ? encodeCursor(docs[docs.length - 1], sortKeys2, "f") : null
|
|
539
|
+
};
|
|
540
|
+
}
|
|
165
541
|
/**
|
|
166
542
|
* Execute the query and return all matching documents as an array.
|
|
167
543
|
*
|
|
@@ -209,7 +585,7 @@ var TypedFindCursor = class {
|
|
|
209
585
|
try {
|
|
210
586
|
return this.schema.parse(raw2);
|
|
211
587
|
} catch (err) {
|
|
212
|
-
if (err instanceof
|
|
588
|
+
if (err instanceof import_zod2.z.ZodError) {
|
|
213
589
|
throw new ZodmonValidationError(this.collectionName, err);
|
|
214
590
|
}
|
|
215
591
|
throw err;
|
|
@@ -219,6 +595,7 @@ var TypedFindCursor = class {
|
|
|
219
595
|
|
|
220
596
|
// src/crud/find.ts
|
|
221
597
|
async function findOne(handle, filter, options) {
|
|
598
|
+
checkUnindexedFields(handle.definition, filter);
|
|
222
599
|
const findOptions = options?.project ? { projection: options.project } : void 0;
|
|
223
600
|
const raw2 = await handle.native.findOne(filter, findOptions);
|
|
224
601
|
if (!raw2) return null;
|
|
@@ -229,7 +606,7 @@ async function findOne(handle, filter, options) {
|
|
|
229
606
|
try {
|
|
230
607
|
return handle.definition.schema.parse(raw2);
|
|
231
608
|
} catch (err) {
|
|
232
|
-
if (err instanceof
|
|
609
|
+
if (err instanceof import_zod3.z.ZodError) {
|
|
233
610
|
throw new ZodmonValidationError(handle.definition.name, err);
|
|
234
611
|
}
|
|
235
612
|
throw err;
|
|
@@ -243,20 +620,21 @@ async function findOneOrThrow(handle, filter, options) {
|
|
|
243
620
|
return doc;
|
|
244
621
|
}
|
|
245
622
|
function find(handle, filter, options) {
|
|
623
|
+
checkUnindexedFields(handle.definition, filter);
|
|
246
624
|
const raw2 = handle.native.find(filter);
|
|
247
625
|
const cursor = raw2;
|
|
248
626
|
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
249
|
-
return new TypedFindCursor(cursor, handle.definition, mode);
|
|
627
|
+
return new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
|
|
250
628
|
}
|
|
251
629
|
|
|
252
630
|
// src/crud/insert.ts
|
|
253
|
-
var
|
|
631
|
+
var import_zod4 = require("zod");
|
|
254
632
|
async function insertOne(handle, doc) {
|
|
255
633
|
let parsed;
|
|
256
634
|
try {
|
|
257
635
|
parsed = handle.definition.schema.parse(doc);
|
|
258
636
|
} catch (err) {
|
|
259
|
-
if (err instanceof
|
|
637
|
+
if (err instanceof import_zod4.z.ZodError) {
|
|
260
638
|
throw new ZodmonValidationError(handle.definition.name, err);
|
|
261
639
|
}
|
|
262
640
|
throw err;
|
|
@@ -271,7 +649,7 @@ async function insertMany(handle, docs) {
|
|
|
271
649
|
try {
|
|
272
650
|
parsed.push(handle.definition.schema.parse(doc));
|
|
273
651
|
} catch (err) {
|
|
274
|
-
if (err instanceof
|
|
652
|
+
if (err instanceof import_zod4.z.ZodError) {
|
|
275
653
|
throw new ZodmonValidationError(handle.definition.name, err);
|
|
276
654
|
}
|
|
277
655
|
throw err;
|
|
@@ -281,6 +659,45 @@ async function insertMany(handle, docs) {
|
|
|
281
659
|
return parsed;
|
|
282
660
|
}
|
|
283
661
|
|
|
662
|
+
// src/crud/update.ts
|
|
663
|
+
var import_zod5 = require("zod");
|
|
664
|
+
async function updateOne(handle, filter, update, options) {
|
|
665
|
+
return await handle.native.updateOne(filter, update, options);
|
|
666
|
+
}
|
|
667
|
+
async function updateMany(handle, filter, update, options) {
|
|
668
|
+
return await handle.native.updateMany(filter, update, options);
|
|
669
|
+
}
|
|
670
|
+
async function findOneAndUpdate(handle, filter, update, options) {
|
|
671
|
+
const driverOptions = {
|
|
672
|
+
returnDocument: options?.returnDocument ?? "after",
|
|
673
|
+
includeResultMetadata: false
|
|
674
|
+
};
|
|
675
|
+
if (options?.upsert !== void 0) {
|
|
676
|
+
driverOptions["upsert"] = options.upsert;
|
|
677
|
+
}
|
|
678
|
+
const result = await handle.native.findOneAndUpdate(
|
|
679
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedFilter intersection type is not directly assignable to MongoDB's Filter
|
|
680
|
+
filter,
|
|
681
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypedUpdateFilter intersection type is not directly assignable to MongoDB's UpdateFilter
|
|
682
|
+
update,
|
|
683
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic options object is not assignable to driver's FindOneAndUpdateOptions under exactOptionalPropertyTypes
|
|
684
|
+
driverOptions
|
|
685
|
+
);
|
|
686
|
+
if (!result) return null;
|
|
687
|
+
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
688
|
+
if (mode === false || mode === "passthrough") {
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
return handle.definition.schema.parse(result);
|
|
693
|
+
} catch (err) {
|
|
694
|
+
if (err instanceof import_zod5.z.ZodError) {
|
|
695
|
+
throw new ZodmonValidationError(handle.definition.name, err);
|
|
696
|
+
}
|
|
697
|
+
throw err;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
284
701
|
// src/client/handle.ts
|
|
285
702
|
var CollectionHandle = class {
|
|
286
703
|
/** The collection definition containing schema, name, and index metadata. */
|
|
@@ -402,6 +819,169 @@ var CollectionHandle = class {
|
|
|
402
819
|
find(filter, options) {
|
|
403
820
|
return find(this, filter, options);
|
|
404
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Update a single document matching the filter.
|
|
824
|
+
*
|
|
825
|
+
* Applies the update operators to the first document that matches the filter.
|
|
826
|
+
* Does not validate the update against the Zod schema — validation happens
|
|
827
|
+
* at the field-operator level through {@link TypedUpdateFilter}.
|
|
828
|
+
*
|
|
829
|
+
* @param filter - Type-safe filter to match documents.
|
|
830
|
+
* @param update - Type-safe update operators to apply.
|
|
831
|
+
* @param options - Optional settings such as `upsert`.
|
|
832
|
+
* @returns The MongoDB `UpdateResult` with match/modify counts.
|
|
833
|
+
*
|
|
834
|
+
* @example
|
|
835
|
+
* ```ts
|
|
836
|
+
* const users = db.use(Users)
|
|
837
|
+
* const result = await users.updateOne({ name: 'Ada' }, { $set: { role: 'admin' } })
|
|
838
|
+
* console.log(result.modifiedCount) // 1
|
|
839
|
+
* ```
|
|
840
|
+
*/
|
|
841
|
+
async updateOne(filter, update, options) {
|
|
842
|
+
return await updateOne(this, filter, update, options);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Update all documents matching the filter.
|
|
846
|
+
*
|
|
847
|
+
* Applies the update operators to every document that matches the filter.
|
|
848
|
+
* Does not validate the update against the Zod schema — validation happens
|
|
849
|
+
* at the field-operator level through {@link TypedUpdateFilter}.
|
|
850
|
+
*
|
|
851
|
+
* @param filter - Type-safe filter to match documents.
|
|
852
|
+
* @param update - Type-safe update operators to apply.
|
|
853
|
+
* @param options - Optional settings such as `upsert`.
|
|
854
|
+
* @returns The MongoDB `UpdateResult` with match/modify counts.
|
|
855
|
+
*
|
|
856
|
+
* @example
|
|
857
|
+
* ```ts
|
|
858
|
+
* const users = db.use(Users)
|
|
859
|
+
* const result = await users.updateMany({ role: 'guest' }, { $set: { role: 'user' } })
|
|
860
|
+
* console.log(result.modifiedCount) // number of guests promoted
|
|
861
|
+
* ```
|
|
862
|
+
*/
|
|
863
|
+
async updateMany(filter, update, options) {
|
|
864
|
+
return await updateMany(this, filter, update, options);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Find a single document matching the filter, apply an update, and return the document.
|
|
868
|
+
*
|
|
869
|
+
* By default, returns the document **after** the update is applied. Set
|
|
870
|
+
* `returnDocument: 'before'` to get the pre-update snapshot. The returned
|
|
871
|
+
* document is validated against the collection's Zod schema using the same
|
|
872
|
+
* resolution logic as {@link findOne}.
|
|
873
|
+
*
|
|
874
|
+
* @param filter - Type-safe filter to match documents.
|
|
875
|
+
* @param update - Type-safe update operators to apply.
|
|
876
|
+
* @param options - Optional settings: `returnDocument`, `upsert`, `validate`.
|
|
877
|
+
* @returns The matched document (before or after update), or `null` if no document matches.
|
|
878
|
+
* @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
|
|
879
|
+
*
|
|
880
|
+
* @example
|
|
881
|
+
* ```ts
|
|
882
|
+
* const users = db.use(Users)
|
|
883
|
+
* const user = await users.findOneAndUpdate(
|
|
884
|
+
* { name: 'Ada' },
|
|
885
|
+
* { $set: { role: 'admin' } },
|
|
886
|
+
* )
|
|
887
|
+
* if (user) console.log(user.role) // 'admin' (returned after update)
|
|
888
|
+
* ```
|
|
889
|
+
*/
|
|
890
|
+
async findOneAndUpdate(filter, update, options) {
|
|
891
|
+
return await findOneAndUpdate(this, filter, update, options);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Delete a single document matching the filter.
|
|
895
|
+
*
|
|
896
|
+
* Removes the first document that matches the filter from the collection.
|
|
897
|
+
* No validation is performed — the document is deleted directly through
|
|
898
|
+
* the MongoDB driver.
|
|
899
|
+
*
|
|
900
|
+
* @param filter - Type-safe filter to match documents.
|
|
901
|
+
* @returns The MongoDB `DeleteResult` with the deleted count.
|
|
902
|
+
*
|
|
903
|
+
* @example
|
|
904
|
+
* ```ts
|
|
905
|
+
* const users = db.use(Users)
|
|
906
|
+
* const result = await users.deleteOne({ name: 'Ada' })
|
|
907
|
+
* console.log(result.deletedCount) // 1
|
|
908
|
+
* ```
|
|
909
|
+
*/
|
|
910
|
+
async deleteOne(filter) {
|
|
911
|
+
return await deleteOne(this, filter);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Delete all documents matching the filter.
|
|
915
|
+
*
|
|
916
|
+
* Removes every document that matches the filter from the collection.
|
|
917
|
+
* No validation is performed — documents are deleted directly through
|
|
918
|
+
* the MongoDB driver.
|
|
919
|
+
*
|
|
920
|
+
* @param filter - Type-safe filter to match documents.
|
|
921
|
+
* @returns The MongoDB `DeleteResult` with the deleted count.
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* ```ts
|
|
925
|
+
* const users = db.use(Users)
|
|
926
|
+
* const result = await users.deleteMany({ role: 'guest' })
|
|
927
|
+
* console.log(result.deletedCount) // number of guests removed
|
|
928
|
+
* ```
|
|
929
|
+
*/
|
|
930
|
+
async deleteMany(filter) {
|
|
931
|
+
return await deleteMany(this, filter);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Find a single document matching the filter, delete it, and return the document.
|
|
935
|
+
*
|
|
936
|
+
* Returns the deleted document, or `null` if no document matches the filter.
|
|
937
|
+
* The returned document is validated against the collection's Zod schema
|
|
938
|
+
* using the same resolution logic as {@link findOne}.
|
|
939
|
+
*
|
|
940
|
+
* @param filter - Type-safe filter to match documents.
|
|
941
|
+
* @param options - Optional settings: `validate`.
|
|
942
|
+
* @returns The deleted document, or `null` if no document matches.
|
|
943
|
+
* @throws {ZodmonValidationError} When the returned document fails schema validation in strict mode.
|
|
944
|
+
*
|
|
945
|
+
* @example
|
|
946
|
+
* ```ts
|
|
947
|
+
* const users = db.use(Users)
|
|
948
|
+
* const user = await users.findOneAndDelete({ name: 'Ada' })
|
|
949
|
+
* if (user) console.log(user.name) // 'Ada' (the deleted document)
|
|
950
|
+
* ```
|
|
951
|
+
*/
|
|
952
|
+
async findOneAndDelete(filter, options) {
|
|
953
|
+
return await findOneAndDelete(this, filter, options);
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Synchronize the indexes declared in this collection's schema with MongoDB.
|
|
957
|
+
*
|
|
958
|
+
* Compares the desired indexes (from field-level `.index()` / `.unique()` /
|
|
959
|
+
* `.text()` / `.expireAfter()` and compound `indexes` in collection options)
|
|
960
|
+
* with the indexes that currently exist in MongoDB, then creates, drops, or
|
|
961
|
+
* reports differences depending on the options.
|
|
962
|
+
*
|
|
963
|
+
* @param options - Optional sync behavior (dryRun, dropOrphaned).
|
|
964
|
+
* @returns A summary of created, dropped, skipped, and stale indexes.
|
|
965
|
+
*
|
|
966
|
+
* @example
|
|
967
|
+
* ```ts
|
|
968
|
+
* const users = db.use(Users)
|
|
969
|
+
* const result = await users.syncIndexes()
|
|
970
|
+
* console.log('Created:', result.created)
|
|
971
|
+
* console.log('Stale:', result.stale.map(s => s.name))
|
|
972
|
+
* ```
|
|
973
|
+
*
|
|
974
|
+
* @example
|
|
975
|
+
* ```ts
|
|
976
|
+
* // Dry run to preview changes without modifying the database
|
|
977
|
+
* const diff = await users.syncIndexes({ dryRun: true })
|
|
978
|
+
* console.log('Would create:', diff.created)
|
|
979
|
+
* console.log('Would drop:', diff.dropped)
|
|
980
|
+
* ```
|
|
981
|
+
*/
|
|
982
|
+
async syncIndexes(options) {
|
|
983
|
+
return await syncIndexes(this, options);
|
|
984
|
+
}
|
|
405
985
|
};
|
|
406
986
|
|
|
407
987
|
// src/client/client.ts
|
|
@@ -411,7 +991,7 @@ var Database = class {
|
|
|
411
991
|
/** Registered collection definitions, keyed by name. Used by syncIndexes(). */
|
|
412
992
|
_collections = /* @__PURE__ */ new Map();
|
|
413
993
|
constructor(uri, dbName, options) {
|
|
414
|
-
this._client = new
|
|
994
|
+
this._client = new import_mongodb2.MongoClient(uri, options);
|
|
415
995
|
this._db = this._client.db(dbName);
|
|
416
996
|
}
|
|
417
997
|
/**
|
|
@@ -427,19 +1007,42 @@ var Database = class {
|
|
|
427
1007
|
*/
|
|
428
1008
|
use(def) {
|
|
429
1009
|
this._collections.set(def.name, def);
|
|
430
|
-
const native = this._db.collection(
|
|
1010
|
+
const native = this._db.collection(
|
|
1011
|
+
def.name
|
|
1012
|
+
);
|
|
431
1013
|
return new CollectionHandle(
|
|
432
1014
|
def,
|
|
433
1015
|
native
|
|
434
1016
|
);
|
|
435
1017
|
}
|
|
436
1018
|
/**
|
|
437
|
-
* Synchronize indexes
|
|
1019
|
+
* Synchronize indexes for all registered collections with MongoDB.
|
|
1020
|
+
*
|
|
1021
|
+
* Iterates every collection registered via {@link use} and calls
|
|
1022
|
+
* {@link syncIndexes} on each one. Returns a record keyed by collection
|
|
1023
|
+
* name with the sync result for each.
|
|
438
1024
|
*
|
|
439
|
-
*
|
|
1025
|
+
* @param options - Optional sync behavior (dryRun, dropOrphaned).
|
|
1026
|
+
* @returns A record mapping collection names to their sync results.
|
|
1027
|
+
*
|
|
1028
|
+
* @example
|
|
1029
|
+
* ```ts
|
|
1030
|
+
* const db = createClient('mongodb://localhost:27017', 'myapp')
|
|
1031
|
+
* db.use(Users)
|
|
1032
|
+
* db.use(Posts)
|
|
1033
|
+
* const results = await db.syncIndexes()
|
|
1034
|
+
* console.log(results['users'].created) // ['email_1']
|
|
1035
|
+
* console.log(results['posts'].created) // ['title_1']
|
|
1036
|
+
* ```
|
|
440
1037
|
*/
|
|
441
|
-
syncIndexes() {
|
|
442
|
-
|
|
1038
|
+
async syncIndexes(options) {
|
|
1039
|
+
const results = {};
|
|
1040
|
+
for (const [name, def] of this._collections) {
|
|
1041
|
+
const native = this._db.collection(name);
|
|
1042
|
+
const handle = new CollectionHandle(def, native);
|
|
1043
|
+
results[name] = await syncIndexes(handle, options);
|
|
1044
|
+
}
|
|
1045
|
+
return results;
|
|
443
1046
|
}
|
|
444
1047
|
/**
|
|
445
1048
|
* Execute a function within a MongoDB transaction with auto-commit/rollback.
|
|
@@ -479,14 +1082,14 @@ function createClient(uri, dbNameOrOptions, maybeOptions) {
|
|
|
479
1082
|
}
|
|
480
1083
|
|
|
481
1084
|
// src/collection/collection.ts
|
|
482
|
-
var
|
|
483
|
-
var
|
|
1085
|
+
var import_mongodb4 = require("mongodb");
|
|
1086
|
+
var import_zod9 = require("zod");
|
|
484
1087
|
|
|
485
1088
|
// src/schema/extensions.ts
|
|
486
|
-
var
|
|
1089
|
+
var import_zod7 = require("zod");
|
|
487
1090
|
|
|
488
1091
|
// src/schema/ref.ts
|
|
489
|
-
var
|
|
1092
|
+
var import_zod6 = require("zod");
|
|
490
1093
|
var refMetadata = /* @__PURE__ */ new WeakMap();
|
|
491
1094
|
function getRefMetadata(schema) {
|
|
492
1095
|
if (typeof schema !== "object" || schema === null) return void 0;
|
|
@@ -494,7 +1097,7 @@ function getRefMetadata(schema) {
|
|
|
494
1097
|
}
|
|
495
1098
|
var REF_GUARD = /* @__PURE__ */ Symbol.for("zodmon_ref");
|
|
496
1099
|
function installRefExtension() {
|
|
497
|
-
const proto =
|
|
1100
|
+
const proto = import_zod6.z.ZodType.prototype;
|
|
498
1101
|
if (REF_GUARD in proto) return;
|
|
499
1102
|
Object.defineProperty(proto, "ref", {
|
|
500
1103
|
value(collection2) {
|
|
@@ -521,7 +1124,7 @@ function getIndexMetadata(schema) {
|
|
|
521
1124
|
}
|
|
522
1125
|
var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
|
|
523
1126
|
function installExtensions() {
|
|
524
|
-
const proto =
|
|
1127
|
+
const proto = import_zod7.z.ZodType.prototype;
|
|
525
1128
|
if (GUARD in proto) return;
|
|
526
1129
|
Object.defineProperty(proto, "index", {
|
|
527
1130
|
/**
|
|
@@ -619,14 +1222,14 @@ function installExtensions() {
|
|
|
619
1222
|
installExtensions();
|
|
620
1223
|
|
|
621
1224
|
// src/schema/object-id.ts
|
|
622
|
-
var
|
|
623
|
-
var
|
|
1225
|
+
var import_mongodb3 = require("mongodb");
|
|
1226
|
+
var import_zod8 = require("zod");
|
|
624
1227
|
var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
|
|
625
1228
|
function objectId() {
|
|
626
|
-
return
|
|
627
|
-
if (val instanceof
|
|
1229
|
+
return import_zod8.z.custom((val) => {
|
|
1230
|
+
if (val instanceof import_mongodb3.ObjectId) return true;
|
|
628
1231
|
return typeof val === "string" && OBJECT_ID_HEX.test(val);
|
|
629
|
-
}, "Invalid ObjectId").transform((val) => val instanceof
|
|
1232
|
+
}, "Invalid ObjectId").transform((val) => val instanceof import_mongodb3.ObjectId ? val : import_mongodb3.ObjectId.createFromHexString(val));
|
|
630
1233
|
}
|
|
631
1234
|
|
|
632
1235
|
// src/collection/collection.ts
|
|
@@ -641,8 +1244,8 @@ function extractFieldIndexes(shape) {
|
|
|
641
1244
|
return result;
|
|
642
1245
|
}
|
|
643
1246
|
function collection(name, shape, options) {
|
|
644
|
-
const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new
|
|
645
|
-
const schema =
|
|
1247
|
+
const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new import_mongodb4.ObjectId()), ...shape };
|
|
1248
|
+
const schema = import_zod9.z.object(resolvedShape);
|
|
646
1249
|
const fieldIndexes = extractFieldIndexes(shape);
|
|
647
1250
|
const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
|
|
648
1251
|
return {
|
|
@@ -654,6 +1257,8 @@ function collection(name, shape, options) {
|
|
|
654
1257
|
schema,
|
|
655
1258
|
shape,
|
|
656
1259
|
fieldIndexes,
|
|
1260
|
+
// Safe cast: compoundIndexes is TIndexes at runtime (or an empty array when
|
|
1261
|
+
// no options provided). The spread into [...TIndexes] preserves the tuple type.
|
|
657
1262
|
compoundIndexes: compoundIndexes ?? [],
|
|
658
1263
|
options: {
|
|
659
1264
|
validation: validation ?? "strict",
|
|
@@ -678,12 +1283,29 @@ var IndexBuilder = class _IndexBuilder {
|
|
|
678
1283
|
options
|
|
679
1284
|
});
|
|
680
1285
|
}
|
|
1286
|
+
// Safe cast: _clone returns IndexBuilder<TKeys> but `this` may carry an
|
|
1287
|
+
// intersection from .name(). The cast is safe because _clone preserves all fields.
|
|
681
1288
|
unique() {
|
|
682
1289
|
return this._clone({ ...this.options, unique: true });
|
|
683
1290
|
}
|
|
1291
|
+
// Safe cast: same reasoning as unique().
|
|
684
1292
|
sparse() {
|
|
685
1293
|
return this._clone({ ...this.options, sparse: true });
|
|
686
1294
|
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Set a custom name for this index, preserving the literal type.
|
|
1297
|
+
*
|
|
1298
|
+
* The returned builder carries the literal name type via an intersection,
|
|
1299
|
+
* enabling type-safe `.hint()` on cursors that only accepts declared names.
|
|
1300
|
+
*
|
|
1301
|
+
* @param name - The index name.
|
|
1302
|
+
* @returns A new IndexBuilder with the name recorded at the type level.
|
|
1303
|
+
*
|
|
1304
|
+
* @example
|
|
1305
|
+
* ```ts
|
|
1306
|
+
* index({ email: 1, role: -1 }).name('email_role_idx')
|
|
1307
|
+
* ```
|
|
1308
|
+
*/
|
|
687
1309
|
name(name) {
|
|
688
1310
|
return this._clone({ ...this.options, name });
|
|
689
1311
|
}
|
|
@@ -693,14 +1315,14 @@ function index(fields) {
|
|
|
693
1315
|
}
|
|
694
1316
|
|
|
695
1317
|
// src/helpers/oid.ts
|
|
696
|
-
var
|
|
1318
|
+
var import_mongodb5 = require("mongodb");
|
|
697
1319
|
function oid(value) {
|
|
698
|
-
if (value === void 0) return new
|
|
699
|
-
if (value instanceof
|
|
700
|
-
return
|
|
1320
|
+
if (value === void 0) return new import_mongodb5.ObjectId();
|
|
1321
|
+
if (value instanceof import_mongodb5.ObjectId) return value;
|
|
1322
|
+
return import_mongodb5.ObjectId.createFromHexString(value);
|
|
701
1323
|
}
|
|
702
1324
|
function isOid(value) {
|
|
703
|
-
return value instanceof
|
|
1325
|
+
return value instanceof import_mongodb5.ObjectId;
|
|
704
1326
|
}
|
|
705
1327
|
|
|
706
1328
|
// src/query/operators.ts
|
|
@@ -723,8 +1345,28 @@ var $or = (...filters) => ({ $or: filters });
|
|
|
723
1345
|
var $and = (...filters) => ({ $and: filters });
|
|
724
1346
|
var $nor = (...filters) => ({ $nor: filters });
|
|
725
1347
|
var raw = (filter) => filter;
|
|
1348
|
+
|
|
1349
|
+
// src/query/namespace.ts
|
|
1350
|
+
var $ = {
|
|
1351
|
+
eq: $eq,
|
|
1352
|
+
ne: $ne,
|
|
1353
|
+
gt: $gt,
|
|
1354
|
+
gte: $gte,
|
|
1355
|
+
lt: $lt,
|
|
1356
|
+
lte: $lte,
|
|
1357
|
+
in: $in,
|
|
1358
|
+
nin: $nin,
|
|
1359
|
+
exists: $exists,
|
|
1360
|
+
regex: $regex,
|
|
1361
|
+
not: $not,
|
|
1362
|
+
or: $or,
|
|
1363
|
+
and: $and,
|
|
1364
|
+
nor: $nor,
|
|
1365
|
+
raw
|
|
1366
|
+
};
|
|
726
1367
|
// Annotate the CommonJS export names for ESM import in node:
|
|
727
1368
|
0 && (module.exports = {
|
|
1369
|
+
$,
|
|
728
1370
|
$and,
|
|
729
1371
|
$eq,
|
|
730
1372
|
$exists,
|
|
@@ -745,23 +1387,34 @@ var raw = (filter) => filter;
|
|
|
745
1387
|
TypedFindCursor,
|
|
746
1388
|
ZodmonNotFoundError,
|
|
747
1389
|
ZodmonValidationError,
|
|
1390
|
+
checkUnindexedFields,
|
|
748
1391
|
collection,
|
|
749
1392
|
createClient,
|
|
1393
|
+
deleteMany,
|
|
1394
|
+
deleteOne,
|
|
1395
|
+
extractComparableOptions,
|
|
750
1396
|
extractDbName,
|
|
751
1397
|
extractFieldIndexes,
|
|
752
1398
|
find,
|
|
753
1399
|
findOne,
|
|
1400
|
+
findOneAndDelete,
|
|
1401
|
+
findOneAndUpdate,
|
|
754
1402
|
findOneOrThrow,
|
|
1403
|
+
generateIndexName,
|
|
755
1404
|
getIndexMetadata,
|
|
756
1405
|
getRefMetadata,
|
|
757
1406
|
index,
|
|
758
1407
|
insertMany,
|
|
759
1408
|
insertOne,
|
|
760
|
-
installExtensions,
|
|
761
|
-
installRefExtension,
|
|
762
1409
|
isOid,
|
|
763
1410
|
objectId,
|
|
764
1411
|
oid,
|
|
765
|
-
raw
|
|
1412
|
+
raw,
|
|
1413
|
+
serializeIndexKey,
|
|
1414
|
+
syncIndexes,
|
|
1415
|
+
toCompoundIndexSpec,
|
|
1416
|
+
toFieldIndexSpec,
|
|
1417
|
+
updateMany,
|
|
1418
|
+
updateOne
|
|
766
1419
|
});
|
|
767
1420
|
//# sourceMappingURL=index.cjs.map
|