monsqlize 3.3.3 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -3
- package/README.md +1 -1
- package/changelogs/v3.3.4.md +21 -0
- package/changelogs/v3.4.0.md +28 -0
- package/dist/cjs/cli/data-task.cjs +8 -5
- package/dist/cjs/index.cjs +594 -100
- package/dist/cjs/transaction/Transaction.cjs +7 -0
- package/dist/cjs/transaction/TransactionManager.cjs +7 -0
- package/dist/esm/index.mjs +594 -100
- package/dist/types/base.d.mts +6 -0
- package/dist/types/base.d.ts +6 -0
- package/dist/types/collection.d.mts +2 -2
- package/dist/types/collection.d.ts +2 -2
- package/dist/types/index.d.mts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/model.d.mts +2 -2
- package/dist/types/model.d.ts +2 -2
- package/dist/types/runtime.d.mts +7 -0
- package/dist/types/runtime.d.ts +7 -0
- package/dist/types/search-index.d.mts +107 -0
- package/dist/types/search-index.d.ts +107 -0
- package/package.json +8 -5
package/dist/esm/index.mjs
CHANGED
|
@@ -66,6 +66,13 @@ var ErrorCodes = {
|
|
|
66
66
|
INVALID_OPERATION: "INVALID_OPERATION",
|
|
67
67
|
/** Invalid local Vector Search options before the driver is called. */
|
|
68
68
|
INVALID_VECTOR_SEARCH: "INVALID_VECTOR_SEARCH",
|
|
69
|
+
/** Search index lifecycle errors (v3.4.0). */
|
|
70
|
+
INVALID_SEARCH_INDEX_DEFINITION: "INVALID_SEARCH_INDEX_DEFINITION",
|
|
71
|
+
SEARCH_INDEX_EXISTS: "SEARCH_INDEX_EXISTS",
|
|
72
|
+
SEARCH_INDEX_NOT_FOUND: "SEARCH_INDEX_NOT_FOUND",
|
|
73
|
+
SEARCH_INDEX_UNSUPPORTED: "SEARCH_INDEX_UNSUPPORTED",
|
|
74
|
+
SEARCH_INDEX_BUILD_FAILED: "SEARCH_INDEX_BUILD_FAILED",
|
|
75
|
+
SEARCH_INDEX_WAIT_TIMEOUT: "SEARCH_INDEX_WAIT_TIMEOUT",
|
|
69
76
|
/** Relation select is outside the supported inclusion-only top-level contract. */
|
|
70
77
|
INVALID_RELATION_SELECT: "INVALID_RELATION_SELECT",
|
|
71
78
|
/** A protected delete found declared Model relations that still reference its target. */
|
|
@@ -998,31 +1005,31 @@ function normalizeRelationSelect(value, context = {}) {
|
|
|
998
1005
|
if (value === void 0) {
|
|
999
1006
|
return void 0;
|
|
1000
1007
|
}
|
|
1001
|
-
const
|
|
1008
|
+
const invalid3 = (reason) => {
|
|
1002
1009
|
throw createError(
|
|
1003
1010
|
ErrorCodes.INVALID_RELATION_SELECT,
|
|
1004
1011
|
`Relation select must use non-empty top-level inclusion fields: ${reason}`,
|
|
1005
1012
|
[{ ...context, value, reason }]
|
|
1006
1013
|
);
|
|
1007
1014
|
};
|
|
1008
|
-
const rawTokens = typeof value === "string" ? [value] : Array.isArray(value) ? value :
|
|
1015
|
+
const rawTokens = typeof value === "string" ? [value] : Array.isArray(value) ? value : invalid3("select must be a string or string array");
|
|
1009
1016
|
const tokens = [];
|
|
1010
1017
|
for (const rawToken of rawTokens) {
|
|
1011
1018
|
if (typeof rawToken !== "string") {
|
|
1012
|
-
|
|
1019
|
+
invalid3("select array entries must be strings");
|
|
1013
1020
|
}
|
|
1014
1021
|
const parts = rawToken.trim().split(/\s+/).filter(Boolean);
|
|
1015
1022
|
if (parts.length === 0) {
|
|
1016
|
-
|
|
1023
|
+
invalid3("select must not be empty");
|
|
1017
1024
|
}
|
|
1018
1025
|
tokens.push(...parts);
|
|
1019
1026
|
}
|
|
1020
1027
|
for (const token of tokens) {
|
|
1021
1028
|
if (token.startsWith("-") || token.startsWith("+")) {
|
|
1022
|
-
|
|
1029
|
+
invalid3("exclusion and prefix tokens are not supported");
|
|
1023
1030
|
}
|
|
1024
1031
|
if (token.includes(".")) {
|
|
1025
|
-
|
|
1032
|
+
invalid3("dot-path fields are not supported");
|
|
1026
1033
|
}
|
|
1027
1034
|
}
|
|
1028
1035
|
return [...new Set(tokens)];
|
|
@@ -3577,8 +3584,8 @@ function normalizeExtensionValue(value, stack) {
|
|
|
3577
3584
|
return `circular:${getExtensionObjectKey(objectValue)}`;
|
|
3578
3585
|
}
|
|
3579
3586
|
const prototype = Object.getPrototypeOf(objectValue);
|
|
3580
|
-
const
|
|
3581
|
-
if (!Array.isArray(objectValue) && !
|
|
3587
|
+
const isPlainObject5 = prototype === Object.prototype || prototype === null;
|
|
3588
|
+
if (!Array.isArray(objectValue) && !isPlainObject5) {
|
|
3582
3589
|
return `object:${prototype?.constructor?.name ?? "unknown"}:${getExtensionObjectKey(objectValue)}`;
|
|
3583
3590
|
}
|
|
3584
3591
|
stack.add(objectValue);
|
|
@@ -6692,6 +6699,28 @@ var ModelInstance = class {
|
|
|
6692
6699
|
dropIndexes() {
|
|
6693
6700
|
return this.runModelWrite(() => this.collection.dropIndexes());
|
|
6694
6701
|
}
|
|
6702
|
+
/** Search index lifecycle (v3.4.0): writes run as model writes for writePathPolicy. */
|
|
6703
|
+
createSearchIndex(spec, options) {
|
|
6704
|
+
return this.runModelWrite(() => this.collection.createSearchIndex(spec, options));
|
|
6705
|
+
}
|
|
6706
|
+
createSearchIndexes(specs) {
|
|
6707
|
+
return this.runModelWrite(() => this.collection.createSearchIndexes(specs));
|
|
6708
|
+
}
|
|
6709
|
+
listSearchIndexes(options) {
|
|
6710
|
+
return this.collection.listSearchIndexes(options);
|
|
6711
|
+
}
|
|
6712
|
+
getSearchIndex(name) {
|
|
6713
|
+
return this.collection.getSearchIndex(name);
|
|
6714
|
+
}
|
|
6715
|
+
updateSearchIndex(name, definition) {
|
|
6716
|
+
return this.runModelWrite(() => this.collection.updateSearchIndex(name, definition));
|
|
6717
|
+
}
|
|
6718
|
+
dropSearchIndex(name, options) {
|
|
6719
|
+
return this.runModelWrite(() => this.collection.dropSearchIndex(name, options));
|
|
6720
|
+
}
|
|
6721
|
+
waitForSearchIndex(name, options) {
|
|
6722
|
+
return this.collection.waitForSearchIndex(name, options);
|
|
6723
|
+
}
|
|
6695
6724
|
prewarmBookmarks(keyDims, pages) {
|
|
6696
6725
|
return this.extendedCollection().prewarmBookmarks(keyDims, pages);
|
|
6697
6726
|
}
|
|
@@ -8248,18 +8277,26 @@ var HealthChecker = class {
|
|
|
8248
8277
|
const status = this._healthStatus.get(poolName);
|
|
8249
8278
|
if (!status) return;
|
|
8250
8279
|
const client = this._clients.get(poolName) ?? this._poolManager?._getPool(poolName);
|
|
8251
|
-
|
|
8280
|
+
const pending = this._inProgress.get(poolName);
|
|
8281
|
+
if (pending && pending.client === client && pending.awaited) return;
|
|
8252
8282
|
const epoch = this._epochs.get(poolName);
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
}
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8283
|
+
let operation;
|
|
8284
|
+
if (pending && pending.client === client) {
|
|
8285
|
+
operation = pending;
|
|
8286
|
+
} else {
|
|
8287
|
+
const promise2 = Promise.resolve().then(() => {
|
|
8288
|
+
if (this._epochs.get(poolName) !== epoch || this._healthStatus.get(poolName) !== status) return;
|
|
8289
|
+
return this._pingOverride ? this._pingOverride(poolName) : this._pingPool(poolName);
|
|
8290
|
+
});
|
|
8291
|
+
operation = { client, promise: promise2, awaited: true };
|
|
8292
|
+
this._inProgress.set(poolName, operation);
|
|
8293
|
+
const clearPending = () => {
|
|
8294
|
+
if (this._inProgress.get(poolName) === operation) this._inProgress.delete(poolName);
|
|
8295
|
+
};
|
|
8296
|
+
void promise2.then(clearPending, clearPending);
|
|
8297
|
+
}
|
|
8298
|
+
operation.awaited = true;
|
|
8299
|
+
const promise = operation.promise;
|
|
8263
8300
|
let timer;
|
|
8264
8301
|
let lastError = null;
|
|
8265
8302
|
try {
|
|
@@ -8271,6 +8308,7 @@ var HealthChecker = class {
|
|
|
8271
8308
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
8272
8309
|
} finally {
|
|
8273
8310
|
if (timer) clearTimeout(timer);
|
|
8311
|
+
operation.awaited = false;
|
|
8274
8312
|
}
|
|
8275
8313
|
if (this._epochs.get(poolName) !== epoch || this._healthStatus.get(poolName) !== status) return;
|
|
8276
8314
|
status.lastCheck = /* @__PURE__ */ new Date();
|
|
@@ -10728,27 +10766,28 @@ function normalizeFindPageLimit(limit, defaults2) {
|
|
|
10728
10766
|
}
|
|
10729
10767
|
return rawLimit;
|
|
10730
10768
|
}
|
|
10731
|
-
function documentReadKey(operation, collection, defaults2, query, options, field) {
|
|
10732
|
-
const
|
|
10769
|
+
function documentReadKey(operation, collection, defaults2, query, options, field, queryPrepared = false) {
|
|
10770
|
+
const keyQuery = queryPrepared ? query ?? {} : normalizeQueryForCache(query, defaults2);
|
|
10771
|
+
const captured = snapshotReadInputs(keyQuery, options, collection.bsonOptions);
|
|
10733
10772
|
const keyOptions = buildResultCacheKeyOptions(captured.options, collection.bsonOptions);
|
|
10734
10773
|
if (operation === "count") delete keyOptions.fieldsAsRaw;
|
|
10735
10774
|
const body = field === void 0 ? stableCacheKeyString(captured.query, "document") : stableCacheKeyString({ key: field, query: captured.query });
|
|
10736
10775
|
return `${operation}:${buildCollectionCacheNamespace(collection, defaults2)}:${body}:${stableCacheKeyString(keyOptions)}`;
|
|
10737
10776
|
}
|
|
10738
|
-
function buildFindCacheKey(collection, defaults2 = {}, query, options) {
|
|
10777
|
+
function buildFindCacheKey(collection, defaults2 = {}, query, options, queryPrepared = false) {
|
|
10739
10778
|
return documentReadKey("find", collection, defaults2, query, normalizeFindProjectionOptions({
|
|
10740
10779
|
limit: defaults2.findLimit,
|
|
10741
10780
|
...options ?? {}
|
|
10742
|
-
}));
|
|
10781
|
+
}), void 0, queryPrepared);
|
|
10743
10782
|
}
|
|
10744
|
-
function buildFindOneCacheKey(collection, defaults2 = {}, query, options) {
|
|
10745
|
-
return documentReadKey("findOne", collection, defaults2, query, normalizeFindProjectionOptions(options));
|
|
10783
|
+
function buildFindOneCacheKey(collection, defaults2 = {}, query, options, queryPrepared = false) {
|
|
10784
|
+
return documentReadKey("findOne", collection, defaults2, query, normalizeFindProjectionOptions(options), void 0, queryPrepared);
|
|
10746
10785
|
}
|
|
10747
|
-
function buildCountCacheKey(collection, defaults2 = {}, query, options) {
|
|
10748
|
-
return documentReadKey("count", collection, defaults2, query, options);
|
|
10786
|
+
function buildCountCacheKey(collection, defaults2 = {}, query, options, queryPrepared = false) {
|
|
10787
|
+
return documentReadKey("count", collection, defaults2, query, options, void 0, queryPrepared);
|
|
10749
10788
|
}
|
|
10750
|
-
function buildDistinctCacheKey(collection, defaults2 = {}, field, query, options) {
|
|
10751
|
-
return documentReadKey("distinct", collection, defaults2, query, options, field);
|
|
10789
|
+
function buildDistinctCacheKey(collection, defaults2 = {}, field, query, options, queryPrepared = false) {
|
|
10790
|
+
return documentReadKey("distinct", collection, defaults2, query, options, field, queryPrepared);
|
|
10752
10791
|
}
|
|
10753
10792
|
function buildPreparedAggregateCacheKey(collection, defaults2 = {}, pipeline = [], options) {
|
|
10754
10793
|
const namespace = buildCollectionCacheNamespace(collection, defaults2);
|
|
@@ -11760,7 +11799,7 @@ var FindChain = class {
|
|
|
11760
11799
|
const cacheTTL = typeof this.options.cache === "number" ? this.options.cache : 0;
|
|
11761
11800
|
const executeOptions = prepared.options;
|
|
11762
11801
|
if (cacheTTL > 0 && this.queryCache && canCacheBsonResult(executeOptions) && !hasSessionOption(executeOptions) && !await isCollectionCacheBarrierActive(this.queryCache, this.collection, this.defaults)) {
|
|
11763
|
-
const cacheKey = buildFindCacheKey(this.collection, this.defaults, prepared.query, executeOptions);
|
|
11802
|
+
const cacheKey = buildFindCacheKey(this.collection, this.defaults, prepared.query, executeOptions, true);
|
|
11764
11803
|
const result = await readThroughCache(this.queryCache, cacheKey, cacheTTL, () => this.runToArray(prepared));
|
|
11765
11804
|
return this.wrapResult("find", startTs, result);
|
|
11766
11805
|
}
|
|
@@ -12101,6 +12140,432 @@ function mapVectorSearchRows(rows) {
|
|
|
12101
12140
|
});
|
|
12102
12141
|
}
|
|
12103
12142
|
|
|
12143
|
+
// src/adapters/mongodb/search-indexes/errors.ts
|
|
12144
|
+
var CODE_INDEX_ALREADY_EXISTS = 68;
|
|
12145
|
+
var CODE_INDEX_NOT_FOUND = 27;
|
|
12146
|
+
var CODE_NAMESPACE_NOT_FOUND = 26;
|
|
12147
|
+
var CODE_COMMAND_NOT_FOUND = 59;
|
|
12148
|
+
var CODE_COMMAND_NOT_SUPPORTED = 115;
|
|
12149
|
+
var CODE_LIST_SEARCH_ATLAS_ONLY = 6047401;
|
|
12150
|
+
var UNSUPPORTED_MESSAGE = /search index commands are only supported|\$listSearchIndexes stage is only allowed/i;
|
|
12151
|
+
function readCode(error) {
|
|
12152
|
+
const e = error;
|
|
12153
|
+
return {
|
|
12154
|
+
code: typeof e?.code === "number" ? e.code : void 0,
|
|
12155
|
+
codeName: typeof e?.codeName === "string" ? e.codeName : void 0,
|
|
12156
|
+
message: typeof e?.message === "string" ? e.message : String(error)
|
|
12157
|
+
};
|
|
12158
|
+
}
|
|
12159
|
+
function classifySearchIndexError(error) {
|
|
12160
|
+
const { code, codeName, message } = readCode(error);
|
|
12161
|
+
if (code === CODE_INDEX_ALREADY_EXISTS || codeName === "IndexAlreadyExists") return "exists";
|
|
12162
|
+
if (code === CODE_NAMESPACE_NOT_FOUND || codeName === "NamespaceNotFound") return "namespaceNotFound";
|
|
12163
|
+
if (code === CODE_INDEX_NOT_FOUND || codeName === "IndexNotFound") return "notFound";
|
|
12164
|
+
if (code === CODE_COMMAND_NOT_FOUND || code === CODE_COMMAND_NOT_SUPPORTED || code === CODE_LIST_SEARCH_ATLAS_ONLY || codeName === "CommandNotFound" || codeName === "CommandNotSupported" || codeName === "SearchNotEnabled") return "unsupported";
|
|
12165
|
+
if (code === void 0 && codeName === void 0 && /already exists/i.test(message)) return "exists";
|
|
12166
|
+
if (UNSUPPORTED_MESSAGE.test(message)) return "unsupported";
|
|
12167
|
+
return "other";
|
|
12168
|
+
}
|
|
12169
|
+
function attachContext(error, ctx) {
|
|
12170
|
+
const contextualized = error;
|
|
12171
|
+
try {
|
|
12172
|
+
contextualized.monsqlize = {
|
|
12173
|
+
...contextualized.monsqlize && typeof contextualized.monsqlize === "object" ? contextualized.monsqlize : {},
|
|
12174
|
+
operation: ctx.operation,
|
|
12175
|
+
namespace: ctx.namespace,
|
|
12176
|
+
indexName: ctx.indexName
|
|
12177
|
+
};
|
|
12178
|
+
} catch {
|
|
12179
|
+
}
|
|
12180
|
+
return error;
|
|
12181
|
+
}
|
|
12182
|
+
function searchIndexError(code, message, ctx, extra = {}, cause) {
|
|
12183
|
+
return createError(code, message, [{ operation: ctx.operation, namespace: ctx.namespace, indexName: ctx.indexName, ...extra }], cause);
|
|
12184
|
+
}
|
|
12185
|
+
function mapSearchIndexError(error, ctx) {
|
|
12186
|
+
if (!(error instanceof Error)) {
|
|
12187
|
+
return createError(ErrorCodes.MONGODB_ERROR, `${ctx.operation} failed`, [{ ...ctx, reason: String(error) }]);
|
|
12188
|
+
}
|
|
12189
|
+
if (error.code !== void 0 && typeof error.code === "string" && String(error.code).startsWith("SEARCH_INDEX_")) {
|
|
12190
|
+
return error;
|
|
12191
|
+
}
|
|
12192
|
+
const { code, codeName, message } = readCode(error);
|
|
12193
|
+
const server = { serverCode: code, serverCodeName: codeName, serverMessage: message };
|
|
12194
|
+
switch (classifySearchIndexError(error)) {
|
|
12195
|
+
case "exists":
|
|
12196
|
+
return searchIndexError(ErrorCodes.SEARCH_INDEX_EXISTS, `Search index "${ctx.indexName}" already exists`, ctx, server, error);
|
|
12197
|
+
case "notFound":
|
|
12198
|
+
return searchIndexError(ErrorCodes.SEARCH_INDEX_NOT_FOUND, `Search index "${ctx.indexName}" was not found`, ctx, server, error);
|
|
12199
|
+
case "unsupported":
|
|
12200
|
+
return searchIndexError(ErrorCodes.SEARCH_INDEX_UNSUPPORTED, "This deployment does not support Search index commands (requires Atlas, MongoDB 8.2+ with mongot, or mongodb-atlas-local)", ctx, server, error);
|
|
12201
|
+
default:
|
|
12202
|
+
return attachContext(searchIndexError(ErrorCodes.MONGODB_ERROR, `${ctx.operation} failed: ${message}`, ctx, server, error), ctx);
|
|
12203
|
+
}
|
|
12204
|
+
}
|
|
12205
|
+
|
|
12206
|
+
// src/adapters/mongodb/search-indexes/normalize.ts
|
|
12207
|
+
function missingSearchIndexInfo(name) {
|
|
12208
|
+
return { name, status: "DOES_NOT_EXIST", queryable: false, typeSource: "unknown", raw: {} };
|
|
12209
|
+
}
|
|
12210
|
+
function normalizeSearchIndexInfo(doc) {
|
|
12211
|
+
const info = {
|
|
12212
|
+
name: String(doc.name),
|
|
12213
|
+
// Unknown future statuses are passed through verbatim rather than rewritten.
|
|
12214
|
+
status: typeof doc.status === "string" && doc.status.length > 0 ? doc.status : "PENDING",
|
|
12215
|
+
queryable: doc.queryable === true,
|
|
12216
|
+
typeSource: "unknown",
|
|
12217
|
+
raw: doc
|
|
12218
|
+
};
|
|
12219
|
+
if (typeof doc.id === "string") info.id = doc.id;
|
|
12220
|
+
const resolved = resolveSearchIndexType(doc);
|
|
12221
|
+
info.typeSource = resolved.typeSource;
|
|
12222
|
+
if (resolved.type !== void 0) info.type = resolved.type;
|
|
12223
|
+
if (doc.latestDefinition && typeof doc.latestDefinition === "object") info.latestDefinition = doc.latestDefinition;
|
|
12224
|
+
const version = doc.latestDefinitionVersion;
|
|
12225
|
+
if (version && typeof version === "object") {
|
|
12226
|
+
info.latestDefinitionVersion = { version: Number(version.version), createdAt: version.createdAt };
|
|
12227
|
+
}
|
|
12228
|
+
if (Array.isArray(doc.statusDetail)) info.statusDetail = doc.statusDetail;
|
|
12229
|
+
return info;
|
|
12230
|
+
}
|
|
12231
|
+
function resolveSearchIndexType(doc) {
|
|
12232
|
+
if (doc.type === "search" || doc.type === "vectorSearch") return { type: doc.type, typeSource: "server" };
|
|
12233
|
+
const definition = doc.latestDefinition;
|
|
12234
|
+
if (definition && typeof definition === "object" && !Array.isArray(definition)) {
|
|
12235
|
+
const fields = definition.fields;
|
|
12236
|
+
if (Array.isArray(fields) && fields.some((field) => field && typeof field === "object" && field.type === "vector")) {
|
|
12237
|
+
return { type: "vectorSearch", typeSource: "inferred" };
|
|
12238
|
+
}
|
|
12239
|
+
if (Object.prototype.hasOwnProperty.call(definition, "mappings")) return { type: "search", typeSource: "inferred" };
|
|
12240
|
+
}
|
|
12241
|
+
return { typeSource: "unknown" };
|
|
12242
|
+
}
|
|
12243
|
+
function matchesSearchIndexType(info, type) {
|
|
12244
|
+
return info.typeSource !== "unknown" && info.type === type;
|
|
12245
|
+
}
|
|
12246
|
+
|
|
12247
|
+
// src/adapters/mongodb/search-indexes/validate.ts
|
|
12248
|
+
var TYPES = /* @__PURE__ */ new Set(["search", "vectorSearch"]);
|
|
12249
|
+
var VECTOR_FIELD_TYPES = /* @__PURE__ */ new Set(["vector", "filter"]);
|
|
12250
|
+
var SIMILARITIES = /* @__PURE__ */ new Set(["euclidean", "cosine", "dotProduct"]);
|
|
12251
|
+
function invalid(operation, namespace, indexName, field, reason) {
|
|
12252
|
+
throw createError(
|
|
12253
|
+
ErrorCodes.INVALID_SEARCH_INDEX_DEFINITION,
|
|
12254
|
+
`${operation}: invalid ${field} (${reason})`,
|
|
12255
|
+
[{ operation, namespace, indexName: typeof indexName === "string" ? indexName : void 0, field, reason }]
|
|
12256
|
+
);
|
|
12257
|
+
}
|
|
12258
|
+
function isPlainObject4(value) {
|
|
12259
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
12260
|
+
const proto = Object.getPrototypeOf(value);
|
|
12261
|
+
return proto === Object.prototype || proto === null;
|
|
12262
|
+
}
|
|
12263
|
+
function assertIndexName(operation, namespace, name) {
|
|
12264
|
+
if (typeof name !== "string" || name.length === 0 || name.trim() !== name) {
|
|
12265
|
+
invalid(operation, namespace, name, "name", "non-empty string without leading or trailing whitespace required");
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
function assertDefinition(operation, namespace, name, definition, type) {
|
|
12269
|
+
if (!isPlainObject4(definition)) invalid(operation, namespace, name, "definition", "plain object required");
|
|
12270
|
+
if (type !== "vectorSearch") return;
|
|
12271
|
+
const fields = definition.fields;
|
|
12272
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
12273
|
+
invalid(operation, namespace, name, "definition.fields", "non-empty array required for vectorSearch");
|
|
12274
|
+
}
|
|
12275
|
+
fields.forEach((field, index) => {
|
|
12276
|
+
const path5 = `definition.fields[${index}]`;
|
|
12277
|
+
if (!isPlainObject4(field)) invalid(operation, namespace, name, path5, "object required");
|
|
12278
|
+
if (typeof field.type !== "string" || !VECTOR_FIELD_TYPES.has(field.type)) {
|
|
12279
|
+
invalid(operation, namespace, name, `${path5}.type`, 'must be "vector" or "filter"');
|
|
12280
|
+
}
|
|
12281
|
+
if (typeof field.path !== "string" || field.path.length === 0) {
|
|
12282
|
+
invalid(operation, namespace, name, `${path5}.path`, "non-empty string required");
|
|
12283
|
+
}
|
|
12284
|
+
if (field.type === "vector") {
|
|
12285
|
+
const dims = field.numDimensions;
|
|
12286
|
+
if (typeof dims !== "number" || !Number.isInteger(dims) || dims <= 0) {
|
|
12287
|
+
invalid(operation, namespace, name, `${path5}.numDimensions`, "positive integer required");
|
|
12288
|
+
}
|
|
12289
|
+
if (typeof field.similarity !== "string" || !SIMILARITIES.has(field.similarity)) {
|
|
12290
|
+
invalid(operation, namespace, name, `${path5}.similarity`, 'must be "euclidean", "cosine", or "dotProduct"');
|
|
12291
|
+
}
|
|
12292
|
+
}
|
|
12293
|
+
});
|
|
12294
|
+
}
|
|
12295
|
+
function assertSpec(operation, namespace, spec) {
|
|
12296
|
+
if (!isPlainObject4(spec)) invalid(operation, namespace, void 0, "spec", "object required");
|
|
12297
|
+
assertIndexName(operation, namespace, spec.name);
|
|
12298
|
+
if (typeof spec.type !== "string" || !TYPES.has(spec.type)) {
|
|
12299
|
+
invalid(operation, namespace, spec.name, "type", 'must be "search" or "vectorSearch"');
|
|
12300
|
+
}
|
|
12301
|
+
assertDefinition(operation, namespace, spec.name, spec.definition, spec.type);
|
|
12302
|
+
}
|
|
12303
|
+
function assertOptionsObject(operation, namespace, name, options) {
|
|
12304
|
+
if (options !== void 0 && !isPlainObject4(options)) invalid(operation, namespace, name, "options", "object required");
|
|
12305
|
+
}
|
|
12306
|
+
function assertEnumOption(operation, namespace, name, field, value, allowed) {
|
|
12307
|
+
if (value !== void 0 && (typeof value !== "string" || !allowed.includes(value))) {
|
|
12308
|
+
invalid(operation, namespace, name, field, `must be one of ${allowed.map((v) => `"${v}"`).join(", ")}`);
|
|
12309
|
+
}
|
|
12310
|
+
}
|
|
12311
|
+
function assertBooleanOption(operation, namespace, name, field, value) {
|
|
12312
|
+
if (value !== void 0 && typeof value !== "boolean") invalid(operation, namespace, name, field, "boolean required");
|
|
12313
|
+
}
|
|
12314
|
+
function resolveWaitOptions(namespace, name, options) {
|
|
12315
|
+
const operation = "waitForSearchIndex";
|
|
12316
|
+
assertOptionsObject(operation, namespace, name, options);
|
|
12317
|
+
const opts = options ?? {};
|
|
12318
|
+
assertEnumOption(operation, namespace, name, "options.until", opts.until, ["queryable", "ready", "dropped"]);
|
|
12319
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
12320
|
+
const intervalMs = opts.intervalMs ?? 2e3;
|
|
12321
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
12322
|
+
invalid(operation, namespace, name, "options.timeoutMs", "positive integer required");
|
|
12323
|
+
}
|
|
12324
|
+
if (!Number.isInteger(intervalMs) || intervalMs < 100) {
|
|
12325
|
+
invalid(operation, namespace, name, "options.intervalMs", "integer >= 100 required");
|
|
12326
|
+
}
|
|
12327
|
+
const signal = opts.signal;
|
|
12328
|
+
if (signal !== void 0 && (signal === null || typeof signal !== "object" || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function")) {
|
|
12329
|
+
invalid(operation, namespace, name, "options.signal", "AbortSignal required");
|
|
12330
|
+
}
|
|
12331
|
+
return { until: opts.until ?? "queryable", timeoutMs, intervalMs, signal };
|
|
12332
|
+
}
|
|
12333
|
+
|
|
12334
|
+
// src/adapters/mongodb/search-indexes/operations.ts
|
|
12335
|
+
var SEARCH_INDEX_MISSING_GRACE_MS = 3e4;
|
|
12336
|
+
async function fetchByName(ctx, operation, name, signal) {
|
|
12337
|
+
try {
|
|
12338
|
+
const docs = await ctx.collection.listSearchIndexes(name, signal ? { signal } : {}).toArray();
|
|
12339
|
+
const match = docs.find((doc) => doc.name === name);
|
|
12340
|
+
return match ? normalizeSearchIndexInfo(match) : missingSearchIndexInfo(name);
|
|
12341
|
+
} catch (error) {
|
|
12342
|
+
if (signal?.aborted) throw signal.reason;
|
|
12343
|
+
if (classifySearchIndexError(error) === "namespaceNotFound") return missingSearchIndexInfo(name);
|
|
12344
|
+
throw mapSearchIndexError(error, { operation, namespace: ctx.namespace, indexName: name });
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12347
|
+
async function getSearchIndexOp(ctx, name) {
|
|
12348
|
+
assertIndexName("getSearchIndex", ctx.namespace, name);
|
|
12349
|
+
return fetchByName(ctx, "getSearchIndex", name);
|
|
12350
|
+
}
|
|
12351
|
+
async function listSearchIndexesOp(ctx, options) {
|
|
12352
|
+
const operation = "listSearchIndexes";
|
|
12353
|
+
assertOptionsObject(operation, ctx.namespace, void 0, options);
|
|
12354
|
+
assertEnumOption(operation, ctx.namespace, void 0, "options.type", options?.type, ["search", "vectorSearch"]);
|
|
12355
|
+
let docs;
|
|
12356
|
+
try {
|
|
12357
|
+
docs = await ctx.collection.listSearchIndexes().toArray();
|
|
12358
|
+
} catch (error) {
|
|
12359
|
+
if (classifySearchIndexError(error) === "namespaceNotFound") return [];
|
|
12360
|
+
throw mapSearchIndexError(error, { operation, namespace: ctx.namespace });
|
|
12361
|
+
}
|
|
12362
|
+
const infos = docs.map(normalizeSearchIndexInfo);
|
|
12363
|
+
return options?.type ? infos.filter((info) => matchesSearchIndexType(info, options.type)) : infos;
|
|
12364
|
+
}
|
|
12365
|
+
async function createSearchIndexOp(ctx, spec, options) {
|
|
12366
|
+
const operation = "createSearchIndex";
|
|
12367
|
+
assertSpec(operation, ctx.namespace, spec);
|
|
12368
|
+
assertOptionsObject(operation, ctx.namespace, spec.name, options);
|
|
12369
|
+
assertEnumOption(operation, ctx.namespace, spec.name, "options.ifExists", options?.ifExists, ["error", "skip", "update"]);
|
|
12370
|
+
const ifExists = options?.ifExists ?? "error";
|
|
12371
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: spec.name };
|
|
12372
|
+
const existing = await fetchByName(ctx, operation, spec.name);
|
|
12373
|
+
if (existing.status !== "DOES_NOT_EXIST") {
|
|
12374
|
+
if (ifExists === "skip") return { name: spec.name, action: "skipped", created: false };
|
|
12375
|
+
if (ifExists === "error") {
|
|
12376
|
+
throw searchIndexError(ErrorCodes.SEARCH_INDEX_EXISTS, `Search index "${spec.name}" already exists`, errCtx);
|
|
12377
|
+
}
|
|
12378
|
+
if (existing.type !== void 0 && existing.type !== spec.type) {
|
|
12379
|
+
throw searchIndexError(
|
|
12380
|
+
ErrorCodes.INVALID_SEARCH_INDEX_DEFINITION,
|
|
12381
|
+
`Search index "${spec.name}" has type "${existing.type}"; drop it before recreating as "${spec.type}"`,
|
|
12382
|
+
errCtx,
|
|
12383
|
+
{ field: "type", reason: "type change requires drop and create" }
|
|
12384
|
+
);
|
|
12385
|
+
}
|
|
12386
|
+
try {
|
|
12387
|
+
await ctx.collection.updateSearchIndex(spec.name, spec.definition);
|
|
12388
|
+
} catch (error) {
|
|
12389
|
+
throw mapSearchIndexError(error, errCtx);
|
|
12390
|
+
}
|
|
12391
|
+
return { name: spec.name, action: "updated", created: false };
|
|
12392
|
+
}
|
|
12393
|
+
try {
|
|
12394
|
+
const name = await ctx.collection.createSearchIndex({ name: spec.name, type: spec.type, definition: spec.definition });
|
|
12395
|
+
return { name: typeof name === "string" && name.length > 0 ? name : spec.name, action: "created", created: true };
|
|
12396
|
+
} catch (error) {
|
|
12397
|
+
const mapped = mapSearchIndexError(error, errCtx);
|
|
12398
|
+
if (mapped.code === ErrorCodes.SEARCH_INDEX_EXISTS && ifExists === "skip") {
|
|
12399
|
+
return { name: spec.name, action: "skipped", created: false };
|
|
12400
|
+
}
|
|
12401
|
+
throw mapped;
|
|
12402
|
+
}
|
|
12403
|
+
}
|
|
12404
|
+
async function createSearchIndexesOp(ctx, specs) {
|
|
12405
|
+
const operation = "createSearchIndexes";
|
|
12406
|
+
if (!Array.isArray(specs) || specs.length === 0) {
|
|
12407
|
+
throw searchIndexError(
|
|
12408
|
+
ErrorCodes.INVALID_SEARCH_INDEX_DEFINITION,
|
|
12409
|
+
`${operation}: specs must be a non-empty array`,
|
|
12410
|
+
{ operation, namespace: ctx.namespace },
|
|
12411
|
+
{ field: "specs", reason: "non-empty array required" }
|
|
12412
|
+
);
|
|
12413
|
+
}
|
|
12414
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12415
|
+
for (const spec of specs) {
|
|
12416
|
+
assertSpec(operation, ctx.namespace, spec);
|
|
12417
|
+
if (seen.has(spec.name)) {
|
|
12418
|
+
throw searchIndexError(
|
|
12419
|
+
ErrorCodes.SEARCH_INDEX_EXISTS,
|
|
12420
|
+
`Search index "${spec.name}" is listed more than once`,
|
|
12421
|
+
{ operation, namespace: ctx.namespace, indexName: spec.name }
|
|
12422
|
+
);
|
|
12423
|
+
}
|
|
12424
|
+
seen.add(spec.name);
|
|
12425
|
+
}
|
|
12426
|
+
try {
|
|
12427
|
+
return await ctx.collection.createSearchIndexes(specs.map(({ name, type, definition }) => ({ name, type, definition })));
|
|
12428
|
+
} catch (error) {
|
|
12429
|
+
throw mapSearchIndexError(error, { operation, namespace: ctx.namespace });
|
|
12430
|
+
}
|
|
12431
|
+
}
|
|
12432
|
+
async function updateSearchIndexOp(ctx, name, definition) {
|
|
12433
|
+
const operation = "updateSearchIndex";
|
|
12434
|
+
assertIndexName(operation, ctx.namespace, name);
|
|
12435
|
+
assertDefinition(operation, ctx.namespace, name, definition);
|
|
12436
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: name };
|
|
12437
|
+
const existing = await fetchByName(ctx, operation, name);
|
|
12438
|
+
if (existing.status === "DOES_NOT_EXIST") {
|
|
12439
|
+
throw searchIndexError(ErrorCodes.SEARCH_INDEX_NOT_FOUND, `Search index "${name}" was not found`, errCtx);
|
|
12440
|
+
}
|
|
12441
|
+
if (existing.type !== void 0) assertDefinition(operation, ctx.namespace, name, definition, existing.type);
|
|
12442
|
+
try {
|
|
12443
|
+
await ctx.collection.updateSearchIndex(name, definition);
|
|
12444
|
+
} catch (error) {
|
|
12445
|
+
throw mapSearchIndexError(error, errCtx);
|
|
12446
|
+
}
|
|
12447
|
+
}
|
|
12448
|
+
async function dropSearchIndexOp(ctx, name, options) {
|
|
12449
|
+
const operation = "dropSearchIndex";
|
|
12450
|
+
assertIndexName(operation, ctx.namespace, name);
|
|
12451
|
+
assertOptionsObject(operation, ctx.namespace, name, options);
|
|
12452
|
+
assertBooleanOption(operation, ctx.namespace, name, "options.ignoreMissing", options?.ignoreMissing);
|
|
12453
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: name };
|
|
12454
|
+
const existing = await fetchByName(ctx, operation, name);
|
|
12455
|
+
if (existing.status === "DOES_NOT_EXIST") {
|
|
12456
|
+
if (options?.ignoreMissing) return;
|
|
12457
|
+
throw searchIndexError(ErrorCodes.SEARCH_INDEX_NOT_FOUND, `Search index "${name}" was not found`, errCtx);
|
|
12458
|
+
}
|
|
12459
|
+
try {
|
|
12460
|
+
await ctx.collection.dropSearchIndex(name);
|
|
12461
|
+
} catch (error) {
|
|
12462
|
+
const mapped = mapSearchIndexError(error, errCtx);
|
|
12463
|
+
if (mapped.code === ErrorCodes.SEARCH_INDEX_NOT_FOUND && options?.ignoreMissing) return;
|
|
12464
|
+
throw mapped;
|
|
12465
|
+
}
|
|
12466
|
+
}
|
|
12467
|
+
function sleep2(ms, signal) {
|
|
12468
|
+
return new Promise((resolve, reject) => {
|
|
12469
|
+
if (signal?.aborted) {
|
|
12470
|
+
reject(signal.reason);
|
|
12471
|
+
return;
|
|
12472
|
+
}
|
|
12473
|
+
const onAbort = () => {
|
|
12474
|
+
clearTimeout(timer);
|
|
12475
|
+
reject(signal?.reason);
|
|
12476
|
+
};
|
|
12477
|
+
const timer = setTimeout(() => {
|
|
12478
|
+
signal?.removeEventListener("abort", onAbort);
|
|
12479
|
+
resolve();
|
|
12480
|
+
}, ms);
|
|
12481
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
12482
|
+
});
|
|
12483
|
+
}
|
|
12484
|
+
function linkSignal(userSignal, remainingMs) {
|
|
12485
|
+
const controller = new AbortController();
|
|
12486
|
+
let expired = false;
|
|
12487
|
+
const onUserAbort = () => controller.abort(userSignal?.reason);
|
|
12488
|
+
userSignal?.addEventListener("abort", onUserAbort, { once: true });
|
|
12489
|
+
const timer = setTimeout(() => {
|
|
12490
|
+
expired = true;
|
|
12491
|
+
controller.abort(new Error("search index wait deadline reached"));
|
|
12492
|
+
}, Math.max(0, remainingMs));
|
|
12493
|
+
return {
|
|
12494
|
+
signal: controller.signal,
|
|
12495
|
+
timedOut: () => expired,
|
|
12496
|
+
dispose: () => {
|
|
12497
|
+
clearTimeout(timer);
|
|
12498
|
+
userSignal?.removeEventListener("abort", onUserAbort);
|
|
12499
|
+
}
|
|
12500
|
+
};
|
|
12501
|
+
}
|
|
12502
|
+
async function waitForSearchIndexOp(ctx, name, options, clock = Date.now) {
|
|
12503
|
+
const operation = "waitForSearchIndex";
|
|
12504
|
+
assertIndexName(operation, ctx.namespace, name);
|
|
12505
|
+
const { until, timeoutMs, intervalMs, signal } = resolveWaitOptions(ctx.namespace, name, options);
|
|
12506
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: name };
|
|
12507
|
+
if (signal?.aborted) throw signal.reason;
|
|
12508
|
+
const start = clock();
|
|
12509
|
+
const grace = Math.min(SEARCH_INDEX_MISSING_GRACE_MS, timeoutMs);
|
|
12510
|
+
let last;
|
|
12511
|
+
const timeoutError = () => searchIndexError(
|
|
12512
|
+
ErrorCodes.SEARCH_INDEX_WAIT_TIMEOUT,
|
|
12513
|
+
`Timed out after ${timeoutMs}ms waiting for Search index "${name}" to become ${until}`,
|
|
12514
|
+
errCtx,
|
|
12515
|
+
{ until, timeoutMs, last }
|
|
12516
|
+
);
|
|
12517
|
+
for (; ; ) {
|
|
12518
|
+
const link = linkSignal(signal, timeoutMs - (clock() - start));
|
|
12519
|
+
let info;
|
|
12520
|
+
try {
|
|
12521
|
+
info = await fetchByName(ctx, operation, name, link.signal);
|
|
12522
|
+
} catch (error) {
|
|
12523
|
+
if (signal?.aborted) throw signal.reason;
|
|
12524
|
+
if (link.timedOut()) throw timeoutError();
|
|
12525
|
+
throw error;
|
|
12526
|
+
} finally {
|
|
12527
|
+
link.dispose();
|
|
12528
|
+
}
|
|
12529
|
+
last = info;
|
|
12530
|
+
if (signal?.aborted) throw signal.reason;
|
|
12531
|
+
if (until === "dropped") {
|
|
12532
|
+
if (info.status === "DOES_NOT_EXIST") return info;
|
|
12533
|
+
} else if (info.status === "DOES_NOT_EXIST") {
|
|
12534
|
+
if (clock() - start >= grace) {
|
|
12535
|
+
throw searchIndexError(
|
|
12536
|
+
ErrorCodes.SEARCH_INDEX_NOT_FOUND,
|
|
12537
|
+
`Search index "${name}" was not found within ${grace}ms`,
|
|
12538
|
+
errCtx,
|
|
12539
|
+
{ until, graceMs: grace }
|
|
12540
|
+
);
|
|
12541
|
+
}
|
|
12542
|
+
} else if (until === "queryable") {
|
|
12543
|
+
if (info.queryable) return info;
|
|
12544
|
+
if (info.status === "FAILED") {
|
|
12545
|
+
throw searchIndexError(
|
|
12546
|
+
ErrorCodes.SEARCH_INDEX_BUILD_FAILED,
|
|
12547
|
+
`Search index "${name}" build failed`,
|
|
12548
|
+
errCtx,
|
|
12549
|
+
{ status: info.status, statusDetail: info.statusDetail }
|
|
12550
|
+
);
|
|
12551
|
+
}
|
|
12552
|
+
} else {
|
|
12553
|
+
if (info.status === "READY") return info;
|
|
12554
|
+
if (info.status === "FAILED") {
|
|
12555
|
+
throw searchIndexError(
|
|
12556
|
+
ErrorCodes.SEARCH_INDEX_BUILD_FAILED,
|
|
12557
|
+
`Search index "${name}" build failed`,
|
|
12558
|
+
errCtx,
|
|
12559
|
+
{ status: info.status, statusDetail: info.statusDetail }
|
|
12560
|
+
);
|
|
12561
|
+
}
|
|
12562
|
+
}
|
|
12563
|
+
const remaining = timeoutMs - (clock() - start);
|
|
12564
|
+
if (remaining <= 0) throw timeoutError();
|
|
12565
|
+
await sleep2(Math.min(intervalMs, remaining), signal);
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12568
|
+
|
|
12104
12569
|
// src/adapters/mongodb/common/collection-accessor-cache-helpers.ts
|
|
12105
12570
|
import { types as utilTypes4 } from "node:util";
|
|
12106
12571
|
function isRecord2(value) {
|
|
@@ -12461,7 +12926,7 @@ async function invalidateReadCachesAfterWrite(queryCache, collection, dbName, co
|
|
|
12461
12926
|
}
|
|
12462
12927
|
|
|
12463
12928
|
// src/adapters/mongodb/writes/write-utils.ts
|
|
12464
|
-
function
|
|
12929
|
+
function sleep3(ms) {
|
|
12465
12930
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12466
12931
|
}
|
|
12467
12932
|
function splitIntoBatches(items, batchSize) {
|
|
@@ -12881,7 +13346,7 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
12881
13346
|
result.retries.push(retryInfo);
|
|
12882
13347
|
onRetry?.(retryInfo);
|
|
12883
13348
|
if (retryDelay > 0) {
|
|
12884
|
-
await
|
|
13349
|
+
await sleep3(retryDelay);
|
|
12885
13350
|
}
|
|
12886
13351
|
continue;
|
|
12887
13352
|
}
|
|
@@ -13010,7 +13475,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
13010
13475
|
result.retries.push(retryInfo);
|
|
13011
13476
|
onRetry?.(retryInfo);
|
|
13012
13477
|
if (retryDelay > 0) {
|
|
13013
|
-
await
|
|
13478
|
+
await sleep3(retryDelay);
|
|
13014
13479
|
}
|
|
13015
13480
|
continue;
|
|
13016
13481
|
}
|
|
@@ -13105,7 +13570,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
13105
13570
|
result.retries.push(retryInfo);
|
|
13106
13571
|
onRetry?.(retryInfo);
|
|
13107
13572
|
if (retryDelay > 0) {
|
|
13108
|
-
await
|
|
13573
|
+
await sleep3(retryDelay);
|
|
13109
13574
|
}
|
|
13110
13575
|
continue;
|
|
13111
13576
|
}
|
|
@@ -13602,7 +14067,7 @@ var MongoCollectionAccessor = class {
|
|
|
13602
14067
|
const { query: normalizedQuery, options: driverOptions } = snapshotReadInputs(this._cvFilter(query), normalizeFindProjectionOptions(rawOptions), this.collectionRef.bsonOptions);
|
|
13603
14068
|
const cacheTTL = rawOptions.explain ? 0 : typeof rawOptions.cache === "number" ? rawOptions.cache : 0;
|
|
13604
14069
|
if (cacheTTL > 0 && this.management.queryCache && canCacheBsonResult(driverOptions) && !hasSessionOption(driverOptions) && !await isCollectionCacheBarrierActive(this.management.queryCache, this.collectionRef, this.management.defaults)) {
|
|
13605
|
-
const cacheKey = buildFindOneCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, driverOptions);
|
|
14070
|
+
const cacheKey = buildFindOneCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, driverOptions, true);
|
|
13606
14071
|
const result2 = await readThroughCache(this.management.queryCache, cacheKey, cacheTTL, () => findOneDocument(this.collectionRef, normalizedQuery, driverOptions));
|
|
13607
14072
|
return wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "findOne", rawOptions, startTs, result2);
|
|
13608
14073
|
}
|
|
@@ -13659,7 +14124,7 @@ var MongoCollectionAccessor = class {
|
|
|
13659
14124
|
);
|
|
13660
14125
|
const countQueue = this.management.defaults?.countQueue;
|
|
13661
14126
|
if (cacheTTL > 0 && this.management.queryCache && canCacheBsonCount(keyOptions) && !hasSessionOption(keyOptions) && !await isCollectionCacheBarrierActive(this.management.queryCache, this.collectionRef, this.management.defaults)) {
|
|
13662
|
-
const cacheKey = buildCountCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, keyOptions);
|
|
14127
|
+
const cacheKey = buildCountCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, keyOptions, true);
|
|
13663
14128
|
const result = await readThroughCache(
|
|
13664
14129
|
this.management.queryCache,
|
|
13665
14130
|
cacheKey,
|
|
@@ -13752,7 +14217,7 @@ var MongoCollectionAccessor = class {
|
|
|
13752
14217
|
driverOptions
|
|
13753
14218
|
);
|
|
13754
14219
|
if (cacheTTL > 0 && this.management.queryCache && canCacheBsonResult(driverOptions) && !hasSessionOption(driverOptions) && !await isCollectionCacheBarrierActive(this.management.queryCache, this.collectionRef, this.management.defaults)) {
|
|
13755
|
-
const cacheKey = buildDistinctCacheKey(this.collectionRef, this.management.defaults, key, normalizedQuery, driverOptions);
|
|
14220
|
+
const cacheKey = buildDistinctCacheKey(this.collectionRef, this.management.defaults, key, normalizedQuery, driverOptions, true);
|
|
13756
14221
|
const result2 = await readThroughCache(this.management.queryCache, cacheKey, cacheTTL, executeDistinct);
|
|
13757
14222
|
return wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "distinct", rawOptions, startTs, result2);
|
|
13758
14223
|
}
|
|
@@ -13873,6 +14338,35 @@ var MongoCollectionAccessor = class {
|
|
|
13873
14338
|
this.assertWritePath("dropIndexes", "management");
|
|
13874
14339
|
return dropIndexesForAccessor(this.collectionRef);
|
|
13875
14340
|
}
|
|
14341
|
+
/** Search index lifecycle (v3.4.0); writes are governed by writePathPolicy `management`. */
|
|
14342
|
+
searchIndexContext() {
|
|
14343
|
+
return { collection: this.collectionRef, namespace: this.getNamespace() };
|
|
14344
|
+
}
|
|
14345
|
+
async createSearchIndex(spec, options) {
|
|
14346
|
+
this.assertWritePath("createSearchIndex", "management");
|
|
14347
|
+
return createSearchIndexOp(this.searchIndexContext(), spec, options);
|
|
14348
|
+
}
|
|
14349
|
+
async createSearchIndexes(specs) {
|
|
14350
|
+
this.assertWritePath("createSearchIndexes", "management");
|
|
14351
|
+
return createSearchIndexesOp(this.searchIndexContext(), specs);
|
|
14352
|
+
}
|
|
14353
|
+
async listSearchIndexes(options) {
|
|
14354
|
+
return listSearchIndexesOp(this.searchIndexContext(), options);
|
|
14355
|
+
}
|
|
14356
|
+
async getSearchIndex(name) {
|
|
14357
|
+
return getSearchIndexOp(this.searchIndexContext(), name);
|
|
14358
|
+
}
|
|
14359
|
+
async updateSearchIndex(name, definition) {
|
|
14360
|
+
this.assertWritePath("updateSearchIndex", "management");
|
|
14361
|
+
return updateSearchIndexOp(this.searchIndexContext(), name, definition);
|
|
14362
|
+
}
|
|
14363
|
+
async dropSearchIndex(name, options) {
|
|
14364
|
+
this.assertWritePath("dropSearchIndex", "management");
|
|
14365
|
+
return dropSearchIndexOp(this.searchIndexContext(), name, options);
|
|
14366
|
+
}
|
|
14367
|
+
async waitForSearchIndex(name, options) {
|
|
14368
|
+
return waitForSearchIndexOp(this.searchIndexContext(), name, options);
|
|
14369
|
+
}
|
|
13876
14370
|
/** Pre-populates bookmark cache entries for the specified key dimensions and page numbers. */
|
|
13877
14371
|
async prewarmBookmarks(keyDims = {}, pages = []) {
|
|
13878
14372
|
return prewarmBookmarksForAccessor(this.bookmarkContext(), keyDims, pages);
|
|
@@ -14757,7 +15251,7 @@ var LockManager = class {
|
|
|
14757
15251
|
break;
|
|
14758
15252
|
}
|
|
14759
15253
|
const delay3 = retryDelay * Math.pow(retryBackoff, attempt);
|
|
14760
|
-
await
|
|
15254
|
+
await sleep4(delay3);
|
|
14761
15255
|
}
|
|
14762
15256
|
this.stats.errors += 1;
|
|
14763
15257
|
if (options.fallbackToNoLock) {
|
|
@@ -14869,7 +15363,7 @@ var LockManager = class {
|
|
|
14869
15363
|
}
|
|
14870
15364
|
}
|
|
14871
15365
|
};
|
|
14872
|
-
async function
|
|
15366
|
+
async function sleep4(ms) {
|
|
14873
15367
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
14874
15368
|
}
|
|
14875
15369
|
var DistributedCacheLockManager = class {
|
|
@@ -14994,10 +15488,10 @@ var DistributedCacheLockManager = class {
|
|
|
14994
15488
|
return new Lock(key, lockId, this, ttl, startedAt);
|
|
14995
15489
|
}
|
|
14996
15490
|
if (attempt === retryTimes) break;
|
|
14997
|
-
await
|
|
15491
|
+
await sleep4(retryDelay);
|
|
14998
15492
|
} catch (error) {
|
|
14999
15493
|
if (attempt === retryTimes) throw error;
|
|
15000
|
-
await
|
|
15494
|
+
await sleep4(retryDelay);
|
|
15001
15495
|
}
|
|
15002
15496
|
}
|
|
15003
15497
|
this.stats.errors++;
|
|
@@ -18026,7 +18520,7 @@ var DEFAULT_DATA_TASK_MAX_DOCUMENTS = 1e4;
|
|
|
18026
18520
|
var DEFAULT_DATA_TASK_BACKUP_MAX_BYTES = 256 * 1024 * 1024;
|
|
18027
18521
|
var ENVIRONMENTS = /* @__PURE__ */ new Set(["development", "test", "staging", "production", "prod", "live"]);
|
|
18028
18522
|
var PRODUCTION_ENVIRONMENTS = /* @__PURE__ */ new Set(["production", "prod", "live"]);
|
|
18029
|
-
function
|
|
18523
|
+
function invalid2(message, collection) {
|
|
18030
18524
|
throw new DataTaskJobError("INVALID_JOB", message, "validate", collection);
|
|
18031
18525
|
}
|
|
18032
18526
|
function canonicalize(value) {
|
|
@@ -18064,15 +18558,15 @@ function connectionDescriptor(value) {
|
|
|
18064
18558
|
}
|
|
18065
18559
|
function validateConnection(value, field) {
|
|
18066
18560
|
if (isDataTaskJobRuntime(value)) return;
|
|
18067
|
-
if (!isRecord3(value) || value.type !== "mongodb")
|
|
18068
|
-
if (typeof value.databaseName !== "string" || value.databaseName.trim() === "")
|
|
18069
|
-
if (!isRecord3(value.config) || typeof value.config.uri !== "string" || value.config.uri.trim() === "")
|
|
18561
|
+
if (!isRecord3(value) || value.type !== "mongodb") invalid2(`${field} must be a monSQLize runtime or MongoDB MonSQLizeOptions.`);
|
|
18562
|
+
if (typeof value.databaseName !== "string" || value.databaseName.trim() === "") invalid2(`${field}.databaseName is required.`);
|
|
18563
|
+
if (!isRecord3(value.config) || typeof value.config.uri !== "string" || value.config.uri.trim() === "") invalid2(`${field}.config.uri is required.`);
|
|
18070
18564
|
}
|
|
18071
18565
|
function uniqueFields(value, pathName, collection) {
|
|
18072
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
18566
|
+
if (!Array.isArray(value) || value.length === 0) invalid2(`${pathName} must be a non-empty string array.`, collection);
|
|
18073
18567
|
const fields = value.map((item) => typeof item === "string" ? item.trim() : "");
|
|
18074
|
-
if (fields.some((item) => item === ""))
|
|
18075
|
-
if (new Set(fields).size !== fields.length)
|
|
18568
|
+
if (fields.some((item) => item === "")) invalid2(`${pathName} must contain non-empty strings.`, collection);
|
|
18569
|
+
if (new Set(fields).size !== fields.length) invalid2(`${pathName} must not contain duplicates.`, collection);
|
|
18076
18570
|
return fields;
|
|
18077
18571
|
}
|
|
18078
18572
|
var FORBIDDEN_FIELD_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -18083,31 +18577,31 @@ function assertBsonLiteral(value, pathName, collection, stack = /* @__PURE__ */
|
|
|
18083
18577
|
const valueType = typeof value;
|
|
18084
18578
|
if (value === null || BSON_PRIMITIVE_TYPES.has(valueType)) return;
|
|
18085
18579
|
if (BSON_UNSUPPORTED_TYPES.has(valueType)) {
|
|
18086
|
-
|
|
18580
|
+
invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18087
18581
|
}
|
|
18088
18582
|
if (value instanceof Date || value instanceof RegExp || Buffer.isBuffer(value)) return;
|
|
18089
18583
|
if (Array.isArray(value)) {
|
|
18090
|
-
if (stack.has(value))
|
|
18584
|
+
if (stack.has(value)) invalid2(`${pathName} must not contain circular values.`, collection);
|
|
18091
18585
|
stack.add(value);
|
|
18092
18586
|
value.forEach((item, position) => assertBsonLiteral(item, `${pathName}[${position}]`, collection, stack));
|
|
18093
18587
|
stack.delete(value);
|
|
18094
18588
|
return;
|
|
18095
18589
|
}
|
|
18096
|
-
if (!isRecord3(value))
|
|
18590
|
+
if (!isRecord3(value)) invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18097
18591
|
if (typeof value._bsontype === "string") return;
|
|
18098
18592
|
const prototype = Object.getPrototypeOf(value);
|
|
18099
|
-
if (!PLAIN_OBJECT_PROTOTYPES.has(prototype))
|
|
18100
|
-
if (stack.has(value))
|
|
18593
|
+
if (!PLAIN_OBJECT_PROTOTYPES.has(prototype)) invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18594
|
+
if (stack.has(value)) invalid2(`${pathName} must not contain circular values.`, collection);
|
|
18101
18595
|
stack.add(value);
|
|
18102
18596
|
for (const [key, item] of Object.entries(value)) assertBsonLiteral(item, `${pathName}.${key}`, collection, stack);
|
|
18103
18597
|
stack.delete(value);
|
|
18104
18598
|
}
|
|
18105
18599
|
function normalizeFieldPath(value, pathName, collection) {
|
|
18106
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
18600
|
+
if (typeof value !== "string" || value.trim() === "") invalid2(`${pathName} must be a non-empty field path.`, collection);
|
|
18107
18601
|
const field = value.trim();
|
|
18108
18602
|
const segments = field.split(".");
|
|
18109
18603
|
if (segments.some((segment) => segment === "" || segment.startsWith("$") || FORBIDDEN_FIELD_PATH_SEGMENTS.has(segment))) {
|
|
18110
|
-
|
|
18604
|
+
invalid2(`${pathName} contains an unsafe field path.`, collection);
|
|
18111
18605
|
}
|
|
18112
18606
|
return field;
|
|
18113
18607
|
}
|
|
@@ -18123,13 +18617,13 @@ function projectionIncludes(projection, field) {
|
|
|
18123
18617
|
return included.some((key) => key === field || field.startsWith(`${key}.`));
|
|
18124
18618
|
}
|
|
18125
18619
|
function normalizeFieldPatch(data, identityFields, collection) {
|
|
18126
|
-
if (data.rename !== void 0 && !isRecord3(data.rename))
|
|
18127
|
-
if (data.set !== void 0 && !isRecord3(data.set))
|
|
18128
|
-
if (data.unset !== void 0 && !Array.isArray(data.unset))
|
|
18620
|
+
if (data.rename !== void 0 && !isRecord3(data.rename)) invalid2("data.rename must be an object.", collection);
|
|
18621
|
+
if (data.set !== void 0 && !isRecord3(data.set)) invalid2("data.set must be an object.", collection);
|
|
18622
|
+
if (data.unset !== void 0 && !Array.isArray(data.unset)) invalid2("data.unset must be an array.", collection);
|
|
18129
18623
|
const renameEntries = Object.entries(data.rename ?? {}).map(([source, destination], position) => {
|
|
18130
18624
|
const normalizedSource = normalizeFieldPath(source, `data.rename source ${position}`, collection);
|
|
18131
18625
|
const normalizedDestination = normalizeFieldPath(destination, `data.rename.${source}`, collection);
|
|
18132
|
-
if (pathsOverlap(normalizedSource, normalizedDestination))
|
|
18626
|
+
if (pathsOverlap(normalizedSource, normalizedDestination)) invalid2("data.rename source and destination must not overlap.", collection);
|
|
18133
18627
|
return [normalizedSource, normalizedDestination];
|
|
18134
18628
|
});
|
|
18135
18629
|
const setEntries = Object.entries(data.set ?? {}).map(([field, value], position) => {
|
|
@@ -18138,14 +18632,14 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18138
18632
|
try {
|
|
18139
18633
|
canonicalStringify(value);
|
|
18140
18634
|
} catch {
|
|
18141
|
-
|
|
18635
|
+
invalid2(`data.set.${field} must be a BSON-compatible literal.`, collection);
|
|
18142
18636
|
}
|
|
18143
18637
|
return [normalizedField, value];
|
|
18144
18638
|
});
|
|
18145
18639
|
const unset = (data.unset ?? []).map((field, position) => normalizeFieldPath(field, `data.unset[${position}]`, collection));
|
|
18146
|
-
if (new Set(unset).size !== unset.length)
|
|
18640
|
+
if (new Set(unset).size !== unset.length) invalid2("data.unset must not contain duplicates.", collection);
|
|
18147
18641
|
const destinations = renameEntries.map(([, destination]) => destination);
|
|
18148
|
-
if (new Set(destinations).size !== destinations.length)
|
|
18642
|
+
if (new Set(destinations).size !== destinations.length) invalid2("data.rename destinations must be unique.", collection);
|
|
18149
18643
|
const configuredPaths = [
|
|
18150
18644
|
...renameEntries.flatMap(([source, destination]) => [
|
|
18151
18645
|
{ path: source, label: `rename source "${source}"` },
|
|
@@ -18157,18 +18651,18 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18157
18651
|
for (let left = 0; left < configuredPaths.length; left += 1) {
|
|
18158
18652
|
for (let right = left + 1; right < configuredPaths.length; right += 1) {
|
|
18159
18653
|
if (pathsOverlap(configuredPaths[left].path, configuredPaths[right].path)) {
|
|
18160
|
-
|
|
18654
|
+
invalid2(`${configuredPaths[left].label} conflicts with ${configuredPaths[right].label}.`, collection);
|
|
18161
18655
|
}
|
|
18162
18656
|
}
|
|
18163
18657
|
}
|
|
18164
18658
|
for (const configured of configuredPaths) {
|
|
18165
18659
|
if (configured.path === "_id" || configured.path.startsWith("_id.") || identityFields.some((identity) => pathsOverlap(configured.path, identity))) {
|
|
18166
|
-
|
|
18660
|
+
invalid2(`${configured.label} must not overlap _id or an identity field.`, collection);
|
|
18167
18661
|
}
|
|
18168
18662
|
}
|
|
18169
18663
|
if (data.projection) {
|
|
18170
18664
|
for (const required of [...identityFields, ...renameEntries.map(([source]) => source)]) {
|
|
18171
|
-
if (!projectionIncludes(data.projection, required))
|
|
18665
|
+
if (!projectionIncludes(data.projection, required)) invalid2(`data.projection must include required field "${required}".`, collection);
|
|
18172
18666
|
}
|
|
18173
18667
|
}
|
|
18174
18668
|
return {
|
|
@@ -18179,105 +18673,105 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18179
18673
|
}
|
|
18180
18674
|
function normalizeData(data, collection) {
|
|
18181
18675
|
if (data === void 0) return void 0;
|
|
18182
|
-
if (!isRecord3(data))
|
|
18676
|
+
if (!isRecord3(data)) invalid2("data must be an object.", collection);
|
|
18183
18677
|
const hasFilter = isRecord3(data.filter) && Object.keys(data.filter).length > 0;
|
|
18184
|
-
if (hasFilter === (data.all === true))
|
|
18678
|
+
if (hasFilter === (data.all === true)) invalid2("data requires exactly one of a non-empty filter or all: true.", collection);
|
|
18185
18679
|
if (!isRecord3(data.identity) || data.identity.mode !== "fields" && data.identity.mode !== "source-id") {
|
|
18186
|
-
|
|
18680
|
+
invalid2("data.identity.mode must be fields or source-id.", collection);
|
|
18187
18681
|
}
|
|
18188
18682
|
const identity = data.identity.mode === "fields" ? { mode: "fields", fields: uniqueFields(data.identity.fields, "identity.fields", collection) } : {
|
|
18189
18683
|
mode: "source-id",
|
|
18190
18684
|
...data.identity.conflictBy === void 0 ? {} : { conflictBy: uniqueFields(data.identity.conflictBy, "identity.conflictBy", collection) }
|
|
18191
18685
|
};
|
|
18192
|
-
if (data.projection !== void 0 && !isRecord3(data.projection))
|
|
18686
|
+
if (data.projection !== void 0 && !isRecord3(data.projection)) invalid2("data.projection must be an object.", collection);
|
|
18193
18687
|
if (data.projection) assertBsonLiteral(data.projection, "data.projection", collection);
|
|
18194
18688
|
if (hasFilter) assertBsonLiteral(data.filter, "data.filter", collection);
|
|
18195
18689
|
const identityFields = identity.mode === "fields" ? identity.fields : ["_id", ...identity.conflictBy ?? []];
|
|
18196
18690
|
const patch = normalizeFieldPatch(data, identityFields, collection);
|
|
18197
18691
|
const batchSize = data.batchSize ?? 500;
|
|
18198
|
-
if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 1e4)
|
|
18692
|
+
if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 1e4) invalid2("data.batchSize must be an integer from 1 to 10000.", collection);
|
|
18199
18693
|
const maxDocuments = data.maxDocuments ?? DEFAULT_DATA_TASK_MAX_DOCUMENTS;
|
|
18200
18694
|
if (!Number.isInteger(maxDocuments) || maxDocuments < 1 || maxDocuments > 1e6) {
|
|
18201
|
-
|
|
18695
|
+
invalid2("data.maxDocuments must be an integer from 1 to 1000000.", collection);
|
|
18202
18696
|
}
|
|
18203
18697
|
const strategy = data.strategy ?? "upsert";
|
|
18204
|
-
if (strategy !== "upsert" && strategy !== "insert")
|
|
18698
|
+
if (strategy !== "upsert" && strategy !== "insert") invalid2("data.strategy must be upsert or insert.", collection);
|
|
18205
18699
|
return { ...data, ...patch, filter: hasFilter ? data.filter : {}, all: data.all === true, identity, strategy, batchSize, maxDocuments };
|
|
18206
18700
|
}
|
|
18207
18701
|
function normalizeIndexes(indexes, collection) {
|
|
18208
18702
|
if (indexes === void 0) return [];
|
|
18209
|
-
if (!Array.isArray(indexes))
|
|
18703
|
+
if (!Array.isArray(indexes)) invalid2("indexes must be an array.", collection);
|
|
18210
18704
|
const names = /* @__PURE__ */ new Set();
|
|
18211
18705
|
return indexes.map((index, position) => {
|
|
18212
|
-
if (!isRecord3(index) || !isRecord3(index.key) || Object.keys(index.key).length === 0)
|
|
18706
|
+
if (!isRecord3(index) || !isRecord3(index.key) || Object.keys(index.key).length === 0) invalid2(`indexes[${position}].key must be non-empty.`, collection);
|
|
18213
18707
|
assertBsonLiteral(index.key, `indexes[${position}].key`, collection);
|
|
18214
|
-
if (index.name !== void 0 && (typeof index.name !== "string" || index.name.trim() === ""))
|
|
18215
|
-
if (index.name && names.has(index.name))
|
|
18708
|
+
if (index.name !== void 0 && (typeof index.name !== "string" || index.name.trim() === "")) invalid2(`indexes[${position}].name must be non-empty.`, collection);
|
|
18709
|
+
if (index.name && names.has(index.name)) invalid2(`duplicate index name "${index.name}".`, collection);
|
|
18216
18710
|
if (index.name) names.add(index.name);
|
|
18217
|
-
if (index.options !== void 0 && !isRecord3(index.options))
|
|
18711
|
+
if (index.options !== void 0 && !isRecord3(index.options)) invalid2(`indexes[${position}].options must be an object.`, collection);
|
|
18218
18712
|
if (index.options) assertBsonLiteral(index.options, `indexes[${position}].options`, collection);
|
|
18219
|
-
if (index.options && "name" in index.options)
|
|
18220
|
-
if (Object.keys(index.key).length === 1 && index.key._id === 1 || index.name === "_id_")
|
|
18713
|
+
if (index.options && "name" in index.options) invalid2(`indexes[${position}].options.name is not allowed; use indexes[${position}].name.`, collection);
|
|
18714
|
+
if (Object.keys(index.key).length === 1 && index.key._id === 1 || index.name === "_id_") invalid2("indexes must not declare the built-in _id index.", collection);
|
|
18221
18715
|
return { key: { ...index.key }, ...index.name ? { name: index.name } : {}, options: { ...index.options ?? {} } };
|
|
18222
18716
|
});
|
|
18223
18717
|
}
|
|
18224
18718
|
function normalizeVerify(verify, collection) {
|
|
18225
|
-
if (verify !== void 0 && !isRecord3(verify))
|
|
18719
|
+
if (verify !== void 0 && !isRecord3(verify)) invalid2("verify must be an object.", collection);
|
|
18226
18720
|
const input = verify;
|
|
18227
18721
|
const mode = input?.mode ?? "sample";
|
|
18228
|
-
if (mode !== "sample" && mode !== "full")
|
|
18722
|
+
if (mode !== "sample" && mode !== "full") invalid2("verify.mode must be sample or full.", collection);
|
|
18229
18723
|
const sampleSize = input?.sampleSize ?? 20;
|
|
18230
|
-
if (!Number.isInteger(sampleSize) || sampleSize < 1 || sampleSize > 1e3)
|
|
18724
|
+
if (!Number.isInteger(sampleSize) || sampleSize < 1 || sampleSize > 1e3) invalid2("verify.sampleSize must be an integer from 1 to 1000.", collection);
|
|
18231
18725
|
const fields = input?.fields === void 0 ? void 0 : uniqueFields(input.fields, "verify.fields", collection);
|
|
18232
18726
|
return { mode, sampleSize, ...fields ? { fields } : {} };
|
|
18233
18727
|
}
|
|
18234
18728
|
function normalizeBackup(backup, production) {
|
|
18235
|
-
if (backup !== void 0 && !isRecord3(backup))
|
|
18729
|
+
if (backup !== void 0 && !isRecord3(backup)) invalid2("backup must be an object.");
|
|
18236
18730
|
const input = backup;
|
|
18237
|
-
if (production && (typeof input?.dir !== "string" || input.dir.trim() === ""))
|
|
18731
|
+
if (production && (typeof input?.dir !== "string" || input.dir.trim() === "")) invalid2("production jobs require an explicit durable backup.dir.");
|
|
18238
18732
|
const dir = typeof input?.dir === "string" && input.dir.trim() ? input.dir : path3.join(os2.tmpdir(), "monsqlize-data-tasks");
|
|
18239
18733
|
const compression = input?.compression ?? "gzip";
|
|
18240
|
-
if (compression !== "gzip" && compression !== "none")
|
|
18241
|
-
if (input?.format !== void 0 && input.format !== "extended-jsonl")
|
|
18734
|
+
if (compression !== "gzip" && compression !== "none") invalid2("backup.compression must be gzip or none.");
|
|
18735
|
+
if (input?.format !== void 0 && input.format !== "extended-jsonl") invalid2("backup.format must be extended-jsonl.");
|
|
18242
18736
|
const retentionDays = input?.retentionDays ?? 7;
|
|
18243
|
-
if (!Number.isInteger(retentionDays) || retentionDays < 1)
|
|
18737
|
+
if (!Number.isInteger(retentionDays) || retentionDays < 1) invalid2("backup.retentionDays must be a positive integer.");
|
|
18244
18738
|
const maxBytes = input?.maxBytes ?? DEFAULT_DATA_TASK_BACKUP_MAX_BYTES;
|
|
18245
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
18739
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) invalid2("backup.maxBytes must be a positive safe integer.");
|
|
18246
18740
|
return { dir, format: "extended-jsonl", compression, retentionDays, maxBytes };
|
|
18247
18741
|
}
|
|
18248
18742
|
function normalizeLock(lock) {
|
|
18249
18743
|
if (!lock) return false;
|
|
18250
|
-
if (lock !== true && !isRecord3(lock))
|
|
18744
|
+
if (lock !== true && !isRecord3(lock)) invalid2("lock must be a boolean or lease options object.");
|
|
18251
18745
|
const options = lock === true ? {} : lock;
|
|
18252
18746
|
const ttlMs = options.ttlMs ?? 12e4;
|
|
18253
18747
|
const waitTimeoutMs = options.waitTimeoutMs ?? 0;
|
|
18254
|
-
if (!Number.isInteger(ttlMs) || ttlMs < 1e3)
|
|
18255
|
-
if (!Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 0)
|
|
18748
|
+
if (!Number.isInteger(ttlMs) || ttlMs < 1e3) invalid2("lock.ttlMs must be an integer of at least 1000.");
|
|
18749
|
+
if (!Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 0) invalid2("lock.waitTimeoutMs must be a non-negative integer.");
|
|
18256
18750
|
return { ttlMs, waitTimeoutMs };
|
|
18257
18751
|
}
|
|
18258
18752
|
function normalizeDataTaskJob(job) {
|
|
18259
|
-
if (!isRecord3(job))
|
|
18260
|
-
if (typeof job.name !== "string" || job.name.trim() === "")
|
|
18753
|
+
if (!isRecord3(job)) invalid2("job must be an object.");
|
|
18754
|
+
if (typeof job.name !== "string" || job.name.trim() === "") invalid2("job.name is required.");
|
|
18261
18755
|
validateConnection(job.source, "source");
|
|
18262
18756
|
validateConnection(job.target, "target");
|
|
18263
|
-
if (job.source === job.target)
|
|
18757
|
+
if (job.source === job.target) invalid2("source and target must be independent runtimes or connection options.");
|
|
18264
18758
|
const sourceOptions = connectionOptions(job.source);
|
|
18265
18759
|
const targetOptions = connectionOptions(job.target);
|
|
18266
|
-
if (sourceOptions && targetOptions && sourceOptions.databaseName === targetOptions.databaseName && sourceOptions.config?.uri === targetOptions.config?.uri)
|
|
18760
|
+
if (sourceOptions && targetOptions && sourceOptions.databaseName === targetOptions.databaseName && sourceOptions.config?.uri === targetOptions.config?.uri) invalid2("source and target resolve to the same MongoDB database.");
|
|
18267
18761
|
if (typeof job.targetEnvironment !== "string" || !ENVIRONMENTS.has(job.targetEnvironment.toLowerCase())) {
|
|
18268
|
-
|
|
18762
|
+
invalid2("targetEnvironment must be development, test, staging, production, prod, or live.");
|
|
18269
18763
|
}
|
|
18270
|
-
if (!Array.isArray(job.collections) || job.collections.length === 0)
|
|
18764
|
+
if (!Array.isArray(job.collections) || job.collections.length === 0) invalid2("collections must be a non-empty array.");
|
|
18271
18765
|
const targetNames = /* @__PURE__ */ new Set();
|
|
18272
18766
|
const collections = job.collections.map((collection, position) => {
|
|
18273
|
-
if (!isRecord3(collection) || typeof collection.name !== "string" || collection.name.trim() === "")
|
|
18767
|
+
if (!isRecord3(collection) || typeof collection.name !== "string" || collection.name.trim() === "") invalid2(`collections[${position}].name is required.`);
|
|
18274
18768
|
const name = collection.name.trim();
|
|
18275
18769
|
const targetName = typeof collection.targetName === "string" && collection.targetName.trim() ? collection.targetName.trim() : name;
|
|
18276
|
-
if (targetNames.has(targetName))
|
|
18770
|
+
if (targetNames.has(targetName)) invalid2(`target collection "${targetName}" is configured more than once.`, name);
|
|
18277
18771
|
targetNames.add(targetName);
|
|
18278
18772
|
const indexes = normalizeIndexes(collection.indexes, name);
|
|
18279
18773
|
const data = normalizeData(collection.data, name);
|
|
18280
|
-
if (indexes.length === 0 && !data)
|
|
18774
|
+
if (indexes.length === 0 && !data) invalid2("each collection requires indexes or data.", name);
|
|
18281
18775
|
return { name, targetName, indexes, ...data ? { data } : {}, verify: normalizeVerify(collection.verify, name) };
|
|
18282
18776
|
});
|
|
18283
18777
|
const production = PRODUCTION_ENVIRONMENTS.has(job.targetEnvironment.toLowerCase());
|