monsqlize 3.3.4 → 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 +4 -3
- package/README.md +1 -1
- package/changelogs/v3.4.0.md +28 -0
- package/dist/cjs/cli/data-task.cjs +4 -2
- package/dist/cjs/index.cjs +559 -75
- package/dist/cjs/transaction/Transaction.cjs +7 -0
- package/dist/cjs/transaction/TransactionManager.cjs +7 -0
- package/dist/esm/index.mjs +559 -75
- 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 +4 -2
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
|
}
|
|
@@ -12111,6 +12140,432 @@ function mapVectorSearchRows(rows) {
|
|
|
12111
12140
|
});
|
|
12112
12141
|
}
|
|
12113
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
|
+
|
|
12114
12569
|
// src/adapters/mongodb/common/collection-accessor-cache-helpers.ts
|
|
12115
12570
|
import { types as utilTypes4 } from "node:util";
|
|
12116
12571
|
function isRecord2(value) {
|
|
@@ -12471,7 +12926,7 @@ async function invalidateReadCachesAfterWrite(queryCache, collection, dbName, co
|
|
|
12471
12926
|
}
|
|
12472
12927
|
|
|
12473
12928
|
// src/adapters/mongodb/writes/write-utils.ts
|
|
12474
|
-
function
|
|
12929
|
+
function sleep3(ms) {
|
|
12475
12930
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12476
12931
|
}
|
|
12477
12932
|
function splitIntoBatches(items, batchSize) {
|
|
@@ -12891,7 +13346,7 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
12891
13346
|
result.retries.push(retryInfo);
|
|
12892
13347
|
onRetry?.(retryInfo);
|
|
12893
13348
|
if (retryDelay > 0) {
|
|
12894
|
-
await
|
|
13349
|
+
await sleep3(retryDelay);
|
|
12895
13350
|
}
|
|
12896
13351
|
continue;
|
|
12897
13352
|
}
|
|
@@ -13020,7 +13475,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
13020
13475
|
result.retries.push(retryInfo);
|
|
13021
13476
|
onRetry?.(retryInfo);
|
|
13022
13477
|
if (retryDelay > 0) {
|
|
13023
|
-
await
|
|
13478
|
+
await sleep3(retryDelay);
|
|
13024
13479
|
}
|
|
13025
13480
|
continue;
|
|
13026
13481
|
}
|
|
@@ -13115,7 +13570,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
13115
13570
|
result.retries.push(retryInfo);
|
|
13116
13571
|
onRetry?.(retryInfo);
|
|
13117
13572
|
if (retryDelay > 0) {
|
|
13118
|
-
await
|
|
13573
|
+
await sleep3(retryDelay);
|
|
13119
13574
|
}
|
|
13120
13575
|
continue;
|
|
13121
13576
|
}
|
|
@@ -13883,6 +14338,35 @@ var MongoCollectionAccessor = class {
|
|
|
13883
14338
|
this.assertWritePath("dropIndexes", "management");
|
|
13884
14339
|
return dropIndexesForAccessor(this.collectionRef);
|
|
13885
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
|
+
}
|
|
13886
14370
|
/** Pre-populates bookmark cache entries for the specified key dimensions and page numbers. */
|
|
13887
14371
|
async prewarmBookmarks(keyDims = {}, pages = []) {
|
|
13888
14372
|
return prewarmBookmarksForAccessor(this.bookmarkContext(), keyDims, pages);
|
|
@@ -14767,7 +15251,7 @@ var LockManager = class {
|
|
|
14767
15251
|
break;
|
|
14768
15252
|
}
|
|
14769
15253
|
const delay3 = retryDelay * Math.pow(retryBackoff, attempt);
|
|
14770
|
-
await
|
|
15254
|
+
await sleep4(delay3);
|
|
14771
15255
|
}
|
|
14772
15256
|
this.stats.errors += 1;
|
|
14773
15257
|
if (options.fallbackToNoLock) {
|
|
@@ -14879,7 +15363,7 @@ var LockManager = class {
|
|
|
14879
15363
|
}
|
|
14880
15364
|
}
|
|
14881
15365
|
};
|
|
14882
|
-
async function
|
|
15366
|
+
async function sleep4(ms) {
|
|
14883
15367
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
14884
15368
|
}
|
|
14885
15369
|
var DistributedCacheLockManager = class {
|
|
@@ -15004,10 +15488,10 @@ var DistributedCacheLockManager = class {
|
|
|
15004
15488
|
return new Lock(key, lockId, this, ttl, startedAt);
|
|
15005
15489
|
}
|
|
15006
15490
|
if (attempt === retryTimes) break;
|
|
15007
|
-
await
|
|
15491
|
+
await sleep4(retryDelay);
|
|
15008
15492
|
} catch (error) {
|
|
15009
15493
|
if (attempt === retryTimes) throw error;
|
|
15010
|
-
await
|
|
15494
|
+
await sleep4(retryDelay);
|
|
15011
15495
|
}
|
|
15012
15496
|
}
|
|
15013
15497
|
this.stats.errors++;
|
|
@@ -18036,7 +18520,7 @@ var DEFAULT_DATA_TASK_MAX_DOCUMENTS = 1e4;
|
|
|
18036
18520
|
var DEFAULT_DATA_TASK_BACKUP_MAX_BYTES = 256 * 1024 * 1024;
|
|
18037
18521
|
var ENVIRONMENTS = /* @__PURE__ */ new Set(["development", "test", "staging", "production", "prod", "live"]);
|
|
18038
18522
|
var PRODUCTION_ENVIRONMENTS = /* @__PURE__ */ new Set(["production", "prod", "live"]);
|
|
18039
|
-
function
|
|
18523
|
+
function invalid2(message, collection) {
|
|
18040
18524
|
throw new DataTaskJobError("INVALID_JOB", message, "validate", collection);
|
|
18041
18525
|
}
|
|
18042
18526
|
function canonicalize(value) {
|
|
@@ -18074,15 +18558,15 @@ function connectionDescriptor(value) {
|
|
|
18074
18558
|
}
|
|
18075
18559
|
function validateConnection(value, field) {
|
|
18076
18560
|
if (isDataTaskJobRuntime(value)) return;
|
|
18077
|
-
if (!isRecord3(value) || value.type !== "mongodb")
|
|
18078
|
-
if (typeof value.databaseName !== "string" || value.databaseName.trim() === "")
|
|
18079
|
-
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.`);
|
|
18080
18564
|
}
|
|
18081
18565
|
function uniqueFields(value, pathName, collection) {
|
|
18082
|
-
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);
|
|
18083
18567
|
const fields = value.map((item) => typeof item === "string" ? item.trim() : "");
|
|
18084
|
-
if (fields.some((item) => item === ""))
|
|
18085
|
-
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);
|
|
18086
18570
|
return fields;
|
|
18087
18571
|
}
|
|
18088
18572
|
var FORBIDDEN_FIELD_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -18093,31 +18577,31 @@ function assertBsonLiteral(value, pathName, collection, stack = /* @__PURE__ */
|
|
|
18093
18577
|
const valueType = typeof value;
|
|
18094
18578
|
if (value === null || BSON_PRIMITIVE_TYPES.has(valueType)) return;
|
|
18095
18579
|
if (BSON_UNSUPPORTED_TYPES.has(valueType)) {
|
|
18096
|
-
|
|
18580
|
+
invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18097
18581
|
}
|
|
18098
18582
|
if (value instanceof Date || value instanceof RegExp || Buffer.isBuffer(value)) return;
|
|
18099
18583
|
if (Array.isArray(value)) {
|
|
18100
|
-
if (stack.has(value))
|
|
18584
|
+
if (stack.has(value)) invalid2(`${pathName} must not contain circular values.`, collection);
|
|
18101
18585
|
stack.add(value);
|
|
18102
18586
|
value.forEach((item, position) => assertBsonLiteral(item, `${pathName}[${position}]`, collection, stack));
|
|
18103
18587
|
stack.delete(value);
|
|
18104
18588
|
return;
|
|
18105
18589
|
}
|
|
18106
|
-
if (!isRecord3(value))
|
|
18590
|
+
if (!isRecord3(value)) invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18107
18591
|
if (typeof value._bsontype === "string") return;
|
|
18108
18592
|
const prototype = Object.getPrototypeOf(value);
|
|
18109
|
-
if (!PLAIN_OBJECT_PROTOTYPES.has(prototype))
|
|
18110
|
-
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);
|
|
18111
18595
|
stack.add(value);
|
|
18112
18596
|
for (const [key, item] of Object.entries(value)) assertBsonLiteral(item, `${pathName}.${key}`, collection, stack);
|
|
18113
18597
|
stack.delete(value);
|
|
18114
18598
|
}
|
|
18115
18599
|
function normalizeFieldPath(value, pathName, collection) {
|
|
18116
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
18600
|
+
if (typeof value !== "string" || value.trim() === "") invalid2(`${pathName} must be a non-empty field path.`, collection);
|
|
18117
18601
|
const field = value.trim();
|
|
18118
18602
|
const segments = field.split(".");
|
|
18119
18603
|
if (segments.some((segment) => segment === "" || segment.startsWith("$") || FORBIDDEN_FIELD_PATH_SEGMENTS.has(segment))) {
|
|
18120
|
-
|
|
18604
|
+
invalid2(`${pathName} contains an unsafe field path.`, collection);
|
|
18121
18605
|
}
|
|
18122
18606
|
return field;
|
|
18123
18607
|
}
|
|
@@ -18133,13 +18617,13 @@ function projectionIncludes(projection, field) {
|
|
|
18133
18617
|
return included.some((key) => key === field || field.startsWith(`${key}.`));
|
|
18134
18618
|
}
|
|
18135
18619
|
function normalizeFieldPatch(data, identityFields, collection) {
|
|
18136
|
-
if (data.rename !== void 0 && !isRecord3(data.rename))
|
|
18137
|
-
if (data.set !== void 0 && !isRecord3(data.set))
|
|
18138
|
-
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);
|
|
18139
18623
|
const renameEntries = Object.entries(data.rename ?? {}).map(([source, destination], position) => {
|
|
18140
18624
|
const normalizedSource = normalizeFieldPath(source, `data.rename source ${position}`, collection);
|
|
18141
18625
|
const normalizedDestination = normalizeFieldPath(destination, `data.rename.${source}`, collection);
|
|
18142
|
-
if (pathsOverlap(normalizedSource, normalizedDestination))
|
|
18626
|
+
if (pathsOverlap(normalizedSource, normalizedDestination)) invalid2("data.rename source and destination must not overlap.", collection);
|
|
18143
18627
|
return [normalizedSource, normalizedDestination];
|
|
18144
18628
|
});
|
|
18145
18629
|
const setEntries = Object.entries(data.set ?? {}).map(([field, value], position) => {
|
|
@@ -18148,14 +18632,14 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18148
18632
|
try {
|
|
18149
18633
|
canonicalStringify(value);
|
|
18150
18634
|
} catch {
|
|
18151
|
-
|
|
18635
|
+
invalid2(`data.set.${field} must be a BSON-compatible literal.`, collection);
|
|
18152
18636
|
}
|
|
18153
18637
|
return [normalizedField, value];
|
|
18154
18638
|
});
|
|
18155
18639
|
const unset = (data.unset ?? []).map((field, position) => normalizeFieldPath(field, `data.unset[${position}]`, collection));
|
|
18156
|
-
if (new Set(unset).size !== unset.length)
|
|
18640
|
+
if (new Set(unset).size !== unset.length) invalid2("data.unset must not contain duplicates.", collection);
|
|
18157
18641
|
const destinations = renameEntries.map(([, destination]) => destination);
|
|
18158
|
-
if (new Set(destinations).size !== destinations.length)
|
|
18642
|
+
if (new Set(destinations).size !== destinations.length) invalid2("data.rename destinations must be unique.", collection);
|
|
18159
18643
|
const configuredPaths = [
|
|
18160
18644
|
...renameEntries.flatMap(([source, destination]) => [
|
|
18161
18645
|
{ path: source, label: `rename source "${source}"` },
|
|
@@ -18167,18 +18651,18 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18167
18651
|
for (let left = 0; left < configuredPaths.length; left += 1) {
|
|
18168
18652
|
for (let right = left + 1; right < configuredPaths.length; right += 1) {
|
|
18169
18653
|
if (pathsOverlap(configuredPaths[left].path, configuredPaths[right].path)) {
|
|
18170
|
-
|
|
18654
|
+
invalid2(`${configuredPaths[left].label} conflicts with ${configuredPaths[right].label}.`, collection);
|
|
18171
18655
|
}
|
|
18172
18656
|
}
|
|
18173
18657
|
}
|
|
18174
18658
|
for (const configured of configuredPaths) {
|
|
18175
18659
|
if (configured.path === "_id" || configured.path.startsWith("_id.") || identityFields.some((identity) => pathsOverlap(configured.path, identity))) {
|
|
18176
|
-
|
|
18660
|
+
invalid2(`${configured.label} must not overlap _id or an identity field.`, collection);
|
|
18177
18661
|
}
|
|
18178
18662
|
}
|
|
18179
18663
|
if (data.projection) {
|
|
18180
18664
|
for (const required of [...identityFields, ...renameEntries.map(([source]) => source)]) {
|
|
18181
|
-
if (!projectionIncludes(data.projection, required))
|
|
18665
|
+
if (!projectionIncludes(data.projection, required)) invalid2(`data.projection must include required field "${required}".`, collection);
|
|
18182
18666
|
}
|
|
18183
18667
|
}
|
|
18184
18668
|
return {
|
|
@@ -18189,105 +18673,105 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18189
18673
|
}
|
|
18190
18674
|
function normalizeData(data, collection) {
|
|
18191
18675
|
if (data === void 0) return void 0;
|
|
18192
|
-
if (!isRecord3(data))
|
|
18676
|
+
if (!isRecord3(data)) invalid2("data must be an object.", collection);
|
|
18193
18677
|
const hasFilter = isRecord3(data.filter) && Object.keys(data.filter).length > 0;
|
|
18194
|
-
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);
|
|
18195
18679
|
if (!isRecord3(data.identity) || data.identity.mode !== "fields" && data.identity.mode !== "source-id") {
|
|
18196
|
-
|
|
18680
|
+
invalid2("data.identity.mode must be fields or source-id.", collection);
|
|
18197
18681
|
}
|
|
18198
18682
|
const identity = data.identity.mode === "fields" ? { mode: "fields", fields: uniqueFields(data.identity.fields, "identity.fields", collection) } : {
|
|
18199
18683
|
mode: "source-id",
|
|
18200
18684
|
...data.identity.conflictBy === void 0 ? {} : { conflictBy: uniqueFields(data.identity.conflictBy, "identity.conflictBy", collection) }
|
|
18201
18685
|
};
|
|
18202
|
-
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);
|
|
18203
18687
|
if (data.projection) assertBsonLiteral(data.projection, "data.projection", collection);
|
|
18204
18688
|
if (hasFilter) assertBsonLiteral(data.filter, "data.filter", collection);
|
|
18205
18689
|
const identityFields = identity.mode === "fields" ? identity.fields : ["_id", ...identity.conflictBy ?? []];
|
|
18206
18690
|
const patch = normalizeFieldPatch(data, identityFields, collection);
|
|
18207
18691
|
const batchSize = data.batchSize ?? 500;
|
|
18208
|
-
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);
|
|
18209
18693
|
const maxDocuments = data.maxDocuments ?? DEFAULT_DATA_TASK_MAX_DOCUMENTS;
|
|
18210
18694
|
if (!Number.isInteger(maxDocuments) || maxDocuments < 1 || maxDocuments > 1e6) {
|
|
18211
|
-
|
|
18695
|
+
invalid2("data.maxDocuments must be an integer from 1 to 1000000.", collection);
|
|
18212
18696
|
}
|
|
18213
18697
|
const strategy = data.strategy ?? "upsert";
|
|
18214
|
-
if (strategy !== "upsert" && strategy !== "insert")
|
|
18698
|
+
if (strategy !== "upsert" && strategy !== "insert") invalid2("data.strategy must be upsert or insert.", collection);
|
|
18215
18699
|
return { ...data, ...patch, filter: hasFilter ? data.filter : {}, all: data.all === true, identity, strategy, batchSize, maxDocuments };
|
|
18216
18700
|
}
|
|
18217
18701
|
function normalizeIndexes(indexes, collection) {
|
|
18218
18702
|
if (indexes === void 0) return [];
|
|
18219
|
-
if (!Array.isArray(indexes))
|
|
18703
|
+
if (!Array.isArray(indexes)) invalid2("indexes must be an array.", collection);
|
|
18220
18704
|
const names = /* @__PURE__ */ new Set();
|
|
18221
18705
|
return indexes.map((index, position) => {
|
|
18222
|
-
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);
|
|
18223
18707
|
assertBsonLiteral(index.key, `indexes[${position}].key`, collection);
|
|
18224
|
-
if (index.name !== void 0 && (typeof index.name !== "string" || index.name.trim() === ""))
|
|
18225
|
-
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);
|
|
18226
18710
|
if (index.name) names.add(index.name);
|
|
18227
|
-
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);
|
|
18228
18712
|
if (index.options) assertBsonLiteral(index.options, `indexes[${position}].options`, collection);
|
|
18229
|
-
if (index.options && "name" in index.options)
|
|
18230
|
-
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);
|
|
18231
18715
|
return { key: { ...index.key }, ...index.name ? { name: index.name } : {}, options: { ...index.options ?? {} } };
|
|
18232
18716
|
});
|
|
18233
18717
|
}
|
|
18234
18718
|
function normalizeVerify(verify, collection) {
|
|
18235
|
-
if (verify !== void 0 && !isRecord3(verify))
|
|
18719
|
+
if (verify !== void 0 && !isRecord3(verify)) invalid2("verify must be an object.", collection);
|
|
18236
18720
|
const input = verify;
|
|
18237
18721
|
const mode = input?.mode ?? "sample";
|
|
18238
|
-
if (mode !== "sample" && mode !== "full")
|
|
18722
|
+
if (mode !== "sample" && mode !== "full") invalid2("verify.mode must be sample or full.", collection);
|
|
18239
18723
|
const sampleSize = input?.sampleSize ?? 20;
|
|
18240
|
-
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);
|
|
18241
18725
|
const fields = input?.fields === void 0 ? void 0 : uniqueFields(input.fields, "verify.fields", collection);
|
|
18242
18726
|
return { mode, sampleSize, ...fields ? { fields } : {} };
|
|
18243
18727
|
}
|
|
18244
18728
|
function normalizeBackup(backup, production) {
|
|
18245
|
-
if (backup !== void 0 && !isRecord3(backup))
|
|
18729
|
+
if (backup !== void 0 && !isRecord3(backup)) invalid2("backup must be an object.");
|
|
18246
18730
|
const input = backup;
|
|
18247
|
-
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.");
|
|
18248
18732
|
const dir = typeof input?.dir === "string" && input.dir.trim() ? input.dir : path3.join(os2.tmpdir(), "monsqlize-data-tasks");
|
|
18249
18733
|
const compression = input?.compression ?? "gzip";
|
|
18250
|
-
if (compression !== "gzip" && compression !== "none")
|
|
18251
|
-
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.");
|
|
18252
18736
|
const retentionDays = input?.retentionDays ?? 7;
|
|
18253
|
-
if (!Number.isInteger(retentionDays) || retentionDays < 1)
|
|
18737
|
+
if (!Number.isInteger(retentionDays) || retentionDays < 1) invalid2("backup.retentionDays must be a positive integer.");
|
|
18254
18738
|
const maxBytes = input?.maxBytes ?? DEFAULT_DATA_TASK_BACKUP_MAX_BYTES;
|
|
18255
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
18739
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) invalid2("backup.maxBytes must be a positive safe integer.");
|
|
18256
18740
|
return { dir, format: "extended-jsonl", compression, retentionDays, maxBytes };
|
|
18257
18741
|
}
|
|
18258
18742
|
function normalizeLock(lock) {
|
|
18259
18743
|
if (!lock) return false;
|
|
18260
|
-
if (lock !== true && !isRecord3(lock))
|
|
18744
|
+
if (lock !== true && !isRecord3(lock)) invalid2("lock must be a boolean or lease options object.");
|
|
18261
18745
|
const options = lock === true ? {} : lock;
|
|
18262
18746
|
const ttlMs = options.ttlMs ?? 12e4;
|
|
18263
18747
|
const waitTimeoutMs = options.waitTimeoutMs ?? 0;
|
|
18264
|
-
if (!Number.isInteger(ttlMs) || ttlMs < 1e3)
|
|
18265
|
-
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.");
|
|
18266
18750
|
return { ttlMs, waitTimeoutMs };
|
|
18267
18751
|
}
|
|
18268
18752
|
function normalizeDataTaskJob(job) {
|
|
18269
|
-
if (!isRecord3(job))
|
|
18270
|
-
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.");
|
|
18271
18755
|
validateConnection(job.source, "source");
|
|
18272
18756
|
validateConnection(job.target, "target");
|
|
18273
|
-
if (job.source === job.target)
|
|
18757
|
+
if (job.source === job.target) invalid2("source and target must be independent runtimes or connection options.");
|
|
18274
18758
|
const sourceOptions = connectionOptions(job.source);
|
|
18275
18759
|
const targetOptions = connectionOptions(job.target);
|
|
18276
|
-
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.");
|
|
18277
18761
|
if (typeof job.targetEnvironment !== "string" || !ENVIRONMENTS.has(job.targetEnvironment.toLowerCase())) {
|
|
18278
|
-
|
|
18762
|
+
invalid2("targetEnvironment must be development, test, staging, production, prod, or live.");
|
|
18279
18763
|
}
|
|
18280
|
-
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.");
|
|
18281
18765
|
const targetNames = /* @__PURE__ */ new Set();
|
|
18282
18766
|
const collections = job.collections.map((collection, position) => {
|
|
18283
|
-
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.`);
|
|
18284
18768
|
const name = collection.name.trim();
|
|
18285
18769
|
const targetName = typeof collection.targetName === "string" && collection.targetName.trim() ? collection.targetName.trim() : name;
|
|
18286
|
-
if (targetNames.has(targetName))
|
|
18770
|
+
if (targetNames.has(targetName)) invalid2(`target collection "${targetName}" is configured more than once.`, name);
|
|
18287
18771
|
targetNames.add(targetName);
|
|
18288
18772
|
const indexes = normalizeIndexes(collection.indexes, name);
|
|
18289
18773
|
const data = normalizeData(collection.data, name);
|
|
18290
|
-
if (indexes.length === 0 && !data)
|
|
18774
|
+
if (indexes.length === 0 && !data) invalid2("each collection requires indexes or data.", name);
|
|
18291
18775
|
return { name, targetName, indexes, ...data ? { data } : {}, verify: normalizeVerify(collection.verify, name) };
|
|
18292
18776
|
});
|
|
18293
18777
|
const production = PRODUCTION_ENVIRONMENTS.has(job.targetEnvironment.toLowerCase());
|