@zodmon/core 0.7.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 +316 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +439 -15
- package/dist/index.d.ts +439 -15
- package/dist/index.js +309 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -41,10 +41,12 @@ __export(index_exports, {
|
|
|
41
41
|
TypedFindCursor: () => TypedFindCursor,
|
|
42
42
|
ZodmonNotFoundError: () => ZodmonNotFoundError,
|
|
43
43
|
ZodmonValidationError: () => ZodmonValidationError,
|
|
44
|
+
checkUnindexedFields: () => checkUnindexedFields,
|
|
44
45
|
collection: () => collection,
|
|
45
46
|
createClient: () => createClient,
|
|
46
47
|
deleteMany: () => deleteMany,
|
|
47
48
|
deleteOne: () => deleteOne,
|
|
49
|
+
extractComparableOptions: () => extractComparableOptions,
|
|
48
50
|
extractDbName: () => extractDbName,
|
|
49
51
|
extractFieldIndexes: () => extractFieldIndexes,
|
|
50
52
|
find: () => find,
|
|
@@ -52,6 +54,7 @@ __export(index_exports, {
|
|
|
52
54
|
findOneAndDelete: () => findOneAndDelete,
|
|
53
55
|
findOneAndUpdate: () => findOneAndUpdate,
|
|
54
56
|
findOneOrThrow: () => findOneOrThrow,
|
|
57
|
+
generateIndexName: () => generateIndexName,
|
|
55
58
|
getIndexMetadata: () => getIndexMetadata,
|
|
56
59
|
getRefMetadata: () => getRefMetadata,
|
|
57
60
|
index: () => index,
|
|
@@ -61,6 +64,10 @@ __export(index_exports, {
|
|
|
61
64
|
objectId: () => objectId,
|
|
62
65
|
oid: () => oid,
|
|
63
66
|
raw: () => raw,
|
|
67
|
+
serializeIndexKey: () => serializeIndexKey,
|
|
68
|
+
syncIndexes: () => syncIndexes,
|
|
69
|
+
toCompoundIndexSpec: () => toCompoundIndexSpec,
|
|
70
|
+
toFieldIndexSpec: () => toFieldIndexSpec,
|
|
64
71
|
updateMany: () => updateMany,
|
|
65
72
|
updateOne: () => updateOne
|
|
66
73
|
});
|
|
@@ -69,6 +76,168 @@ module.exports = __toCommonJS(index_exports);
|
|
|
69
76
|
// src/client/client.ts
|
|
70
77
|
var import_mongodb2 = require("mongodb");
|
|
71
78
|
|
|
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
|
+
}
|
|
102
|
+
|
|
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
|
+
}
|
|
116
|
+
}
|
|
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
|
+
|
|
72
241
|
// src/crud/delete.ts
|
|
73
242
|
var import_zod = require("zod");
|
|
74
243
|
|
|
@@ -132,6 +301,29 @@ var ZodmonNotFoundError = class extends Error {
|
|
|
132
301
|
}
|
|
133
302
|
};
|
|
134
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
|
+
|
|
135
327
|
// src/query/cursor.ts
|
|
136
328
|
var import_zod2 = require("zod");
|
|
137
329
|
|
|
@@ -149,8 +341,8 @@ function deserializeValue(value) {
|
|
|
149
341
|
}
|
|
150
342
|
return value;
|
|
151
343
|
}
|
|
152
|
-
function encodeCursor(doc,
|
|
153
|
-
const values =
|
|
344
|
+
function encodeCursor(doc, sortKeys2, direction) {
|
|
345
|
+
const values = sortKeys2.map(([field]) => serializeValue(doc[field]));
|
|
154
346
|
return btoa(JSON.stringify([direction, ...values]));
|
|
155
347
|
}
|
|
156
348
|
function decodeCursor(cursor) {
|
|
@@ -169,14 +361,14 @@ function decodeCursor(cursor) {
|
|
|
169
361
|
}
|
|
170
362
|
return { direction, values: rawValues.map(deserializeValue) };
|
|
171
363
|
}
|
|
172
|
-
function buildCursorFilter(
|
|
364
|
+
function buildCursorFilter(sortKeys2, values, isBackward) {
|
|
173
365
|
const clauses = [];
|
|
174
|
-
for (let i = 0; i <
|
|
366
|
+
for (let i = 0; i < sortKeys2.length; i++) {
|
|
175
367
|
const clause = {};
|
|
176
368
|
for (let j = 0; j < i; j++) {
|
|
177
|
-
clause[
|
|
369
|
+
clause[sortKeys2[j][0]] = values[j];
|
|
178
370
|
}
|
|
179
|
-
const [field, direction] =
|
|
371
|
+
const [field, direction] = sortKeys2[i];
|
|
180
372
|
const isAsc = direction === 1;
|
|
181
373
|
const op = isAsc !== isBackward ? "$gt" : "$lt";
|
|
182
374
|
if (values[i] === null) {
|
|
@@ -272,14 +464,37 @@ var TypedFindCursor = class {
|
|
|
272
464
|
this.cursor.limit(n);
|
|
273
465
|
return this;
|
|
274
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
|
+
}
|
|
275
490
|
async paginate(opts) {
|
|
276
491
|
const sortRecord = this.sortSpec ? this.sortSpec : null;
|
|
277
|
-
const
|
|
278
|
-
const sort = Object.fromEntries(
|
|
492
|
+
const sortKeys2 = resolveSortKeys(sortRecord);
|
|
493
|
+
const sort = Object.fromEntries(sortKeys2);
|
|
279
494
|
if ("page" in opts) {
|
|
280
|
-
return await this.offsetPaginate(
|
|
495
|
+
return await this.offsetPaginate(sortKeys2, sort, opts);
|
|
281
496
|
}
|
|
282
|
-
return await this.cursorPaginate(
|
|
497
|
+
return await this.cursorPaginate(sortKeys2, sort, opts);
|
|
283
498
|
}
|
|
284
499
|
/** @internal Offset pagination implementation. */
|
|
285
500
|
async offsetPaginate(_sortKeys, sort, opts) {
|
|
@@ -300,16 +515,16 @@ var TypedFindCursor = class {
|
|
|
300
515
|
};
|
|
301
516
|
}
|
|
302
517
|
/** @internal Cursor pagination implementation. */
|
|
303
|
-
async cursorPaginate(
|
|
518
|
+
async cursorPaginate(sortKeys2, sort, opts) {
|
|
304
519
|
let isBackward = false;
|
|
305
520
|
let combinedFilter = this.filter;
|
|
306
521
|
if (opts.cursor) {
|
|
307
522
|
const decoded = decodeCursor(opts.cursor);
|
|
308
523
|
isBackward = decoded.direction === "b";
|
|
309
|
-
const cursorFilter = buildCursorFilter(
|
|
524
|
+
const cursorFilter = buildCursorFilter(sortKeys2, decoded.values, isBackward);
|
|
310
525
|
combinedFilter = this.filter && Object.keys(this.filter).length > 0 ? { $and: [this.filter, cursorFilter] } : cursorFilter;
|
|
311
526
|
}
|
|
312
|
-
const effectiveSort = isBackward ? Object.fromEntries(
|
|
527
|
+
const effectiveSort = isBackward ? Object.fromEntries(sortKeys2.map(([f, d]) => [f, d === 1 ? -1 : 1])) : sort;
|
|
313
528
|
const raw2 = await this.nativeCollection.find(combinedFilter).sort(effectiveSort).limit(opts.limit + 1).toArray();
|
|
314
529
|
const hasMore = raw2.length > opts.limit;
|
|
315
530
|
if (hasMore) raw2.pop();
|
|
@@ -319,8 +534,8 @@ var TypedFindCursor = class {
|
|
|
319
534
|
docs,
|
|
320
535
|
hasNext: isBackward ? true : hasMore,
|
|
321
536
|
hasPrev: isBackward ? hasMore : opts.cursor != null,
|
|
322
|
-
startCursor: docs.length > 0 ? encodeCursor(docs[0],
|
|
323
|
-
endCursor: docs.length > 0 ? encodeCursor(docs[docs.length - 1],
|
|
537
|
+
startCursor: docs.length > 0 ? encodeCursor(docs[0], sortKeys2, "b") : null,
|
|
538
|
+
endCursor: docs.length > 0 ? encodeCursor(docs[docs.length - 1], sortKeys2, "f") : null
|
|
324
539
|
};
|
|
325
540
|
}
|
|
326
541
|
/**
|
|
@@ -380,6 +595,7 @@ var TypedFindCursor = class {
|
|
|
380
595
|
|
|
381
596
|
// src/crud/find.ts
|
|
382
597
|
async function findOne(handle, filter, options) {
|
|
598
|
+
checkUnindexedFields(handle.definition, filter);
|
|
383
599
|
const findOptions = options?.project ? { projection: options.project } : void 0;
|
|
384
600
|
const raw2 = await handle.native.findOne(filter, findOptions);
|
|
385
601
|
if (!raw2) return null;
|
|
@@ -404,6 +620,7 @@ async function findOneOrThrow(handle, filter, options) {
|
|
|
404
620
|
return doc;
|
|
405
621
|
}
|
|
406
622
|
function find(handle, filter, options) {
|
|
623
|
+
checkUnindexedFields(handle.definition, filter);
|
|
407
624
|
const raw2 = handle.native.find(filter);
|
|
408
625
|
const cursor = raw2;
|
|
409
626
|
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
@@ -735,6 +952,36 @@ var CollectionHandle = class {
|
|
|
735
952
|
async findOneAndDelete(filter, options) {
|
|
736
953
|
return await findOneAndDelete(this, filter, options);
|
|
737
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
|
+
}
|
|
738
985
|
};
|
|
739
986
|
|
|
740
987
|
// src/client/client.ts
|
|
@@ -760,19 +1007,42 @@ var Database = class {
|
|
|
760
1007
|
*/
|
|
761
1008
|
use(def) {
|
|
762
1009
|
this._collections.set(def.name, def);
|
|
763
|
-
const native = this._db.collection(
|
|
1010
|
+
const native = this._db.collection(
|
|
1011
|
+
def.name
|
|
1012
|
+
);
|
|
764
1013
|
return new CollectionHandle(
|
|
765
1014
|
def,
|
|
766
1015
|
native
|
|
767
1016
|
);
|
|
768
1017
|
}
|
|
769
1018
|
/**
|
|
770
|
-
* 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.
|
|
1024
|
+
*
|
|
1025
|
+
* @param options - Optional sync behavior (dryRun, dropOrphaned).
|
|
1026
|
+
* @returns A record mapping collection names to their sync results.
|
|
771
1027
|
*
|
|
772
|
-
*
|
|
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
|
+
* ```
|
|
773
1037
|
*/
|
|
774
|
-
syncIndexes() {
|
|
775
|
-
|
|
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;
|
|
776
1046
|
}
|
|
777
1047
|
/**
|
|
778
1048
|
* Execute a function within a MongoDB transaction with auto-commit/rollback.
|
|
@@ -987,6 +1257,8 @@ function collection(name, shape, options) {
|
|
|
987
1257
|
schema,
|
|
988
1258
|
shape,
|
|
989
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.
|
|
990
1262
|
compoundIndexes: compoundIndexes ?? [],
|
|
991
1263
|
options: {
|
|
992
1264
|
validation: validation ?? "strict",
|
|
@@ -1011,12 +1283,29 @@ var IndexBuilder = class _IndexBuilder {
|
|
|
1011
1283
|
options
|
|
1012
1284
|
});
|
|
1013
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.
|
|
1014
1288
|
unique() {
|
|
1015
1289
|
return this._clone({ ...this.options, unique: true });
|
|
1016
1290
|
}
|
|
1291
|
+
// Safe cast: same reasoning as unique().
|
|
1017
1292
|
sparse() {
|
|
1018
1293
|
return this._clone({ ...this.options, sparse: true });
|
|
1019
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
|
+
*/
|
|
1020
1309
|
name(name) {
|
|
1021
1310
|
return this._clone({ ...this.options, name });
|
|
1022
1311
|
}
|
|
@@ -1098,10 +1387,12 @@ var $ = {
|
|
|
1098
1387
|
TypedFindCursor,
|
|
1099
1388
|
ZodmonNotFoundError,
|
|
1100
1389
|
ZodmonValidationError,
|
|
1390
|
+
checkUnindexedFields,
|
|
1101
1391
|
collection,
|
|
1102
1392
|
createClient,
|
|
1103
1393
|
deleteMany,
|
|
1104
1394
|
deleteOne,
|
|
1395
|
+
extractComparableOptions,
|
|
1105
1396
|
extractDbName,
|
|
1106
1397
|
extractFieldIndexes,
|
|
1107
1398
|
find,
|
|
@@ -1109,6 +1400,7 @@ var $ = {
|
|
|
1109
1400
|
findOneAndDelete,
|
|
1110
1401
|
findOneAndUpdate,
|
|
1111
1402
|
findOneOrThrow,
|
|
1403
|
+
generateIndexName,
|
|
1112
1404
|
getIndexMetadata,
|
|
1113
1405
|
getRefMetadata,
|
|
1114
1406
|
index,
|
|
@@ -1118,6 +1410,10 @@ var $ = {
|
|
|
1118
1410
|
objectId,
|
|
1119
1411
|
oid,
|
|
1120
1412
|
raw,
|
|
1413
|
+
serializeIndexKey,
|
|
1414
|
+
syncIndexes,
|
|
1415
|
+
toCompoundIndexSpec,
|
|
1416
|
+
toFieldIndexSpec,
|
|
1121
1417
|
updateMany,
|
|
1122
1418
|
updateOne
|
|
1123
1419
|
});
|