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/cjs/index.cjs
CHANGED
|
@@ -83,6 +83,13 @@ var ErrorCodes = {
|
|
|
83
83
|
INVALID_OPERATION: "INVALID_OPERATION",
|
|
84
84
|
/** Invalid local Vector Search options before the driver is called. */
|
|
85
85
|
INVALID_VECTOR_SEARCH: "INVALID_VECTOR_SEARCH",
|
|
86
|
+
/** Search index lifecycle errors (v3.4.0). */
|
|
87
|
+
INVALID_SEARCH_INDEX_DEFINITION: "INVALID_SEARCH_INDEX_DEFINITION",
|
|
88
|
+
SEARCH_INDEX_EXISTS: "SEARCH_INDEX_EXISTS",
|
|
89
|
+
SEARCH_INDEX_NOT_FOUND: "SEARCH_INDEX_NOT_FOUND",
|
|
90
|
+
SEARCH_INDEX_UNSUPPORTED: "SEARCH_INDEX_UNSUPPORTED",
|
|
91
|
+
SEARCH_INDEX_BUILD_FAILED: "SEARCH_INDEX_BUILD_FAILED",
|
|
92
|
+
SEARCH_INDEX_WAIT_TIMEOUT: "SEARCH_INDEX_WAIT_TIMEOUT",
|
|
86
93
|
/** Relation select is outside the supported inclusion-only top-level contract. */
|
|
87
94
|
INVALID_RELATION_SELECT: "INVALID_RELATION_SELECT",
|
|
88
95
|
/** A protected delete found declared Model relations that still reference its target. */
|
|
@@ -1015,31 +1022,31 @@ function normalizeRelationSelect(value, context = {}) {
|
|
|
1015
1022
|
if (value === void 0) {
|
|
1016
1023
|
return void 0;
|
|
1017
1024
|
}
|
|
1018
|
-
const
|
|
1025
|
+
const invalid3 = (reason) => {
|
|
1019
1026
|
throw createError(
|
|
1020
1027
|
ErrorCodes.INVALID_RELATION_SELECT,
|
|
1021
1028
|
`Relation select must use non-empty top-level inclusion fields: ${reason}`,
|
|
1022
1029
|
[{ ...context, value, reason }]
|
|
1023
1030
|
);
|
|
1024
1031
|
};
|
|
1025
|
-
const rawTokens = typeof value === "string" ? [value] : Array.isArray(value) ? value :
|
|
1032
|
+
const rawTokens = typeof value === "string" ? [value] : Array.isArray(value) ? value : invalid3("select must be a string or string array");
|
|
1026
1033
|
const tokens = [];
|
|
1027
1034
|
for (const rawToken of rawTokens) {
|
|
1028
1035
|
if (typeof rawToken !== "string") {
|
|
1029
|
-
|
|
1036
|
+
invalid3("select array entries must be strings");
|
|
1030
1037
|
}
|
|
1031
1038
|
const parts = rawToken.trim().split(/\s+/).filter(Boolean);
|
|
1032
1039
|
if (parts.length === 0) {
|
|
1033
|
-
|
|
1040
|
+
invalid3("select must not be empty");
|
|
1034
1041
|
}
|
|
1035
1042
|
tokens.push(...parts);
|
|
1036
1043
|
}
|
|
1037
1044
|
for (const token of tokens) {
|
|
1038
1045
|
if (token.startsWith("-") || token.startsWith("+")) {
|
|
1039
|
-
|
|
1046
|
+
invalid3("exclusion and prefix tokens are not supported");
|
|
1040
1047
|
}
|
|
1041
1048
|
if (token.includes(".")) {
|
|
1042
|
-
|
|
1049
|
+
invalid3("dot-path fields are not supported");
|
|
1043
1050
|
}
|
|
1044
1051
|
}
|
|
1045
1052
|
return [...new Set(tokens)];
|
|
@@ -3594,8 +3601,8 @@ function normalizeExtensionValue(value, stack) {
|
|
|
3594
3601
|
return `circular:${getExtensionObjectKey(objectValue)}`;
|
|
3595
3602
|
}
|
|
3596
3603
|
const prototype = Object.getPrototypeOf(objectValue);
|
|
3597
|
-
const
|
|
3598
|
-
if (!Array.isArray(objectValue) && !
|
|
3604
|
+
const isPlainObject5 = prototype === Object.prototype || prototype === null;
|
|
3605
|
+
if (!Array.isArray(objectValue) && !isPlainObject5) {
|
|
3599
3606
|
return `object:${prototype?.constructor?.name ?? "unknown"}:${getExtensionObjectKey(objectValue)}`;
|
|
3600
3607
|
}
|
|
3601
3608
|
stack.add(objectValue);
|
|
@@ -6709,6 +6716,28 @@ var ModelInstance = class {
|
|
|
6709
6716
|
dropIndexes() {
|
|
6710
6717
|
return this.runModelWrite(() => this.collection.dropIndexes());
|
|
6711
6718
|
}
|
|
6719
|
+
/** Search index lifecycle (v3.4.0): writes run as model writes for writePathPolicy. */
|
|
6720
|
+
createSearchIndex(spec, options) {
|
|
6721
|
+
return this.runModelWrite(() => this.collection.createSearchIndex(spec, options));
|
|
6722
|
+
}
|
|
6723
|
+
createSearchIndexes(specs) {
|
|
6724
|
+
return this.runModelWrite(() => this.collection.createSearchIndexes(specs));
|
|
6725
|
+
}
|
|
6726
|
+
listSearchIndexes(options) {
|
|
6727
|
+
return this.collection.listSearchIndexes(options);
|
|
6728
|
+
}
|
|
6729
|
+
getSearchIndex(name) {
|
|
6730
|
+
return this.collection.getSearchIndex(name);
|
|
6731
|
+
}
|
|
6732
|
+
updateSearchIndex(name, definition) {
|
|
6733
|
+
return this.runModelWrite(() => this.collection.updateSearchIndex(name, definition));
|
|
6734
|
+
}
|
|
6735
|
+
dropSearchIndex(name, options) {
|
|
6736
|
+
return this.runModelWrite(() => this.collection.dropSearchIndex(name, options));
|
|
6737
|
+
}
|
|
6738
|
+
waitForSearchIndex(name, options) {
|
|
6739
|
+
return this.collection.waitForSearchIndex(name, options);
|
|
6740
|
+
}
|
|
6712
6741
|
prewarmBookmarks(keyDims, pages) {
|
|
6713
6742
|
return this.extendedCollection().prewarmBookmarks(keyDims, pages);
|
|
6714
6743
|
}
|
|
@@ -12128,6 +12157,432 @@ function mapVectorSearchRows(rows) {
|
|
|
12128
12157
|
});
|
|
12129
12158
|
}
|
|
12130
12159
|
|
|
12160
|
+
// src/adapters/mongodb/search-indexes/errors.ts
|
|
12161
|
+
var CODE_INDEX_ALREADY_EXISTS = 68;
|
|
12162
|
+
var CODE_INDEX_NOT_FOUND = 27;
|
|
12163
|
+
var CODE_NAMESPACE_NOT_FOUND = 26;
|
|
12164
|
+
var CODE_COMMAND_NOT_FOUND = 59;
|
|
12165
|
+
var CODE_COMMAND_NOT_SUPPORTED = 115;
|
|
12166
|
+
var CODE_LIST_SEARCH_ATLAS_ONLY = 6047401;
|
|
12167
|
+
var UNSUPPORTED_MESSAGE = /search index commands are only supported|\$listSearchIndexes stage is only allowed/i;
|
|
12168
|
+
function readCode(error) {
|
|
12169
|
+
const e = error;
|
|
12170
|
+
return {
|
|
12171
|
+
code: typeof e?.code === "number" ? e.code : void 0,
|
|
12172
|
+
codeName: typeof e?.codeName === "string" ? e.codeName : void 0,
|
|
12173
|
+
message: typeof e?.message === "string" ? e.message : String(error)
|
|
12174
|
+
};
|
|
12175
|
+
}
|
|
12176
|
+
function classifySearchIndexError(error) {
|
|
12177
|
+
const { code, codeName, message } = readCode(error);
|
|
12178
|
+
if (code === CODE_INDEX_ALREADY_EXISTS || codeName === "IndexAlreadyExists") return "exists";
|
|
12179
|
+
if (code === CODE_NAMESPACE_NOT_FOUND || codeName === "NamespaceNotFound") return "namespaceNotFound";
|
|
12180
|
+
if (code === CODE_INDEX_NOT_FOUND || codeName === "IndexNotFound") return "notFound";
|
|
12181
|
+
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";
|
|
12182
|
+
if (code === void 0 && codeName === void 0 && /already exists/i.test(message)) return "exists";
|
|
12183
|
+
if (UNSUPPORTED_MESSAGE.test(message)) return "unsupported";
|
|
12184
|
+
return "other";
|
|
12185
|
+
}
|
|
12186
|
+
function attachContext(error, ctx) {
|
|
12187
|
+
const contextualized = error;
|
|
12188
|
+
try {
|
|
12189
|
+
contextualized.monsqlize = {
|
|
12190
|
+
...contextualized.monsqlize && typeof contextualized.monsqlize === "object" ? contextualized.monsqlize : {},
|
|
12191
|
+
operation: ctx.operation,
|
|
12192
|
+
namespace: ctx.namespace,
|
|
12193
|
+
indexName: ctx.indexName
|
|
12194
|
+
};
|
|
12195
|
+
} catch {
|
|
12196
|
+
}
|
|
12197
|
+
return error;
|
|
12198
|
+
}
|
|
12199
|
+
function searchIndexError(code, message, ctx, extra = {}, cause) {
|
|
12200
|
+
return createError(code, message, [{ operation: ctx.operation, namespace: ctx.namespace, indexName: ctx.indexName, ...extra }], cause);
|
|
12201
|
+
}
|
|
12202
|
+
function mapSearchIndexError(error, ctx) {
|
|
12203
|
+
if (!(error instanceof Error)) {
|
|
12204
|
+
return createError(ErrorCodes.MONGODB_ERROR, `${ctx.operation} failed`, [{ ...ctx, reason: String(error) }]);
|
|
12205
|
+
}
|
|
12206
|
+
if (error.code !== void 0 && typeof error.code === "string" && String(error.code).startsWith("SEARCH_INDEX_")) {
|
|
12207
|
+
return error;
|
|
12208
|
+
}
|
|
12209
|
+
const { code, codeName, message } = readCode(error);
|
|
12210
|
+
const server = { serverCode: code, serverCodeName: codeName, serverMessage: message };
|
|
12211
|
+
switch (classifySearchIndexError(error)) {
|
|
12212
|
+
case "exists":
|
|
12213
|
+
return searchIndexError(ErrorCodes.SEARCH_INDEX_EXISTS, `Search index "${ctx.indexName}" already exists`, ctx, server, error);
|
|
12214
|
+
case "notFound":
|
|
12215
|
+
return searchIndexError(ErrorCodes.SEARCH_INDEX_NOT_FOUND, `Search index "${ctx.indexName}" was not found`, ctx, server, error);
|
|
12216
|
+
case "unsupported":
|
|
12217
|
+
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);
|
|
12218
|
+
default:
|
|
12219
|
+
return attachContext(searchIndexError(ErrorCodes.MONGODB_ERROR, `${ctx.operation} failed: ${message}`, ctx, server, error), ctx);
|
|
12220
|
+
}
|
|
12221
|
+
}
|
|
12222
|
+
|
|
12223
|
+
// src/adapters/mongodb/search-indexes/normalize.ts
|
|
12224
|
+
function missingSearchIndexInfo(name) {
|
|
12225
|
+
return { name, status: "DOES_NOT_EXIST", queryable: false, typeSource: "unknown", raw: {} };
|
|
12226
|
+
}
|
|
12227
|
+
function normalizeSearchIndexInfo(doc) {
|
|
12228
|
+
const info = {
|
|
12229
|
+
name: String(doc.name),
|
|
12230
|
+
// Unknown future statuses are passed through verbatim rather than rewritten.
|
|
12231
|
+
status: typeof doc.status === "string" && doc.status.length > 0 ? doc.status : "PENDING",
|
|
12232
|
+
queryable: doc.queryable === true,
|
|
12233
|
+
typeSource: "unknown",
|
|
12234
|
+
raw: doc
|
|
12235
|
+
};
|
|
12236
|
+
if (typeof doc.id === "string") info.id = doc.id;
|
|
12237
|
+
const resolved = resolveSearchIndexType(doc);
|
|
12238
|
+
info.typeSource = resolved.typeSource;
|
|
12239
|
+
if (resolved.type !== void 0) info.type = resolved.type;
|
|
12240
|
+
if (doc.latestDefinition && typeof doc.latestDefinition === "object") info.latestDefinition = doc.latestDefinition;
|
|
12241
|
+
const version = doc.latestDefinitionVersion;
|
|
12242
|
+
if (version && typeof version === "object") {
|
|
12243
|
+
info.latestDefinitionVersion = { version: Number(version.version), createdAt: version.createdAt };
|
|
12244
|
+
}
|
|
12245
|
+
if (Array.isArray(doc.statusDetail)) info.statusDetail = doc.statusDetail;
|
|
12246
|
+
return info;
|
|
12247
|
+
}
|
|
12248
|
+
function resolveSearchIndexType(doc) {
|
|
12249
|
+
if (doc.type === "search" || doc.type === "vectorSearch") return { type: doc.type, typeSource: "server" };
|
|
12250
|
+
const definition = doc.latestDefinition;
|
|
12251
|
+
if (definition && typeof definition === "object" && !Array.isArray(definition)) {
|
|
12252
|
+
const fields = definition.fields;
|
|
12253
|
+
if (Array.isArray(fields) && fields.some((field) => field && typeof field === "object" && field.type === "vector")) {
|
|
12254
|
+
return { type: "vectorSearch", typeSource: "inferred" };
|
|
12255
|
+
}
|
|
12256
|
+
if (Object.prototype.hasOwnProperty.call(definition, "mappings")) return { type: "search", typeSource: "inferred" };
|
|
12257
|
+
}
|
|
12258
|
+
return { typeSource: "unknown" };
|
|
12259
|
+
}
|
|
12260
|
+
function matchesSearchIndexType(info, type) {
|
|
12261
|
+
return info.typeSource !== "unknown" && info.type === type;
|
|
12262
|
+
}
|
|
12263
|
+
|
|
12264
|
+
// src/adapters/mongodb/search-indexes/validate.ts
|
|
12265
|
+
var TYPES = /* @__PURE__ */ new Set(["search", "vectorSearch"]);
|
|
12266
|
+
var VECTOR_FIELD_TYPES = /* @__PURE__ */ new Set(["vector", "filter"]);
|
|
12267
|
+
var SIMILARITIES = /* @__PURE__ */ new Set(["euclidean", "cosine", "dotProduct"]);
|
|
12268
|
+
function invalid(operation, namespace, indexName, field, reason) {
|
|
12269
|
+
throw createError(
|
|
12270
|
+
ErrorCodes.INVALID_SEARCH_INDEX_DEFINITION,
|
|
12271
|
+
`${operation}: invalid ${field} (${reason})`,
|
|
12272
|
+
[{ operation, namespace, indexName: typeof indexName === "string" ? indexName : void 0, field, reason }]
|
|
12273
|
+
);
|
|
12274
|
+
}
|
|
12275
|
+
function isPlainObject4(value) {
|
|
12276
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
12277
|
+
const proto = Object.getPrototypeOf(value);
|
|
12278
|
+
return proto === Object.prototype || proto === null;
|
|
12279
|
+
}
|
|
12280
|
+
function assertIndexName(operation, namespace, name) {
|
|
12281
|
+
if (typeof name !== "string" || name.length === 0 || name.trim() !== name) {
|
|
12282
|
+
invalid(operation, namespace, name, "name", "non-empty string without leading or trailing whitespace required");
|
|
12283
|
+
}
|
|
12284
|
+
}
|
|
12285
|
+
function assertDefinition(operation, namespace, name, definition, type) {
|
|
12286
|
+
if (!isPlainObject4(definition)) invalid(operation, namespace, name, "definition", "plain object required");
|
|
12287
|
+
if (type !== "vectorSearch") return;
|
|
12288
|
+
const fields = definition.fields;
|
|
12289
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
12290
|
+
invalid(operation, namespace, name, "definition.fields", "non-empty array required for vectorSearch");
|
|
12291
|
+
}
|
|
12292
|
+
fields.forEach((field, index) => {
|
|
12293
|
+
const path5 = `definition.fields[${index}]`;
|
|
12294
|
+
if (!isPlainObject4(field)) invalid(operation, namespace, name, path5, "object required");
|
|
12295
|
+
if (typeof field.type !== "string" || !VECTOR_FIELD_TYPES.has(field.type)) {
|
|
12296
|
+
invalid(operation, namespace, name, `${path5}.type`, 'must be "vector" or "filter"');
|
|
12297
|
+
}
|
|
12298
|
+
if (typeof field.path !== "string" || field.path.length === 0) {
|
|
12299
|
+
invalid(operation, namespace, name, `${path5}.path`, "non-empty string required");
|
|
12300
|
+
}
|
|
12301
|
+
if (field.type === "vector") {
|
|
12302
|
+
const dims = field.numDimensions;
|
|
12303
|
+
if (typeof dims !== "number" || !Number.isInteger(dims) || dims <= 0) {
|
|
12304
|
+
invalid(operation, namespace, name, `${path5}.numDimensions`, "positive integer required");
|
|
12305
|
+
}
|
|
12306
|
+
if (typeof field.similarity !== "string" || !SIMILARITIES.has(field.similarity)) {
|
|
12307
|
+
invalid(operation, namespace, name, `${path5}.similarity`, 'must be "euclidean", "cosine", or "dotProduct"');
|
|
12308
|
+
}
|
|
12309
|
+
}
|
|
12310
|
+
});
|
|
12311
|
+
}
|
|
12312
|
+
function assertSpec(operation, namespace, spec) {
|
|
12313
|
+
if (!isPlainObject4(spec)) invalid(operation, namespace, void 0, "spec", "object required");
|
|
12314
|
+
assertIndexName(operation, namespace, spec.name);
|
|
12315
|
+
if (typeof spec.type !== "string" || !TYPES.has(spec.type)) {
|
|
12316
|
+
invalid(operation, namespace, spec.name, "type", 'must be "search" or "vectorSearch"');
|
|
12317
|
+
}
|
|
12318
|
+
assertDefinition(operation, namespace, spec.name, spec.definition, spec.type);
|
|
12319
|
+
}
|
|
12320
|
+
function assertOptionsObject(operation, namespace, name, options) {
|
|
12321
|
+
if (options !== void 0 && !isPlainObject4(options)) invalid(operation, namespace, name, "options", "object required");
|
|
12322
|
+
}
|
|
12323
|
+
function assertEnumOption(operation, namespace, name, field, value, allowed) {
|
|
12324
|
+
if (value !== void 0 && (typeof value !== "string" || !allowed.includes(value))) {
|
|
12325
|
+
invalid(operation, namespace, name, field, `must be one of ${allowed.map((v) => `"${v}"`).join(", ")}`);
|
|
12326
|
+
}
|
|
12327
|
+
}
|
|
12328
|
+
function assertBooleanOption(operation, namespace, name, field, value) {
|
|
12329
|
+
if (value !== void 0 && typeof value !== "boolean") invalid(operation, namespace, name, field, "boolean required");
|
|
12330
|
+
}
|
|
12331
|
+
function resolveWaitOptions(namespace, name, options) {
|
|
12332
|
+
const operation = "waitForSearchIndex";
|
|
12333
|
+
assertOptionsObject(operation, namespace, name, options);
|
|
12334
|
+
const opts = options ?? {};
|
|
12335
|
+
assertEnumOption(operation, namespace, name, "options.until", opts.until, ["queryable", "ready", "dropped"]);
|
|
12336
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
12337
|
+
const intervalMs = opts.intervalMs ?? 2e3;
|
|
12338
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
12339
|
+
invalid(operation, namespace, name, "options.timeoutMs", "positive integer required");
|
|
12340
|
+
}
|
|
12341
|
+
if (!Number.isInteger(intervalMs) || intervalMs < 100) {
|
|
12342
|
+
invalid(operation, namespace, name, "options.intervalMs", "integer >= 100 required");
|
|
12343
|
+
}
|
|
12344
|
+
const signal = opts.signal;
|
|
12345
|
+
if (signal !== void 0 && (signal === null || typeof signal !== "object" || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function")) {
|
|
12346
|
+
invalid(operation, namespace, name, "options.signal", "AbortSignal required");
|
|
12347
|
+
}
|
|
12348
|
+
return { until: opts.until ?? "queryable", timeoutMs, intervalMs, signal };
|
|
12349
|
+
}
|
|
12350
|
+
|
|
12351
|
+
// src/adapters/mongodb/search-indexes/operations.ts
|
|
12352
|
+
var SEARCH_INDEX_MISSING_GRACE_MS = 3e4;
|
|
12353
|
+
async function fetchByName(ctx, operation, name, signal) {
|
|
12354
|
+
try {
|
|
12355
|
+
const docs = await ctx.collection.listSearchIndexes(name, signal ? { signal } : {}).toArray();
|
|
12356
|
+
const match = docs.find((doc) => doc.name === name);
|
|
12357
|
+
return match ? normalizeSearchIndexInfo(match) : missingSearchIndexInfo(name);
|
|
12358
|
+
} catch (error) {
|
|
12359
|
+
if (signal?.aborted) throw signal.reason;
|
|
12360
|
+
if (classifySearchIndexError(error) === "namespaceNotFound") return missingSearchIndexInfo(name);
|
|
12361
|
+
throw mapSearchIndexError(error, { operation, namespace: ctx.namespace, indexName: name });
|
|
12362
|
+
}
|
|
12363
|
+
}
|
|
12364
|
+
async function getSearchIndexOp(ctx, name) {
|
|
12365
|
+
assertIndexName("getSearchIndex", ctx.namespace, name);
|
|
12366
|
+
return fetchByName(ctx, "getSearchIndex", name);
|
|
12367
|
+
}
|
|
12368
|
+
async function listSearchIndexesOp(ctx, options) {
|
|
12369
|
+
const operation = "listSearchIndexes";
|
|
12370
|
+
assertOptionsObject(operation, ctx.namespace, void 0, options);
|
|
12371
|
+
assertEnumOption(operation, ctx.namespace, void 0, "options.type", options?.type, ["search", "vectorSearch"]);
|
|
12372
|
+
let docs;
|
|
12373
|
+
try {
|
|
12374
|
+
docs = await ctx.collection.listSearchIndexes().toArray();
|
|
12375
|
+
} catch (error) {
|
|
12376
|
+
if (classifySearchIndexError(error) === "namespaceNotFound") return [];
|
|
12377
|
+
throw mapSearchIndexError(error, { operation, namespace: ctx.namespace });
|
|
12378
|
+
}
|
|
12379
|
+
const infos = docs.map(normalizeSearchIndexInfo);
|
|
12380
|
+
return options?.type ? infos.filter((info) => matchesSearchIndexType(info, options.type)) : infos;
|
|
12381
|
+
}
|
|
12382
|
+
async function createSearchIndexOp(ctx, spec, options) {
|
|
12383
|
+
const operation = "createSearchIndex";
|
|
12384
|
+
assertSpec(operation, ctx.namespace, spec);
|
|
12385
|
+
assertOptionsObject(operation, ctx.namespace, spec.name, options);
|
|
12386
|
+
assertEnumOption(operation, ctx.namespace, spec.name, "options.ifExists", options?.ifExists, ["error", "skip", "update"]);
|
|
12387
|
+
const ifExists = options?.ifExists ?? "error";
|
|
12388
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: spec.name };
|
|
12389
|
+
const existing = await fetchByName(ctx, operation, spec.name);
|
|
12390
|
+
if (existing.status !== "DOES_NOT_EXIST") {
|
|
12391
|
+
if (ifExists === "skip") return { name: spec.name, action: "skipped", created: false };
|
|
12392
|
+
if (ifExists === "error") {
|
|
12393
|
+
throw searchIndexError(ErrorCodes.SEARCH_INDEX_EXISTS, `Search index "${spec.name}" already exists`, errCtx);
|
|
12394
|
+
}
|
|
12395
|
+
if (existing.type !== void 0 && existing.type !== spec.type) {
|
|
12396
|
+
throw searchIndexError(
|
|
12397
|
+
ErrorCodes.INVALID_SEARCH_INDEX_DEFINITION,
|
|
12398
|
+
`Search index "${spec.name}" has type "${existing.type}"; drop it before recreating as "${spec.type}"`,
|
|
12399
|
+
errCtx,
|
|
12400
|
+
{ field: "type", reason: "type change requires drop and create" }
|
|
12401
|
+
);
|
|
12402
|
+
}
|
|
12403
|
+
try {
|
|
12404
|
+
await ctx.collection.updateSearchIndex(spec.name, spec.definition);
|
|
12405
|
+
} catch (error) {
|
|
12406
|
+
throw mapSearchIndexError(error, errCtx);
|
|
12407
|
+
}
|
|
12408
|
+
return { name: spec.name, action: "updated", created: false };
|
|
12409
|
+
}
|
|
12410
|
+
try {
|
|
12411
|
+
const name = await ctx.collection.createSearchIndex({ name: spec.name, type: spec.type, definition: spec.definition });
|
|
12412
|
+
return { name: typeof name === "string" && name.length > 0 ? name : spec.name, action: "created", created: true };
|
|
12413
|
+
} catch (error) {
|
|
12414
|
+
const mapped = mapSearchIndexError(error, errCtx);
|
|
12415
|
+
if (mapped.code === ErrorCodes.SEARCH_INDEX_EXISTS && ifExists === "skip") {
|
|
12416
|
+
return { name: spec.name, action: "skipped", created: false };
|
|
12417
|
+
}
|
|
12418
|
+
throw mapped;
|
|
12419
|
+
}
|
|
12420
|
+
}
|
|
12421
|
+
async function createSearchIndexesOp(ctx, specs) {
|
|
12422
|
+
const operation = "createSearchIndexes";
|
|
12423
|
+
if (!Array.isArray(specs) || specs.length === 0) {
|
|
12424
|
+
throw searchIndexError(
|
|
12425
|
+
ErrorCodes.INVALID_SEARCH_INDEX_DEFINITION,
|
|
12426
|
+
`${operation}: specs must be a non-empty array`,
|
|
12427
|
+
{ operation, namespace: ctx.namespace },
|
|
12428
|
+
{ field: "specs", reason: "non-empty array required" }
|
|
12429
|
+
);
|
|
12430
|
+
}
|
|
12431
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12432
|
+
for (const spec of specs) {
|
|
12433
|
+
assertSpec(operation, ctx.namespace, spec);
|
|
12434
|
+
if (seen.has(spec.name)) {
|
|
12435
|
+
throw searchIndexError(
|
|
12436
|
+
ErrorCodes.SEARCH_INDEX_EXISTS,
|
|
12437
|
+
`Search index "${spec.name}" is listed more than once`,
|
|
12438
|
+
{ operation, namespace: ctx.namespace, indexName: spec.name }
|
|
12439
|
+
);
|
|
12440
|
+
}
|
|
12441
|
+
seen.add(spec.name);
|
|
12442
|
+
}
|
|
12443
|
+
try {
|
|
12444
|
+
return await ctx.collection.createSearchIndexes(specs.map(({ name, type, definition }) => ({ name, type, definition })));
|
|
12445
|
+
} catch (error) {
|
|
12446
|
+
throw mapSearchIndexError(error, { operation, namespace: ctx.namespace });
|
|
12447
|
+
}
|
|
12448
|
+
}
|
|
12449
|
+
async function updateSearchIndexOp(ctx, name, definition) {
|
|
12450
|
+
const operation = "updateSearchIndex";
|
|
12451
|
+
assertIndexName(operation, ctx.namespace, name);
|
|
12452
|
+
assertDefinition(operation, ctx.namespace, name, definition);
|
|
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
|
+
throw searchIndexError(ErrorCodes.SEARCH_INDEX_NOT_FOUND, `Search index "${name}" was not found`, errCtx);
|
|
12457
|
+
}
|
|
12458
|
+
if (existing.type !== void 0) assertDefinition(operation, ctx.namespace, name, definition, existing.type);
|
|
12459
|
+
try {
|
|
12460
|
+
await ctx.collection.updateSearchIndex(name, definition);
|
|
12461
|
+
} catch (error) {
|
|
12462
|
+
throw mapSearchIndexError(error, errCtx);
|
|
12463
|
+
}
|
|
12464
|
+
}
|
|
12465
|
+
async function dropSearchIndexOp(ctx, name, options) {
|
|
12466
|
+
const operation = "dropSearchIndex";
|
|
12467
|
+
assertIndexName(operation, ctx.namespace, name);
|
|
12468
|
+
assertOptionsObject(operation, ctx.namespace, name, options);
|
|
12469
|
+
assertBooleanOption(operation, ctx.namespace, name, "options.ignoreMissing", options?.ignoreMissing);
|
|
12470
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: name };
|
|
12471
|
+
const existing = await fetchByName(ctx, operation, name);
|
|
12472
|
+
if (existing.status === "DOES_NOT_EXIST") {
|
|
12473
|
+
if (options?.ignoreMissing) return;
|
|
12474
|
+
throw searchIndexError(ErrorCodes.SEARCH_INDEX_NOT_FOUND, `Search index "${name}" was not found`, errCtx);
|
|
12475
|
+
}
|
|
12476
|
+
try {
|
|
12477
|
+
await ctx.collection.dropSearchIndex(name);
|
|
12478
|
+
} catch (error) {
|
|
12479
|
+
const mapped = mapSearchIndexError(error, errCtx);
|
|
12480
|
+
if (mapped.code === ErrorCodes.SEARCH_INDEX_NOT_FOUND && options?.ignoreMissing) return;
|
|
12481
|
+
throw mapped;
|
|
12482
|
+
}
|
|
12483
|
+
}
|
|
12484
|
+
function sleep2(ms, signal) {
|
|
12485
|
+
return new Promise((resolve, reject) => {
|
|
12486
|
+
if (signal?.aborted) {
|
|
12487
|
+
reject(signal.reason);
|
|
12488
|
+
return;
|
|
12489
|
+
}
|
|
12490
|
+
const onAbort = () => {
|
|
12491
|
+
clearTimeout(timer);
|
|
12492
|
+
reject(signal?.reason);
|
|
12493
|
+
};
|
|
12494
|
+
const timer = setTimeout(() => {
|
|
12495
|
+
signal?.removeEventListener("abort", onAbort);
|
|
12496
|
+
resolve();
|
|
12497
|
+
}, ms);
|
|
12498
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
12499
|
+
});
|
|
12500
|
+
}
|
|
12501
|
+
function linkSignal(userSignal, remainingMs) {
|
|
12502
|
+
const controller = new AbortController();
|
|
12503
|
+
let expired = false;
|
|
12504
|
+
const onUserAbort = () => controller.abort(userSignal?.reason);
|
|
12505
|
+
userSignal?.addEventListener("abort", onUserAbort, { once: true });
|
|
12506
|
+
const timer = setTimeout(() => {
|
|
12507
|
+
expired = true;
|
|
12508
|
+
controller.abort(new Error("search index wait deadline reached"));
|
|
12509
|
+
}, Math.max(0, remainingMs));
|
|
12510
|
+
return {
|
|
12511
|
+
signal: controller.signal,
|
|
12512
|
+
timedOut: () => expired,
|
|
12513
|
+
dispose: () => {
|
|
12514
|
+
clearTimeout(timer);
|
|
12515
|
+
userSignal?.removeEventListener("abort", onUserAbort);
|
|
12516
|
+
}
|
|
12517
|
+
};
|
|
12518
|
+
}
|
|
12519
|
+
async function waitForSearchIndexOp(ctx, name, options, clock = Date.now) {
|
|
12520
|
+
const operation = "waitForSearchIndex";
|
|
12521
|
+
assertIndexName(operation, ctx.namespace, name);
|
|
12522
|
+
const { until, timeoutMs, intervalMs, signal } = resolveWaitOptions(ctx.namespace, name, options);
|
|
12523
|
+
const errCtx = { operation, namespace: ctx.namespace, indexName: name };
|
|
12524
|
+
if (signal?.aborted) throw signal.reason;
|
|
12525
|
+
const start = clock();
|
|
12526
|
+
const grace = Math.min(SEARCH_INDEX_MISSING_GRACE_MS, timeoutMs);
|
|
12527
|
+
let last;
|
|
12528
|
+
const timeoutError = () => searchIndexError(
|
|
12529
|
+
ErrorCodes.SEARCH_INDEX_WAIT_TIMEOUT,
|
|
12530
|
+
`Timed out after ${timeoutMs}ms waiting for Search index "${name}" to become ${until}`,
|
|
12531
|
+
errCtx,
|
|
12532
|
+
{ until, timeoutMs, last }
|
|
12533
|
+
);
|
|
12534
|
+
for (; ; ) {
|
|
12535
|
+
const link = linkSignal(signal, timeoutMs - (clock() - start));
|
|
12536
|
+
let info;
|
|
12537
|
+
try {
|
|
12538
|
+
info = await fetchByName(ctx, operation, name, link.signal);
|
|
12539
|
+
} catch (error) {
|
|
12540
|
+
if (signal?.aborted) throw signal.reason;
|
|
12541
|
+
if (link.timedOut()) throw timeoutError();
|
|
12542
|
+
throw error;
|
|
12543
|
+
} finally {
|
|
12544
|
+
link.dispose();
|
|
12545
|
+
}
|
|
12546
|
+
last = info;
|
|
12547
|
+
if (signal?.aborted) throw signal.reason;
|
|
12548
|
+
if (until === "dropped") {
|
|
12549
|
+
if (info.status === "DOES_NOT_EXIST") return info;
|
|
12550
|
+
} else if (info.status === "DOES_NOT_EXIST") {
|
|
12551
|
+
if (clock() - start >= grace) {
|
|
12552
|
+
throw searchIndexError(
|
|
12553
|
+
ErrorCodes.SEARCH_INDEX_NOT_FOUND,
|
|
12554
|
+
`Search index "${name}" was not found within ${grace}ms`,
|
|
12555
|
+
errCtx,
|
|
12556
|
+
{ until, graceMs: grace }
|
|
12557
|
+
);
|
|
12558
|
+
}
|
|
12559
|
+
} else if (until === "queryable") {
|
|
12560
|
+
if (info.queryable) return info;
|
|
12561
|
+
if (info.status === "FAILED") {
|
|
12562
|
+
throw searchIndexError(
|
|
12563
|
+
ErrorCodes.SEARCH_INDEX_BUILD_FAILED,
|
|
12564
|
+
`Search index "${name}" build failed`,
|
|
12565
|
+
errCtx,
|
|
12566
|
+
{ status: info.status, statusDetail: info.statusDetail }
|
|
12567
|
+
);
|
|
12568
|
+
}
|
|
12569
|
+
} else {
|
|
12570
|
+
if (info.status === "READY") return info;
|
|
12571
|
+
if (info.status === "FAILED") {
|
|
12572
|
+
throw searchIndexError(
|
|
12573
|
+
ErrorCodes.SEARCH_INDEX_BUILD_FAILED,
|
|
12574
|
+
`Search index "${name}" build failed`,
|
|
12575
|
+
errCtx,
|
|
12576
|
+
{ status: info.status, statusDetail: info.statusDetail }
|
|
12577
|
+
);
|
|
12578
|
+
}
|
|
12579
|
+
}
|
|
12580
|
+
const remaining = timeoutMs - (clock() - start);
|
|
12581
|
+
if (remaining <= 0) throw timeoutError();
|
|
12582
|
+
await sleep2(Math.min(intervalMs, remaining), signal);
|
|
12583
|
+
}
|
|
12584
|
+
}
|
|
12585
|
+
|
|
12131
12586
|
// src/adapters/mongodb/common/collection-accessor-cache-helpers.ts
|
|
12132
12587
|
var import_node_util4 = require("node:util");
|
|
12133
12588
|
function isRecord2(value) {
|
|
@@ -12488,7 +12943,7 @@ async function invalidateReadCachesAfterWrite(queryCache, collection, dbName, co
|
|
|
12488
12943
|
}
|
|
12489
12944
|
|
|
12490
12945
|
// src/adapters/mongodb/writes/write-utils.ts
|
|
12491
|
-
function
|
|
12946
|
+
function sleep3(ms) {
|
|
12492
12947
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12493
12948
|
}
|
|
12494
12949
|
function splitIntoBatches(items, batchSize) {
|
|
@@ -12908,7 +13363,7 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
12908
13363
|
result.retries.push(retryInfo);
|
|
12909
13364
|
onRetry?.(retryInfo);
|
|
12910
13365
|
if (retryDelay > 0) {
|
|
12911
|
-
await
|
|
13366
|
+
await sleep3(retryDelay);
|
|
12912
13367
|
}
|
|
12913
13368
|
continue;
|
|
12914
13369
|
}
|
|
@@ -13037,7 +13492,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
13037
13492
|
result.retries.push(retryInfo);
|
|
13038
13493
|
onRetry?.(retryInfo);
|
|
13039
13494
|
if (retryDelay > 0) {
|
|
13040
|
-
await
|
|
13495
|
+
await sleep3(retryDelay);
|
|
13041
13496
|
}
|
|
13042
13497
|
continue;
|
|
13043
13498
|
}
|
|
@@ -13132,7 +13587,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
13132
13587
|
result.retries.push(retryInfo);
|
|
13133
13588
|
onRetry?.(retryInfo);
|
|
13134
13589
|
if (retryDelay > 0) {
|
|
13135
|
-
await
|
|
13590
|
+
await sleep3(retryDelay);
|
|
13136
13591
|
}
|
|
13137
13592
|
continue;
|
|
13138
13593
|
}
|
|
@@ -13900,6 +14355,35 @@ var MongoCollectionAccessor = class {
|
|
|
13900
14355
|
this.assertWritePath("dropIndexes", "management");
|
|
13901
14356
|
return dropIndexesForAccessor(this.collectionRef);
|
|
13902
14357
|
}
|
|
14358
|
+
/** Search index lifecycle (v3.4.0); writes are governed by writePathPolicy `management`. */
|
|
14359
|
+
searchIndexContext() {
|
|
14360
|
+
return { collection: this.collectionRef, namespace: this.getNamespace() };
|
|
14361
|
+
}
|
|
14362
|
+
async createSearchIndex(spec, options) {
|
|
14363
|
+
this.assertWritePath("createSearchIndex", "management");
|
|
14364
|
+
return createSearchIndexOp(this.searchIndexContext(), spec, options);
|
|
14365
|
+
}
|
|
14366
|
+
async createSearchIndexes(specs) {
|
|
14367
|
+
this.assertWritePath("createSearchIndexes", "management");
|
|
14368
|
+
return createSearchIndexesOp(this.searchIndexContext(), specs);
|
|
14369
|
+
}
|
|
14370
|
+
async listSearchIndexes(options) {
|
|
14371
|
+
return listSearchIndexesOp(this.searchIndexContext(), options);
|
|
14372
|
+
}
|
|
14373
|
+
async getSearchIndex(name) {
|
|
14374
|
+
return getSearchIndexOp(this.searchIndexContext(), name);
|
|
14375
|
+
}
|
|
14376
|
+
async updateSearchIndex(name, definition) {
|
|
14377
|
+
this.assertWritePath("updateSearchIndex", "management");
|
|
14378
|
+
return updateSearchIndexOp(this.searchIndexContext(), name, definition);
|
|
14379
|
+
}
|
|
14380
|
+
async dropSearchIndex(name, options) {
|
|
14381
|
+
this.assertWritePath("dropSearchIndex", "management");
|
|
14382
|
+
return dropSearchIndexOp(this.searchIndexContext(), name, options);
|
|
14383
|
+
}
|
|
14384
|
+
async waitForSearchIndex(name, options) {
|
|
14385
|
+
return waitForSearchIndexOp(this.searchIndexContext(), name, options);
|
|
14386
|
+
}
|
|
13903
14387
|
/** Pre-populates bookmark cache entries for the specified key dimensions and page numbers. */
|
|
13904
14388
|
async prewarmBookmarks(keyDims = {}, pages = []) {
|
|
13905
14389
|
return prewarmBookmarksForAccessor(this.bookmarkContext(), keyDims, pages);
|
|
@@ -14784,7 +15268,7 @@ var LockManager = class {
|
|
|
14784
15268
|
break;
|
|
14785
15269
|
}
|
|
14786
15270
|
const delay3 = retryDelay * Math.pow(retryBackoff, attempt);
|
|
14787
|
-
await
|
|
15271
|
+
await sleep4(delay3);
|
|
14788
15272
|
}
|
|
14789
15273
|
this.stats.errors += 1;
|
|
14790
15274
|
if (options.fallbackToNoLock) {
|
|
@@ -14896,7 +15380,7 @@ var LockManager = class {
|
|
|
14896
15380
|
}
|
|
14897
15381
|
}
|
|
14898
15382
|
};
|
|
14899
|
-
async function
|
|
15383
|
+
async function sleep4(ms) {
|
|
14900
15384
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
14901
15385
|
}
|
|
14902
15386
|
var DistributedCacheLockManager = class {
|
|
@@ -15021,10 +15505,10 @@ var DistributedCacheLockManager = class {
|
|
|
15021
15505
|
return new Lock(key, lockId, this, ttl, startedAt);
|
|
15022
15506
|
}
|
|
15023
15507
|
if (attempt === retryTimes) break;
|
|
15024
|
-
await
|
|
15508
|
+
await sleep4(retryDelay);
|
|
15025
15509
|
} catch (error) {
|
|
15026
15510
|
if (attempt === retryTimes) throw error;
|
|
15027
|
-
await
|
|
15511
|
+
await sleep4(retryDelay);
|
|
15028
15512
|
}
|
|
15029
15513
|
}
|
|
15030
15514
|
this.stats.errors++;
|
|
@@ -18050,7 +18534,7 @@ var DEFAULT_DATA_TASK_MAX_DOCUMENTS = 1e4;
|
|
|
18050
18534
|
var DEFAULT_DATA_TASK_BACKUP_MAX_BYTES = 256 * 1024 * 1024;
|
|
18051
18535
|
var ENVIRONMENTS = /* @__PURE__ */ new Set(["development", "test", "staging", "production", "prod", "live"]);
|
|
18052
18536
|
var PRODUCTION_ENVIRONMENTS = /* @__PURE__ */ new Set(["production", "prod", "live"]);
|
|
18053
|
-
function
|
|
18537
|
+
function invalid2(message, collection) {
|
|
18054
18538
|
throw new DataTaskJobError("INVALID_JOB", message, "validate", collection);
|
|
18055
18539
|
}
|
|
18056
18540
|
function canonicalize(value) {
|
|
@@ -18088,15 +18572,15 @@ function connectionDescriptor(value) {
|
|
|
18088
18572
|
}
|
|
18089
18573
|
function validateConnection(value, field) {
|
|
18090
18574
|
if (isDataTaskJobRuntime(value)) return;
|
|
18091
|
-
if (!isRecord3(value) || value.type !== "mongodb")
|
|
18092
|
-
if (typeof value.databaseName !== "string" || value.databaseName.trim() === "")
|
|
18093
|
-
if (!isRecord3(value.config) || typeof value.config.uri !== "string" || value.config.uri.trim() === "")
|
|
18575
|
+
if (!isRecord3(value) || value.type !== "mongodb") invalid2(`${field} must be a monSQLize runtime or MongoDB MonSQLizeOptions.`);
|
|
18576
|
+
if (typeof value.databaseName !== "string" || value.databaseName.trim() === "") invalid2(`${field}.databaseName is required.`);
|
|
18577
|
+
if (!isRecord3(value.config) || typeof value.config.uri !== "string" || value.config.uri.trim() === "") invalid2(`${field}.config.uri is required.`);
|
|
18094
18578
|
}
|
|
18095
18579
|
function uniqueFields(value, pathName, collection) {
|
|
18096
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
18580
|
+
if (!Array.isArray(value) || value.length === 0) invalid2(`${pathName} must be a non-empty string array.`, collection);
|
|
18097
18581
|
const fields = value.map((item) => typeof item === "string" ? item.trim() : "");
|
|
18098
|
-
if (fields.some((item) => item === ""))
|
|
18099
|
-
if (new Set(fields).size !== fields.length)
|
|
18582
|
+
if (fields.some((item) => item === "")) invalid2(`${pathName} must contain non-empty strings.`, collection);
|
|
18583
|
+
if (new Set(fields).size !== fields.length) invalid2(`${pathName} must not contain duplicates.`, collection);
|
|
18100
18584
|
return fields;
|
|
18101
18585
|
}
|
|
18102
18586
|
var FORBIDDEN_FIELD_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -18107,31 +18591,31 @@ function assertBsonLiteral(value, pathName, collection, stack = /* @__PURE__ */
|
|
|
18107
18591
|
const valueType = typeof value;
|
|
18108
18592
|
if (value === null || BSON_PRIMITIVE_TYPES.has(valueType)) return;
|
|
18109
18593
|
if (BSON_UNSUPPORTED_TYPES.has(valueType)) {
|
|
18110
|
-
|
|
18594
|
+
invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18111
18595
|
}
|
|
18112
18596
|
if (value instanceof Date || value instanceof RegExp || Buffer.isBuffer(value)) return;
|
|
18113
18597
|
if (Array.isArray(value)) {
|
|
18114
|
-
if (stack.has(value))
|
|
18598
|
+
if (stack.has(value)) invalid2(`${pathName} must not contain circular values.`, collection);
|
|
18115
18599
|
stack.add(value);
|
|
18116
18600
|
value.forEach((item, position) => assertBsonLiteral(item, `${pathName}[${position}]`, collection, stack));
|
|
18117
18601
|
stack.delete(value);
|
|
18118
18602
|
return;
|
|
18119
18603
|
}
|
|
18120
|
-
if (!isRecord3(value))
|
|
18604
|
+
if (!isRecord3(value)) invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18121
18605
|
if (typeof value._bsontype === "string") return;
|
|
18122
18606
|
const prototype = Object.getPrototypeOf(value);
|
|
18123
|
-
if (!PLAIN_OBJECT_PROTOTYPES.has(prototype))
|
|
18124
|
-
if (stack.has(value))
|
|
18607
|
+
if (!PLAIN_OBJECT_PROTOTYPES.has(prototype)) invalid2(`${pathName} must contain only BSON-compatible literals.`, collection);
|
|
18608
|
+
if (stack.has(value)) invalid2(`${pathName} must not contain circular values.`, collection);
|
|
18125
18609
|
stack.add(value);
|
|
18126
18610
|
for (const [key, item] of Object.entries(value)) assertBsonLiteral(item, `${pathName}.${key}`, collection, stack);
|
|
18127
18611
|
stack.delete(value);
|
|
18128
18612
|
}
|
|
18129
18613
|
function normalizeFieldPath(value, pathName, collection) {
|
|
18130
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
18614
|
+
if (typeof value !== "string" || value.trim() === "") invalid2(`${pathName} must be a non-empty field path.`, collection);
|
|
18131
18615
|
const field = value.trim();
|
|
18132
18616
|
const segments = field.split(".");
|
|
18133
18617
|
if (segments.some((segment) => segment === "" || segment.startsWith("$") || FORBIDDEN_FIELD_PATH_SEGMENTS.has(segment))) {
|
|
18134
|
-
|
|
18618
|
+
invalid2(`${pathName} contains an unsafe field path.`, collection);
|
|
18135
18619
|
}
|
|
18136
18620
|
return field;
|
|
18137
18621
|
}
|
|
@@ -18147,13 +18631,13 @@ function projectionIncludes(projection, field) {
|
|
|
18147
18631
|
return included.some((key) => key === field || field.startsWith(`${key}.`));
|
|
18148
18632
|
}
|
|
18149
18633
|
function normalizeFieldPatch(data, identityFields, collection) {
|
|
18150
|
-
if (data.rename !== void 0 && !isRecord3(data.rename))
|
|
18151
|
-
if (data.set !== void 0 && !isRecord3(data.set))
|
|
18152
|
-
if (data.unset !== void 0 && !Array.isArray(data.unset))
|
|
18634
|
+
if (data.rename !== void 0 && !isRecord3(data.rename)) invalid2("data.rename must be an object.", collection);
|
|
18635
|
+
if (data.set !== void 0 && !isRecord3(data.set)) invalid2("data.set must be an object.", collection);
|
|
18636
|
+
if (data.unset !== void 0 && !Array.isArray(data.unset)) invalid2("data.unset must be an array.", collection);
|
|
18153
18637
|
const renameEntries = Object.entries(data.rename ?? {}).map(([source, destination], position) => {
|
|
18154
18638
|
const normalizedSource = normalizeFieldPath(source, `data.rename source ${position}`, collection);
|
|
18155
18639
|
const normalizedDestination = normalizeFieldPath(destination, `data.rename.${source}`, collection);
|
|
18156
|
-
if (pathsOverlap(normalizedSource, normalizedDestination))
|
|
18640
|
+
if (pathsOverlap(normalizedSource, normalizedDestination)) invalid2("data.rename source and destination must not overlap.", collection);
|
|
18157
18641
|
return [normalizedSource, normalizedDestination];
|
|
18158
18642
|
});
|
|
18159
18643
|
const setEntries = Object.entries(data.set ?? {}).map(([field, value], position) => {
|
|
@@ -18162,14 +18646,14 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18162
18646
|
try {
|
|
18163
18647
|
canonicalStringify(value);
|
|
18164
18648
|
} catch {
|
|
18165
|
-
|
|
18649
|
+
invalid2(`data.set.${field} must be a BSON-compatible literal.`, collection);
|
|
18166
18650
|
}
|
|
18167
18651
|
return [normalizedField, value];
|
|
18168
18652
|
});
|
|
18169
18653
|
const unset = (data.unset ?? []).map((field, position) => normalizeFieldPath(field, `data.unset[${position}]`, collection));
|
|
18170
|
-
if (new Set(unset).size !== unset.length)
|
|
18654
|
+
if (new Set(unset).size !== unset.length) invalid2("data.unset must not contain duplicates.", collection);
|
|
18171
18655
|
const destinations = renameEntries.map(([, destination]) => destination);
|
|
18172
|
-
if (new Set(destinations).size !== destinations.length)
|
|
18656
|
+
if (new Set(destinations).size !== destinations.length) invalid2("data.rename destinations must be unique.", collection);
|
|
18173
18657
|
const configuredPaths = [
|
|
18174
18658
|
...renameEntries.flatMap(([source, destination]) => [
|
|
18175
18659
|
{ path: source, label: `rename source "${source}"` },
|
|
@@ -18181,18 +18665,18 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18181
18665
|
for (let left = 0; left < configuredPaths.length; left += 1) {
|
|
18182
18666
|
for (let right = left + 1; right < configuredPaths.length; right += 1) {
|
|
18183
18667
|
if (pathsOverlap(configuredPaths[left].path, configuredPaths[right].path)) {
|
|
18184
|
-
|
|
18668
|
+
invalid2(`${configuredPaths[left].label} conflicts with ${configuredPaths[right].label}.`, collection);
|
|
18185
18669
|
}
|
|
18186
18670
|
}
|
|
18187
18671
|
}
|
|
18188
18672
|
for (const configured of configuredPaths) {
|
|
18189
18673
|
if (configured.path === "_id" || configured.path.startsWith("_id.") || identityFields.some((identity) => pathsOverlap(configured.path, identity))) {
|
|
18190
|
-
|
|
18674
|
+
invalid2(`${configured.label} must not overlap _id or an identity field.`, collection);
|
|
18191
18675
|
}
|
|
18192
18676
|
}
|
|
18193
18677
|
if (data.projection) {
|
|
18194
18678
|
for (const required of [...identityFields, ...renameEntries.map(([source]) => source)]) {
|
|
18195
|
-
if (!projectionIncludes(data.projection, required))
|
|
18679
|
+
if (!projectionIncludes(data.projection, required)) invalid2(`data.projection must include required field "${required}".`, collection);
|
|
18196
18680
|
}
|
|
18197
18681
|
}
|
|
18198
18682
|
return {
|
|
@@ -18203,105 +18687,105 @@ function normalizeFieldPatch(data, identityFields, collection) {
|
|
|
18203
18687
|
}
|
|
18204
18688
|
function normalizeData(data, collection) {
|
|
18205
18689
|
if (data === void 0) return void 0;
|
|
18206
|
-
if (!isRecord3(data))
|
|
18690
|
+
if (!isRecord3(data)) invalid2("data must be an object.", collection);
|
|
18207
18691
|
const hasFilter = isRecord3(data.filter) && Object.keys(data.filter).length > 0;
|
|
18208
|
-
if (hasFilter === (data.all === true))
|
|
18692
|
+
if (hasFilter === (data.all === true)) invalid2("data requires exactly one of a non-empty filter or all: true.", collection);
|
|
18209
18693
|
if (!isRecord3(data.identity) || data.identity.mode !== "fields" && data.identity.mode !== "source-id") {
|
|
18210
|
-
|
|
18694
|
+
invalid2("data.identity.mode must be fields or source-id.", collection);
|
|
18211
18695
|
}
|
|
18212
18696
|
const identity = data.identity.mode === "fields" ? { mode: "fields", fields: uniqueFields(data.identity.fields, "identity.fields", collection) } : {
|
|
18213
18697
|
mode: "source-id",
|
|
18214
18698
|
...data.identity.conflictBy === void 0 ? {} : { conflictBy: uniqueFields(data.identity.conflictBy, "identity.conflictBy", collection) }
|
|
18215
18699
|
};
|
|
18216
|
-
if (data.projection !== void 0 && !isRecord3(data.projection))
|
|
18700
|
+
if (data.projection !== void 0 && !isRecord3(data.projection)) invalid2("data.projection must be an object.", collection);
|
|
18217
18701
|
if (data.projection) assertBsonLiteral(data.projection, "data.projection", collection);
|
|
18218
18702
|
if (hasFilter) assertBsonLiteral(data.filter, "data.filter", collection);
|
|
18219
18703
|
const identityFields = identity.mode === "fields" ? identity.fields : ["_id", ...identity.conflictBy ?? []];
|
|
18220
18704
|
const patch = normalizeFieldPatch(data, identityFields, collection);
|
|
18221
18705
|
const batchSize = data.batchSize ?? 500;
|
|
18222
|
-
if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 1e4)
|
|
18706
|
+
if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 1e4) invalid2("data.batchSize must be an integer from 1 to 10000.", collection);
|
|
18223
18707
|
const maxDocuments = data.maxDocuments ?? DEFAULT_DATA_TASK_MAX_DOCUMENTS;
|
|
18224
18708
|
if (!Number.isInteger(maxDocuments) || maxDocuments < 1 || maxDocuments > 1e6) {
|
|
18225
|
-
|
|
18709
|
+
invalid2("data.maxDocuments must be an integer from 1 to 1000000.", collection);
|
|
18226
18710
|
}
|
|
18227
18711
|
const strategy = data.strategy ?? "upsert";
|
|
18228
|
-
if (strategy !== "upsert" && strategy !== "insert")
|
|
18712
|
+
if (strategy !== "upsert" && strategy !== "insert") invalid2("data.strategy must be upsert or insert.", collection);
|
|
18229
18713
|
return { ...data, ...patch, filter: hasFilter ? data.filter : {}, all: data.all === true, identity, strategy, batchSize, maxDocuments };
|
|
18230
18714
|
}
|
|
18231
18715
|
function normalizeIndexes(indexes, collection) {
|
|
18232
18716
|
if (indexes === void 0) return [];
|
|
18233
|
-
if (!Array.isArray(indexes))
|
|
18717
|
+
if (!Array.isArray(indexes)) invalid2("indexes must be an array.", collection);
|
|
18234
18718
|
const names = /* @__PURE__ */ new Set();
|
|
18235
18719
|
return indexes.map((index, position) => {
|
|
18236
|
-
if (!isRecord3(index) || !isRecord3(index.key) || Object.keys(index.key).length === 0)
|
|
18720
|
+
if (!isRecord3(index) || !isRecord3(index.key) || Object.keys(index.key).length === 0) invalid2(`indexes[${position}].key must be non-empty.`, collection);
|
|
18237
18721
|
assertBsonLiteral(index.key, `indexes[${position}].key`, collection);
|
|
18238
|
-
if (index.name !== void 0 && (typeof index.name !== "string" || index.name.trim() === ""))
|
|
18239
|
-
if (index.name && names.has(index.name))
|
|
18722
|
+
if (index.name !== void 0 && (typeof index.name !== "string" || index.name.trim() === "")) invalid2(`indexes[${position}].name must be non-empty.`, collection);
|
|
18723
|
+
if (index.name && names.has(index.name)) invalid2(`duplicate index name "${index.name}".`, collection);
|
|
18240
18724
|
if (index.name) names.add(index.name);
|
|
18241
|
-
if (index.options !== void 0 && !isRecord3(index.options))
|
|
18725
|
+
if (index.options !== void 0 && !isRecord3(index.options)) invalid2(`indexes[${position}].options must be an object.`, collection);
|
|
18242
18726
|
if (index.options) assertBsonLiteral(index.options, `indexes[${position}].options`, collection);
|
|
18243
|
-
if (index.options && "name" in index.options)
|
|
18244
|
-
if (Object.keys(index.key).length === 1 && index.key._id === 1 || index.name === "_id_")
|
|
18727
|
+
if (index.options && "name" in index.options) invalid2(`indexes[${position}].options.name is not allowed; use indexes[${position}].name.`, collection);
|
|
18728
|
+
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);
|
|
18245
18729
|
return { key: { ...index.key }, ...index.name ? { name: index.name } : {}, options: { ...index.options ?? {} } };
|
|
18246
18730
|
});
|
|
18247
18731
|
}
|
|
18248
18732
|
function normalizeVerify(verify, collection) {
|
|
18249
|
-
if (verify !== void 0 && !isRecord3(verify))
|
|
18733
|
+
if (verify !== void 0 && !isRecord3(verify)) invalid2("verify must be an object.", collection);
|
|
18250
18734
|
const input = verify;
|
|
18251
18735
|
const mode = input?.mode ?? "sample";
|
|
18252
|
-
if (mode !== "sample" && mode !== "full")
|
|
18736
|
+
if (mode !== "sample" && mode !== "full") invalid2("verify.mode must be sample or full.", collection);
|
|
18253
18737
|
const sampleSize = input?.sampleSize ?? 20;
|
|
18254
|
-
if (!Number.isInteger(sampleSize) || sampleSize < 1 || sampleSize > 1e3)
|
|
18738
|
+
if (!Number.isInteger(sampleSize) || sampleSize < 1 || sampleSize > 1e3) invalid2("verify.sampleSize must be an integer from 1 to 1000.", collection);
|
|
18255
18739
|
const fields = input?.fields === void 0 ? void 0 : uniqueFields(input.fields, "verify.fields", collection);
|
|
18256
18740
|
return { mode, sampleSize, ...fields ? { fields } : {} };
|
|
18257
18741
|
}
|
|
18258
18742
|
function normalizeBackup(backup, production) {
|
|
18259
|
-
if (backup !== void 0 && !isRecord3(backup))
|
|
18743
|
+
if (backup !== void 0 && !isRecord3(backup)) invalid2("backup must be an object.");
|
|
18260
18744
|
const input = backup;
|
|
18261
|
-
if (production && (typeof input?.dir !== "string" || input.dir.trim() === ""))
|
|
18745
|
+
if (production && (typeof input?.dir !== "string" || input.dir.trim() === "")) invalid2("production jobs require an explicit durable backup.dir.");
|
|
18262
18746
|
const dir = typeof input?.dir === "string" && input.dir.trim() ? input.dir : import_node_path3.default.join(import_node_os2.default.tmpdir(), "monsqlize-data-tasks");
|
|
18263
18747
|
const compression = input?.compression ?? "gzip";
|
|
18264
|
-
if (compression !== "gzip" && compression !== "none")
|
|
18265
|
-
if (input?.format !== void 0 && input.format !== "extended-jsonl")
|
|
18748
|
+
if (compression !== "gzip" && compression !== "none") invalid2("backup.compression must be gzip or none.");
|
|
18749
|
+
if (input?.format !== void 0 && input.format !== "extended-jsonl") invalid2("backup.format must be extended-jsonl.");
|
|
18266
18750
|
const retentionDays = input?.retentionDays ?? 7;
|
|
18267
|
-
if (!Number.isInteger(retentionDays) || retentionDays < 1)
|
|
18751
|
+
if (!Number.isInteger(retentionDays) || retentionDays < 1) invalid2("backup.retentionDays must be a positive integer.");
|
|
18268
18752
|
const maxBytes = input?.maxBytes ?? DEFAULT_DATA_TASK_BACKUP_MAX_BYTES;
|
|
18269
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
18753
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) invalid2("backup.maxBytes must be a positive safe integer.");
|
|
18270
18754
|
return { dir, format: "extended-jsonl", compression, retentionDays, maxBytes };
|
|
18271
18755
|
}
|
|
18272
18756
|
function normalizeLock(lock) {
|
|
18273
18757
|
if (!lock) return false;
|
|
18274
|
-
if (lock !== true && !isRecord3(lock))
|
|
18758
|
+
if (lock !== true && !isRecord3(lock)) invalid2("lock must be a boolean or lease options object.");
|
|
18275
18759
|
const options = lock === true ? {} : lock;
|
|
18276
18760
|
const ttlMs = options.ttlMs ?? 12e4;
|
|
18277
18761
|
const waitTimeoutMs = options.waitTimeoutMs ?? 0;
|
|
18278
|
-
if (!Number.isInteger(ttlMs) || ttlMs < 1e3)
|
|
18279
|
-
if (!Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 0)
|
|
18762
|
+
if (!Number.isInteger(ttlMs) || ttlMs < 1e3) invalid2("lock.ttlMs must be an integer of at least 1000.");
|
|
18763
|
+
if (!Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 0) invalid2("lock.waitTimeoutMs must be a non-negative integer.");
|
|
18280
18764
|
return { ttlMs, waitTimeoutMs };
|
|
18281
18765
|
}
|
|
18282
18766
|
function normalizeDataTaskJob(job) {
|
|
18283
|
-
if (!isRecord3(job))
|
|
18284
|
-
if (typeof job.name !== "string" || job.name.trim() === "")
|
|
18767
|
+
if (!isRecord3(job)) invalid2("job must be an object.");
|
|
18768
|
+
if (typeof job.name !== "string" || job.name.trim() === "") invalid2("job.name is required.");
|
|
18285
18769
|
validateConnection(job.source, "source");
|
|
18286
18770
|
validateConnection(job.target, "target");
|
|
18287
|
-
if (job.source === job.target)
|
|
18771
|
+
if (job.source === job.target) invalid2("source and target must be independent runtimes or connection options.");
|
|
18288
18772
|
const sourceOptions = connectionOptions(job.source);
|
|
18289
18773
|
const targetOptions = connectionOptions(job.target);
|
|
18290
|
-
if (sourceOptions && targetOptions && sourceOptions.databaseName === targetOptions.databaseName && sourceOptions.config?.uri === targetOptions.config?.uri)
|
|
18774
|
+
if (sourceOptions && targetOptions && sourceOptions.databaseName === targetOptions.databaseName && sourceOptions.config?.uri === targetOptions.config?.uri) invalid2("source and target resolve to the same MongoDB database.");
|
|
18291
18775
|
if (typeof job.targetEnvironment !== "string" || !ENVIRONMENTS.has(job.targetEnvironment.toLowerCase())) {
|
|
18292
|
-
|
|
18776
|
+
invalid2("targetEnvironment must be development, test, staging, production, prod, or live.");
|
|
18293
18777
|
}
|
|
18294
|
-
if (!Array.isArray(job.collections) || job.collections.length === 0)
|
|
18778
|
+
if (!Array.isArray(job.collections) || job.collections.length === 0) invalid2("collections must be a non-empty array.");
|
|
18295
18779
|
const targetNames = /* @__PURE__ */ new Set();
|
|
18296
18780
|
const collections = job.collections.map((collection, position) => {
|
|
18297
|
-
if (!isRecord3(collection) || typeof collection.name !== "string" || collection.name.trim() === "")
|
|
18781
|
+
if (!isRecord3(collection) || typeof collection.name !== "string" || collection.name.trim() === "") invalid2(`collections[${position}].name is required.`);
|
|
18298
18782
|
const name = collection.name.trim();
|
|
18299
18783
|
const targetName = typeof collection.targetName === "string" && collection.targetName.trim() ? collection.targetName.trim() : name;
|
|
18300
|
-
if (targetNames.has(targetName))
|
|
18784
|
+
if (targetNames.has(targetName)) invalid2(`target collection "${targetName}" is configured more than once.`, name);
|
|
18301
18785
|
targetNames.add(targetName);
|
|
18302
18786
|
const indexes = normalizeIndexes(collection.indexes, name);
|
|
18303
18787
|
const data = normalizeData(collection.data, name);
|
|
18304
|
-
if (indexes.length === 0 && !data)
|
|
18788
|
+
if (indexes.length === 0 && !data) invalid2("each collection requires indexes or data.", name);
|
|
18305
18789
|
return { name, targetName, indexes, ...data ? { data } : {}, verify: normalizeVerify(collection.verify, name) };
|
|
18306
18790
|
});
|
|
18307
18791
|
const production = PRODUCTION_ENVIRONMENTS.has(job.targetEnvironment.toLowerCase());
|