@rpcbase/migrations 0.1.0 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"assertCurrent.d.ts","sourceRoot":"","sources":["../src/assertCurrent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,aAAa,EACb,oBAAoB,EACrB,MAAM,SAAS,CAAA;AAGhB,eAAO,MAAM,uBAAuB,GAClC,UAAU,yBAAyB,EACnC,UAAU,yBAAyB,EACnC,UAAS,oBAAyB,KACjC,OAAO,CAAC,aAAa,CAYvB,CAAA"}
1
+ {"version":3,"file":"assertCurrent.d.ts","sourceRoot":"","sources":["../src/assertCurrent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,aAAa,EACb,oBAAoB,EACrB,MAAM,SAAS,CAAA;AAGhB,eAAO,MAAM,uBAAuB,GAClC,UAAU,yBAAyB,EACnC,UAAU,yBAAyB,EACnC,UAAS,oBAAyB,KACjC,OAAO,CAAC,aAAa,CAcvB,CAAA"}
@@ -4,6 +4,7 @@ export type CreateMigrationDatabaseProviderOptions = {
4
4
  client: MongoClient;
5
5
  appName: string;
6
6
  tenantCollection?: string;
7
+ initializingTenantStaleAfterMs?: number;
7
8
  filesystemRequired?: (tenantId: string, signal: AbortSignal) => boolean | Promise<boolean>;
8
9
  };
9
10
  export declare const createMigrationDatabaseProvider: (options: CreateMigrationDatabaseProviderOptions) => MigrationDatabaseProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"databaseProvider.d.ts","sourceRoot":"","sources":["../src/databaseProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAiBxD,MAAM,MAAM,sCAAsC,GAAG;IACnD,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC3F,CAAA;AAED,eAAO,MAAM,+BAA+B,GAC1C,SAAS,sCAAsC,KAC9C,yBAuCF,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAI,IAAI,EAAE,KAAG,MAAyB,CAAA"}
1
+ {"version":3,"file":"databaseProvider.d.ts","sourceRoot":"","sources":["../src/databaseProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAiBxD,MAAM,MAAM,sCAAsC,GAAG;IACnD,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8BAA8B,CAAC,EAAE,MAAM,CAAA;IACvC,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC3F,CAAA;AAED,eAAO,MAAM,+BAA+B,GAC1C,SAAS,sCAAsC,KAC9C,yBAkEF,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAI,IAAI,EAAE,KAAG,MAAyB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"diffResources.d.ts","sourceRoot":"","sources":["../src/diffResources.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAKV,cAAc,EACd,kBAAkB,EAEnB,MAAM,SAAS,CAAA;AAwChB,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,cAAc,EACtB,OAAO,cAAc,KACpB,kBAoBF,CAAA"}
1
+ {"version":3,"file":"diffResources.d.ts","sourceRoot":"","sources":["../src/diffResources.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAKV,cAAc,EACd,kBAAkB,EAEnB,MAAM,SAAS,CAAA;AAyChB,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,cAAc,EACtB,OAAO,cAAc,KACpB,kBAoBF,CAAA"}
package/dist/helpers.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { Db } from 'mongodb';
2
- import { MigrationHelpers } from './types';
2
+ import { MigrationHelpers, MongoResources } from './types';
3
3
  export type CreateMigrationHelpersOptions = {
4
4
  searchTimeoutMs?: number;
5
5
  searchPollIntervalMs?: number;
6
6
  };
7
7
  export declare const createMigrationHelpers: (db: Db, signal: AbortSignal, options?: CreateMigrationHelpersOptions) => MigrationHelpers;
8
+ export declare const reconcileRuntimeIndexOptions: (db: Db, resources: MongoResources, signal: AbortSignal) => Promise<void>;
8
9
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAY,MAAM,SAAS,CAAA;AAI3C,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,SAAS,CAAA;AAwC/D,MAAM,MAAM,6BAA6B,GAAG;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,IAAI,EAAE,EACN,QAAQ,WAAW,EACnB,UAAS,6BAAkC,KAC1C,gBAoJF,CAAA"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAY,MAAM,SAAS,CAAA;AAI3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAwC/D,MAAM,MAAM,6BAA6B,GAAG;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,IAAI,EAAE,EACN,QAAQ,WAAW,EACnB,UAAS,6BAAkC,KAC1C,gBAwJF,CAAA;AAED,eAAO,MAAM,4BAA4B,GACvC,IAAI,EAAE,EACN,WAAW,cAAc,EACzB,QAAQ,WAAW,KAClB,OAAO,CAAC,IAAI,CAoCd,CAAA"}
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var scopes = /* @__PURE__ */ new Set([
20
20
  "tenant",
21
21
  "filesystem"
22
22
  ]);
23
+ var runtimeIndexOptions = /* @__PURE__ */ new Set(["expireAfterSeconds"]);
23
24
  var assertName = (value, label) => {
24
25
  const normalized = value.trim();
25
26
  if (!normalized) throw new Error(`${label} is required`);
@@ -59,12 +60,18 @@ var normalizeCollection = (resource) => {
59
60
  var normalizeIndex = (resource) => {
60
61
  assertScope(resource.scope);
61
62
  if (Object.keys(resource.key).length === 0) throw new Error("Index key cannot be empty");
63
+ const optionNames = new Set(Object.keys(resource.options ?? {}));
64
+ const unsupportedRuntimeOption = Object.keys(resource.runtimeOptions ?? {}).find((name) => !runtimeIndexOptions.has(name));
65
+ if (unsupportedRuntimeOption) throw new Error(`Unsupported runtime-managed index option: ${unsupportedRuntimeOption}`);
66
+ const overlappingRuntimeOption = Object.keys(resource.runtimeOptions ?? {}).find((name) => optionNames.has(name));
67
+ if (overlappingRuntimeOption) throw new Error(`Index option ${overlappingRuntimeOption} cannot be both fixed and runtime-managed`);
62
68
  return {
63
69
  scope: resource.scope,
64
70
  collection: assertName(resource.collection, "Index collection"),
65
71
  name: assertName(resource.name, "Index name"),
66
72
  key: structuredClone(resource.key),
67
- ...resource.options ? { options: copyDocument(resource.options) } : {}
73
+ ...resource.options ? { options: copyDocument(resource.options) } : {},
74
+ ...resource.runtimeOptions ? { runtimeOptions: copyDocument(resource.runtimeOptions) } : {}
68
75
  };
69
76
  };
70
77
  var normalizeSearchIndex = (resource) => {
@@ -95,7 +102,8 @@ var checksumInput = (resources) => ({
95
102
  indexes: resources.indexes.map((resource) => ({
96
103
  ...resource,
97
104
  key: Object.entries(resource.key),
98
- options: resource.options ?? {}
105
+ options: resource.options ?? {},
106
+ ...resource.runtimeOptions ? { runtimeOptions: Object.keys(resource.runtimeOptions).sort() } : {}
99
107
  })),
100
108
  searchIndexes: resources.searchIndexes,
101
109
  collectionValidators: resources.collectionValidators
@@ -123,15 +131,18 @@ var mergeMongoResources = (resourceSets) => {
123
131
  const indexes = /* @__PURE__ */ new Map();
124
132
  const searchIndexes = /* @__PURE__ */ new Map();
125
133
  const validators = /* @__PURE__ */ new Map();
126
- const merge = (target, resource, identity, label) => {
134
+ const merge = (target, resource, identity, label, comparisonValue = (value) => value) => {
127
135
  const key = identity(resource);
128
136
  const previous = target.get(key);
129
- if (previous && canonicalStringify(previous) !== canonicalStringify(resource)) throw new Error(`Conflicting ${label} resource across migration sources: ${key}`);
137
+ if (previous && canonicalStringify(comparisonValue(previous)) !== canonicalStringify(comparisonValue(resource))) throw new Error(`Conflicting ${label} resource across migration sources: ${key}`);
130
138
  target.set(key, resource);
131
139
  };
132
140
  for (const resources of resourceSets) {
133
141
  for (const resource of resources.collections) merge(collections, resource, collectionIdentity$1, "collection");
134
- for (const resource of resources.indexes) merge(indexes, resource, indexIdentity$1, "index");
142
+ for (const resource of resources.indexes) merge(indexes, resource, indexIdentity$1, "index", (value) => ({
143
+ ...value,
144
+ key: Object.entries(value.key)
145
+ }));
135
146
  for (const resource of resources.searchIndexes) merge(searchIndexes, resource, indexIdentity$1, "Search index");
136
147
  for (const resource of resources.collectionValidators) merge(validators, resource, validatorIdentity$1, "collection validator");
137
148
  }
@@ -149,7 +160,8 @@ var indexIdentity = (resource) => `${resource.scope}:${resource.collection}:${re
149
160
  var validatorIdentity = (resource) => `${resource.scope}:${resource.collection}`;
150
161
  var indexComparisonValue = (resource) => ({
151
162
  ...resource,
152
- key: Object.entries(resource.key)
163
+ key: Object.entries(resource.key),
164
+ runtimeOptions: Object.keys(resource.runtimeOptions ?? {}).sort()
153
165
  });
154
166
  var diffSet = (before, after, identity, comparisonValue = (resource) => resource) => {
155
167
  const beforeById = new Map(before.map((resource) => [identity(resource), resource]));
@@ -508,7 +520,10 @@ var inspectMongoResources = async (db, resources, options = {}) => {
508
520
  continue;
509
521
  }
510
522
  const actualDefinition = normalizeIndexDefinition(actual.key ?? {}, actual, expected.key);
511
- const expectedDefinition = normalizeIndexDefinition(expected.key, expected.options ?? {});
523
+ const expectedDefinition = normalizeIndexDefinition(expected.key, {
524
+ ...expected.options ?? {},
525
+ ...expected.runtimeOptions ?? {}
526
+ });
512
527
  if (!same$1(actualDefinition.key, expectedDefinition.key)) divergences.push({
513
528
  code: "index_key_mismatch",
514
529
  scope: expected.scope,
@@ -520,15 +535,20 @@ var inspectMongoResources = async (db, resources, options = {}) => {
520
535
  });
521
536
  const expectedOptions = expectedDefinition.options;
522
537
  const actualOptions = actualDefinition.options;
523
- if (!same$1(actualOptions, expectedOptions)) divergences.push({
524
- code: "index_options_mismatch",
525
- scope: expected.scope,
526
- collection: expected.collection,
527
- resource: expected.name,
528
- expected: expectedOptions,
529
- actual: actualOptions,
530
- message: message(expected.collection, expected.name, "index options differ")
531
- });
538
+ if (!same$1(actualOptions, expectedOptions)) {
539
+ const runtimeOptionNames = new Set(Object.keys(expected.runtimeOptions ?? {}));
540
+ const fixedOptions = (indexOptions) => Object.fromEntries(Object.entries(indexOptions).filter(([name]) => !runtimeOptionNames.has(name)));
541
+ const runtimeOnly = runtimeOptionNames.size > 0 && same$1(fixedOptions(actualOptions), fixedOptions(expectedOptions));
542
+ divergences.push({
543
+ code: runtimeOnly ? "runtime_index_options_mismatch" : "index_options_mismatch",
544
+ scope: expected.scope,
545
+ collection: expected.collection,
546
+ resource: expected.name,
547
+ expected: expectedOptions,
548
+ actual: actualOptions,
549
+ message: message(expected.collection, expected.name, "index options differ")
550
+ });
551
+ }
532
552
  }
533
553
  for (const expected of resources.collectionValidators) {
534
554
  const collection = collectionsByName.get(expected.collection);
@@ -757,12 +777,45 @@ var createMigrationHelpers = (db, signal, options = {}) => {
757
777
  });
758
778
  for (const index of resources.indexes) await ensureIndex(index.collection, index.key, {
759
779
  name: index.name,
760
- ...index.options ?? {}
780
+ ...index.options ?? {},
781
+ ...index.runtimeOptions ?? {}
761
782
  });
762
783
  for (const index of resources.searchIndexes) await ensureSearchIndex(index.collection, index.name, index.definition);
763
784
  }
764
785
  };
765
786
  };
787
+ var reconcileRuntimeIndexOptions = async (db, resources, signal) => {
788
+ for (const index of resources.indexes) {
789
+ const runtimeOptions = index.runtimeOptions ?? {};
790
+ if (Object.keys(runtimeOptions).length === 0) continue;
791
+ signal.throwIfAborted();
792
+ let indexes;
793
+ try {
794
+ indexes = await db.collection(index.collection).listIndexes().toArray();
795
+ } catch (error) {
796
+ if (namespaceMissing(error)) continue;
797
+ throw error;
798
+ }
799
+ const actual = indexes.find((candidate) => candidate.name === index.name);
800
+ if (!actual) continue;
801
+ const actualDefinition = normalizeIndexDefinition(actual.key ?? {}, actual, index.key);
802
+ const expectedDefinition = normalizeIndexDefinition(index.key, {
803
+ ...index.options ?? {},
804
+ ...runtimeOptions
805
+ });
806
+ const runtimeOptionNames = new Set(Object.keys(runtimeOptions));
807
+ const fixedOptions = (options) => Object.fromEntries(Object.entries(options).filter(([name]) => !runtimeOptionNames.has(name)));
808
+ if (!same(actualDefinition.key, expectedDefinition.key) || !same(fixedOptions(actualDefinition.options), fixedOptions(expectedDefinition.options))) continue;
809
+ if (!Object.keys(runtimeOptions).some((name) => !same(actualDefinition.options[name], expectedDefinition.options[name]))) continue;
810
+ await db.command({
811
+ collMod: index.collection,
812
+ index: {
813
+ name: index.name,
814
+ ...runtimeOptions
815
+ }
816
+ });
817
+ }
818
+ };
766
819
  //#endregion
767
820
  //#region src/errors.ts
768
821
  var MigrationIntegrityError = class extends Error {
@@ -811,11 +864,21 @@ var createMigrationDatabaseProvider = (options) => {
811
864
  const appName = normalizeAppName(options.appName);
812
865
  const globalDbName = `${appName}-global-db`;
813
866
  const tenantCollection = options.tenantCollection?.trim() || "rbtenants";
867
+ const initializingTenantStaleAfterMs = options.initializingTenantStaleAfterMs ?? 5 * 6e4;
868
+ if (!Number.isFinite(initializingTenantStaleAfterMs) || initializingTenantStaleAfterMs < 0) throw new Error("initializingTenantStaleAfterMs must be a non-negative finite number");
869
+ const staleInitializingFilter = () => ({
870
+ provisioningStatus: "initializing",
871
+ $or: [{ provisioningStartedAt: { $exists: false } }, { provisioningStartedAt: { $lte: new Date(Date.now() - initializingTenantStaleAfterMs) } }]
872
+ });
814
873
  return {
815
874
  global: () => options.client.db(globalDbName),
816
875
  tenantIds: async (signal) => {
817
876
  signal.throwIfAborted();
818
- const documents = await options.client.db(globalDbName).collection(tenantCollection).find({ $or: [{ provisioningStatus: { $exists: false } }, { provisioningStatus: "active" }] }, { projection: { tenantId: 1 } }).sort({ tenantId: 1 }).toArray();
877
+ const documents = await options.client.db(globalDbName).collection(tenantCollection).find({ $or: [
878
+ { provisioningStatus: { $exists: false } },
879
+ { provisioningStatus: "active" },
880
+ staleInitializingFilter()
881
+ ] }, { projection: { tenantId: 1 } }).sort({ tenantId: 1 }).toArray();
819
882
  signal.throwIfAborted();
820
883
  return [...new Set(documents.flatMap((document) => typeof document.tenantId === "string" && document.tenantId.trim() ? [normalizeTenantId(document.tenantId)] : []))];
821
884
  },
@@ -827,6 +890,21 @@ var createMigrationDatabaseProvider = (options) => {
827
890
  return Boolean(tenant);
828
891
  },
829
892
  tenant: (tenantId) => options.client.db(`${appName}-${normalizeTenantId(tenantId)}-db`),
893
+ activateRecoveredTenant: async (tenantId, signal) => {
894
+ signal.throwIfAborted();
895
+ const result = await options.client.db(globalDbName).collection(tenantCollection).updateOne({
896
+ tenantId: normalizeTenantId(tenantId),
897
+ ...staleInitializingFilter()
898
+ }, {
899
+ $set: {
900
+ provisioningStatus: "active",
901
+ provisionedAt: /* @__PURE__ */ new Date()
902
+ },
903
+ $unset: { provisioningError: "" }
904
+ });
905
+ signal.throwIfAborted();
906
+ return result.matchedCount === 1;
907
+ },
830
908
  filesystemRequired: options.filesystemRequired ?? (() => false),
831
909
  filesystem: (tenantId) => options.client.db(`${appName}-${normalizeTenantId(tenantId)}-filesystem-db`)
832
910
  };
@@ -968,7 +1046,9 @@ var planMigrations = async (registry, provider, options = {}) => {
968
1046
  databases,
969
1047
  hasPending: databases.some((database) => database.pending.length > 0 || database.running.length > 0 || database.failed.length > 0),
970
1048
  hasOffline: databases.some((database) => database.pending.some((migration) => migration.mode === "offline") || [...database.running, ...database.failed].some((id) => registry.migrationsById.get(id)?.mode === "offline")),
971
- hasErrors: databases.some((database) => database.integrityErrors.length > 0 || database.running.length === 0 && database.failed.length === 0 && database.resourceDivergences.length > 0)
1049
+ hasRollbackIncompatible: databases.some((database) => database.pending.some((migration) => migration.rollback === "incompatible") || [...database.running, ...database.failed].some((id) => registry.migrationsById.get(id)?.rollback === "incompatible")),
1050
+ hasAppliedRollbackIncompatible: databases.some((database) => database.applied.some((id) => registry.migrationsById.get(id)?.rollback === "incompatible")),
1051
+ hasErrors: databases.some((database) => database.integrityErrors.length > 0 || database.running.length === 0 && database.failed.length === 0 && database.resourceDivergences.some((divergence) => divergence.code !== "runtime_index_options_mismatch"))
972
1052
  };
973
1053
  };
974
1054
  //#endregion
@@ -1118,7 +1198,7 @@ var combineSignals = (...signals) => {
1118
1198
  };
1119
1199
  var assertPlanCanRun = (plan) => {
1120
1200
  const retrying = plan.running.length > 0 || plan.failed.length > 0;
1121
- const errors = [...plan.integrityErrors, ...retrying ? [] : plan.resourceDivergences.map((divergence) => divergence.message)];
1201
+ const errors = [...plan.integrityErrors, ...retrying ? [] : plan.resourceDivergences.filter((divergence) => divergence.code !== "runtime_index_options_mismatch").map((divergence) => divergence.message)];
1122
1202
  if (errors.length > 0) throw new MigrationIntegrityError(`${plan.database}: ${errors.join("; ")}`);
1123
1203
  };
1124
1204
  var recordForMigration = (migration, lock, attempt, release) => ({
@@ -1276,8 +1356,15 @@ var applyMigration = async (registry, target, migration, lock, signal, release)
1276
1356
  };
1277
1357
  var sourcePredecessorsApplied = (registry, migration, applied) => registry.sources.find((source) => source.name === migration.source)?.migrations.filter((candidate) => candidate.scope === migration.scope && candidate.sourcePosition < migration.sourcePosition).every((candidate) => applied.has(candidate.qualifiedId)) ?? false;
1278
1358
  var runTarget = async (registry, target, lock, signal, options, earlierScopeDependencyApplied) => {
1279
- const plan = await planMigrationTarget(registry, target, { signal });
1359
+ let plan = await planMigrationTarget(registry, target, { signal });
1280
1360
  assertPlanCanRun(plan);
1361
+ if (plan.resourceDivergences.some((divergence) => divergence.code === "runtime_index_options_mismatch")) {
1362
+ const history = await readMigrationHistory(target.db);
1363
+ const expectedResources = getExpectedResources(registry, target.scope, history);
1364
+ await reconcileRuntimeIndexOptions(target.db, expectedResources, signal);
1365
+ plan = await planMigrationTarget(registry, target, { signal });
1366
+ assertPlanCanRun(plan);
1367
+ }
1281
1368
  const applied = new Set(plan.applied);
1282
1369
  const migrationsToRun = registry.migrations.filter((migration) => migration.scope === target.scope && !applied.has(migration.qualifiedId));
1283
1370
  for (const migration of migrationsToRun) {
@@ -1353,10 +1440,20 @@ var runMigrations = async (registry, provider, options = {}) => {
1353
1440
  return false;
1354
1441
  });
1355
1442
  });
1356
- return await planMigrations(registry, provider, {
1443
+ const plan = await planMigrations(registry, provider, {
1357
1444
  tenantId: options.tenantId,
1358
1445
  signal
1359
1446
  });
1447
+ let activatedRecoveredTenant = false;
1448
+ if (!options.initializeTenant && provider.activateRecoveredTenant) for (const tenantId of tenantApplied.keys()) {
1449
+ const tenantPlans = plan.databases.filter((database) => database.tenantId === tenantId);
1450
+ if (!(tenantPlans.length > 0 && tenantPlans.every((database) => database.pending.length === 0 && database.running.length === 0 && database.failed.length === 0 && database.integrityErrors.length === 0 && database.resourceDivergences.length === 0))) continue;
1451
+ if (await provider.activateRecoveredTenant(tenantId, signal)) activatedRecoveredTenant = true;
1452
+ }
1453
+ return activatedRecoveredTenant ? await planMigrations(registry, provider, {
1454
+ tenantId: options.tenantId,
1455
+ signal
1456
+ }) : plan;
1360
1457
  } finally {
1361
1458
  await lock.release();
1362
1459
  }
@@ -1377,7 +1474,7 @@ var assertMigrationsCurrent = async (registry, provider, options = {}) => {
1377
1474
  if (database.running.length > 0) errors.push(`${database.database}: migration running`);
1378
1475
  if (database.failed.length > 0) errors.push(`${database.database}: migration failed`);
1379
1476
  errors.push(...database.integrityErrors.map((error) => `${database.database}: ${error}`));
1380
- errors.push(...database.resourceDivergences.map((divergence) => `${database.database}: ${divergence.message}`));
1477
+ errors.push(...database.resourceDivergences.filter((divergence) => divergence.code !== "runtime_index_options_mismatch").map((divergence) => `${database.database}: ${divergence.message}`));
1381
1478
  }
1382
1479
  if (errors.length > 0) throw new MigrationIntegrityError(errors.join("; "));
1383
1480
  return plan;
@@ -1561,6 +1658,6 @@ var testSingleMigration = async (options) => {
1561
1658
  }
1562
1659
  };
1563
1660
  //#endregion
1564
- export { MIGRATIONS_COLLECTION, MIGRATION_LOCKS_COLLECTION, MigrationIntegrityError, MigrationLockLostError, MigrationLockUnavailableError, acquireMigrationLock, assertMigrationsCurrent, canonicalChecksum, canonicalStringify, collectMigrationTargets, compileMigrationRegistry, computeMigrationIntegrity, createMigrationDatabaseProvider, createMigrationHelpers, createMigrationIntegrityPlugin, defineMigration, defineMigrationSource, defineMongoResources, diffMongoResources, filterMongoResources, getExpectedResources, getMigrationDatabaseName, initializeTenantMigrations, inspectMongoResources, mergeMongoResources, planMigrationTarget, planMigrations, readMigrationHistory, runMigrations, sha256, testMigrationRegistry, testSingleMigration };
1661
+ export { MIGRATIONS_COLLECTION, MIGRATION_LOCKS_COLLECTION, MigrationIntegrityError, MigrationLockLostError, MigrationLockUnavailableError, acquireMigrationLock, assertMigrationsCurrent, canonicalChecksum, canonicalStringify, collectMigrationTargets, compileMigrationRegistry, computeMigrationIntegrity, createMigrationDatabaseProvider, createMigrationHelpers, createMigrationIntegrityPlugin, defineMigration, defineMigrationSource, defineMongoResources, diffMongoResources, filterMongoResources, getExpectedResources, getMigrationDatabaseName, initializeTenantMigrations, inspectMongoResources, mergeMongoResources, planMigrationTarget, planMigrations, readMigrationHistory, reconcileRuntimeIndexOptions, runMigrations, sha256, testMigrationRegistry, testSingleMigration };
1565
1662
 
1566
1663
  //# sourceMappingURL=index.js.map