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.
@@ -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 invalid2 = (reason) => {
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 : invalid2("select must be a string or string array");
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
- invalid2("select array entries must be strings");
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
- invalid2("select must not be empty");
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
- invalid2("exclusion and prefix tokens are not supported");
1029
+ invalid3("exclusion and prefix tokens are not supported");
1023
1030
  }
1024
1031
  if (token.includes(".")) {
1025
- invalid2("dot-path fields are not supported");
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 isPlainObject4 = prototype === Object.prototype || prototype === null;
3581
- if (!Array.isArray(objectValue) && !isPlainObject4) {
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 sleep2(ms) {
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 sleep2(retryDelay);
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 sleep2(retryDelay);
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 sleep2(retryDelay);
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 sleep3(delay3);
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 sleep3(ms) {
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 sleep3(retryDelay);
15491
+ await sleep4(retryDelay);
15008
15492
  } catch (error) {
15009
15493
  if (attempt === retryTimes) throw error;
15010
- await sleep3(retryDelay);
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 invalid(message, collection) {
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") invalid(`${field} must be a monSQLize runtime or MongoDB MonSQLizeOptions.`);
18078
- if (typeof value.databaseName !== "string" || value.databaseName.trim() === "") invalid(`${field}.databaseName is required.`);
18079
- if (!isRecord3(value.config) || typeof value.config.uri !== "string" || value.config.uri.trim() === "") invalid(`${field}.config.uri is required.`);
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) invalid(`${pathName} must be a non-empty string array.`, collection);
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 === "")) invalid(`${pathName} must contain non-empty strings.`, collection);
18085
- if (new Set(fields).size !== fields.length) invalid(`${pathName} must not contain duplicates.`, collection);
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
- invalid(`${pathName} must contain only BSON-compatible literals.`, collection);
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)) invalid(`${pathName} must not contain circular values.`, collection);
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)) invalid(`${pathName} must contain only BSON-compatible literals.`, collection);
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)) invalid(`${pathName} must contain only BSON-compatible literals.`, collection);
18110
- if (stack.has(value)) invalid(`${pathName} must not contain circular values.`, collection);
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() === "") invalid(`${pathName} must be a non-empty field path.`, collection);
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
- invalid(`${pathName} contains an unsafe field path.`, collection);
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)) invalid("data.rename must be an object.", collection);
18137
- if (data.set !== void 0 && !isRecord3(data.set)) invalid("data.set must be an object.", collection);
18138
- if (data.unset !== void 0 && !Array.isArray(data.unset)) invalid("data.unset must be an array.", collection);
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)) invalid("data.rename source and destination must not overlap.", collection);
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
- invalid(`data.set.${field} must be a BSON-compatible literal.`, collection);
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) invalid("data.unset must not contain duplicates.", collection);
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) invalid("data.rename destinations must be unique.", collection);
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
- invalid(`${configuredPaths[left].label} conflicts with ${configuredPaths[right].label}.`, collection);
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
- invalid(`${configured.label} must not overlap _id or an identity field.`, collection);
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)) invalid(`data.projection must include required field "${required}".`, collection);
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)) invalid("data must be an object.", collection);
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)) invalid("data requires exactly one of a non-empty filter or all: true.", collection);
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
- invalid("data.identity.mode must be fields or source-id.", collection);
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)) invalid("data.projection must be an object.", collection);
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) invalid("data.batchSize must be an integer from 1 to 10000.", collection);
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
- invalid("data.maxDocuments must be an integer from 1 to 1000000.", collection);
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") invalid("data.strategy must be upsert or insert.", collection);
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)) invalid("indexes must be an array.", collection);
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) invalid(`indexes[${position}].key must be non-empty.`, collection);
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() === "")) invalid(`indexes[${position}].name must be non-empty.`, collection);
18225
- if (index.name && names.has(index.name)) invalid(`duplicate index name "${index.name}".`, collection);
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)) invalid(`indexes[${position}].options must be an object.`, collection);
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) invalid(`indexes[${position}].options.name is not allowed; use indexes[${position}].name.`, collection);
18230
- if (Object.keys(index.key).length === 1 && index.key._id === 1 || index.name === "_id_") invalid("indexes must not declare the built-in _id index.", collection);
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)) invalid("verify must be an object.", collection);
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") invalid("verify.mode must be sample or full.", collection);
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) invalid("verify.sampleSize must be an integer from 1 to 1000.", collection);
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)) invalid("backup must be an object.");
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() === "")) invalid("production jobs require an explicit durable backup.dir.");
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") invalid("backup.compression must be gzip or none.");
18251
- if (input?.format !== void 0 && input.format !== "extended-jsonl") invalid("backup.format must be 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) invalid("backup.retentionDays must be a positive integer.");
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) invalid("backup.maxBytes must be a positive safe integer.");
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)) invalid("lock must be a boolean or lease options object.");
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) invalid("lock.ttlMs must be an integer of at least 1000.");
18265
- if (!Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 0) invalid("lock.waitTimeoutMs must be a non-negative integer.");
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)) invalid("job must be an object.");
18270
- if (typeof job.name !== "string" || job.name.trim() === "") invalid("job.name is required.");
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) invalid("source and target must be independent runtimes or connection options.");
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) invalid("source and target resolve to the same MongoDB database.");
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
- invalid("targetEnvironment must be development, test, staging, production, prod, or live.");
18762
+ invalid2("targetEnvironment must be development, test, staging, production, prod, or live.");
18279
18763
  }
18280
- if (!Array.isArray(job.collections) || job.collections.length === 0) invalid("collections must be a non-empty array.");
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() === "") invalid(`collections[${position}].name is required.`);
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)) invalid(`target collection "${targetName}" is configured more than once.`, name);
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) invalid("each collection requires indexes or data.", name);
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());