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.
@@ -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 invalid2 = (reason) => {
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 : invalid2("select must be a string or string array");
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
- invalid2("select array entries must be strings");
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
- invalid2("select must not be empty");
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
- invalid2("exclusion and prefix tokens are not supported");
1046
+ invalid3("exclusion and prefix tokens are not supported");
1040
1047
  }
1041
1048
  if (token.includes(".")) {
1042
- invalid2("dot-path fields are not supported");
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 isPlainObject4 = prototype === Object.prototype || prototype === null;
3598
- if (!Array.isArray(objectValue) && !isPlainObject4) {
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 sleep2(ms) {
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 sleep2(retryDelay);
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 sleep2(retryDelay);
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 sleep2(retryDelay);
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 sleep3(delay3);
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 sleep3(ms) {
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 sleep3(retryDelay);
15508
+ await sleep4(retryDelay);
15025
15509
  } catch (error) {
15026
15510
  if (attempt === retryTimes) throw error;
15027
- await sleep3(retryDelay);
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 invalid(message, collection) {
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") invalid(`${field} must be a monSQLize runtime or MongoDB MonSQLizeOptions.`);
18092
- if (typeof value.databaseName !== "string" || value.databaseName.trim() === "") invalid(`${field}.databaseName is required.`);
18093
- if (!isRecord3(value.config) || typeof value.config.uri !== "string" || value.config.uri.trim() === "") invalid(`${field}.config.uri is required.`);
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) invalid(`${pathName} must be a non-empty string array.`, collection);
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 === "")) invalid(`${pathName} must contain non-empty strings.`, collection);
18099
- if (new Set(fields).size !== fields.length) invalid(`${pathName} must not contain duplicates.`, collection);
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
- invalid(`${pathName} must contain only BSON-compatible literals.`, collection);
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)) invalid(`${pathName} must not contain circular values.`, collection);
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)) invalid(`${pathName} must contain only BSON-compatible literals.`, collection);
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)) invalid(`${pathName} must contain only BSON-compatible literals.`, collection);
18124
- if (stack.has(value)) invalid(`${pathName} must not contain circular values.`, collection);
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() === "") invalid(`${pathName} must be a non-empty field path.`, collection);
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
- invalid(`${pathName} contains an unsafe field path.`, collection);
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)) invalid("data.rename must be an object.", collection);
18151
- if (data.set !== void 0 && !isRecord3(data.set)) invalid("data.set must be an object.", collection);
18152
- if (data.unset !== void 0 && !Array.isArray(data.unset)) invalid("data.unset must be an array.", collection);
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)) invalid("data.rename source and destination must not overlap.", collection);
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
- invalid(`data.set.${field} must be a BSON-compatible literal.`, collection);
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) invalid("data.unset must not contain duplicates.", collection);
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) invalid("data.rename destinations must be unique.", collection);
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
- invalid(`${configuredPaths[left].label} conflicts with ${configuredPaths[right].label}.`, collection);
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
- invalid(`${configured.label} must not overlap _id or an identity field.`, collection);
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)) invalid(`data.projection must include required field "${required}".`, collection);
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)) invalid("data must be an object.", collection);
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)) invalid("data requires exactly one of a non-empty filter or all: true.", collection);
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
- invalid("data.identity.mode must be fields or source-id.", collection);
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)) invalid("data.projection must be an object.", collection);
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) invalid("data.batchSize must be an integer from 1 to 10000.", collection);
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
- invalid("data.maxDocuments must be an integer from 1 to 1000000.", collection);
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") invalid("data.strategy must be upsert or insert.", collection);
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)) invalid("indexes must be an array.", collection);
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) invalid(`indexes[${position}].key must be non-empty.`, collection);
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() === "")) invalid(`indexes[${position}].name must be non-empty.`, collection);
18239
- if (index.name && names.has(index.name)) invalid(`duplicate index name "${index.name}".`, collection);
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)) invalid(`indexes[${position}].options must be an object.`, collection);
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) invalid(`indexes[${position}].options.name is not allowed; use indexes[${position}].name.`, collection);
18244
- 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);
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)) invalid("verify must be an object.", collection);
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") invalid("verify.mode must be sample or full.", collection);
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) invalid("verify.sampleSize must be an integer from 1 to 1000.", collection);
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)) invalid("backup must be an object.");
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() === "")) invalid("production jobs require an explicit durable backup.dir.");
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") invalid("backup.compression must be gzip or none.");
18265
- if (input?.format !== void 0 && input.format !== "extended-jsonl") invalid("backup.format must be 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) invalid("backup.retentionDays must be a positive integer.");
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) invalid("backup.maxBytes must be a positive safe integer.");
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)) invalid("lock must be a boolean or lease options object.");
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) invalid("lock.ttlMs must be an integer of at least 1000.");
18279
- if (!Number.isInteger(waitTimeoutMs) || waitTimeoutMs < 0) invalid("lock.waitTimeoutMs must be a non-negative integer.");
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)) invalid("job must be an object.");
18284
- if (typeof job.name !== "string" || job.name.trim() === "") invalid("job.name is required.");
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) invalid("source and target must be independent runtimes or connection options.");
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) invalid("source and target resolve to the same MongoDB database.");
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
- invalid("targetEnvironment must be development, test, staging, production, prod, or live.");
18776
+ invalid2("targetEnvironment must be development, test, staging, production, prod, or live.");
18293
18777
  }
18294
- if (!Array.isArray(job.collections) || job.collections.length === 0) invalid("collections must be a non-empty array.");
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() === "") invalid(`collections[${position}].name is required.`);
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)) invalid(`target collection "${targetName}" is configured more than once.`, name);
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) invalid("each collection requires indexes or data.", name);
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());