@dudousxd/nestjs-durable-store-mikro-orm 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,7 +39,8 @@ __export(index_exports, {
39
39
  WorkflowRunRepository: () => WorkflowRunRepository,
40
40
  durableEntities: () => durableEntities,
41
41
  durableManagedTables: () => durableManagedTables,
42
- ensureMikroOrmDurableSchema: () => ensureMikroOrmDurableSchema
42
+ ensureMikroOrmDurableSchema: () => ensureMikroOrmDurableSchema,
43
+ mikroOrmSchemaRepairWarning: () => mikroOrmSchemaRepairWarning
43
44
  });
44
45
  module.exports = __toCommonJS(index_exports);
45
46
 
@@ -721,6 +722,78 @@ var MARKER_TABLE = "durable_schema_meta";
721
722
  var MARKER_ROW_ID = "durable";
722
723
  var SCHEMA_LOCK_NAME = "durable_schema";
723
724
  var SCHEMA_REVISION = 1;
725
+ function mikroOrmSchemaRepairWarning(repairs) {
726
+ return [
727
+ `[nestjs-durable] store-mikro-orm repaired the durable schema at boot: ${repairs.join("; ")}.`,
728
+ "The database had drifted from the schema this library version expects (it was created by an",
729
+ "older version, or a migration left these out), so they were applied at runtime and nothing",
730
+ "recorded them in your migration history. To make them permanent, add a migration that calls",
731
+ "`ensureMikroOrmDurableSchema(orm)`. This warning appears only when a repair was applied; a",
732
+ "schema that is already current logs nothing."
733
+ ].join(" ");
734
+ }
735
+ __name(mikroOrmSchemaRepairWarning, "mikroOrmSchemaRepairWarning");
736
+ function unquote(identifier) {
737
+ return identifier.replace(/^[`"']|[`"']$/g, "");
738
+ }
739
+ __name(unquote, "unquote");
740
+ function describeDurableStatement(statement) {
741
+ const table = targetTable(statement) ?? "?";
742
+ if (/^create\s+table\b/i.test(statement)) return [
743
+ `created table ${table}`
744
+ ];
745
+ const createIndex = statement.match(/^create\s+(?:unique\s+)?index\s+(?:if\s+not\s+exists\s+)?([`"']?[\w$]+[`"']?)/i);
746
+ if (createIndex?.[1]) return [
747
+ `created index ${unquote(createIndex[1])} on ${table}`
748
+ ];
749
+ if (/^alter\s+table\b/i.test(statement)) {
750
+ const out = [];
751
+ let other = false;
752
+ for (const action of splitTopLevel(statement.replace(/^alter\s+table\s+[`"']?[\w$]+[`"']?\s*/i, ""))) {
753
+ const index = action.match(/^add\s+(?:unique\s+|fulltext\s+)?(?:index|key)\s+([`"']?[\w$]+[`"']?)/i);
754
+ const constraint = action.match(/^add\s+constraint\s+([`"']?[\w$]+[`"']?)/i);
755
+ const column = action.match(/^add\s+(?:column\s+)?([`"']?[\w$]+[`"']?)/i);
756
+ if (index?.[1]) out.push(`created index ${unquote(index[1])} on ${table}`);
757
+ else if (constraint?.[1]) out.push(`added constraint ${unquote(constraint[1])} on ${table}`);
758
+ else if (column?.[1] && !/^add\s+(?:primary|unique|fulltext|foreign)\b/i.test(action)) out.push(`added column ${table}.${unquote(column[1])}`);
759
+ else other = true;
760
+ }
761
+ if (other || out.length === 0) {
762
+ const flat = statement.replace(/\s+/g, " ");
763
+ out.push(`altered ${table} (${flat.length > 160 ? `${flat.slice(0, 157)}...` : flat})`);
764
+ }
765
+ return out;
766
+ }
767
+ return [
768
+ `ran ${statement.replace(/\s+/g, " ").slice(0, 160)}`
769
+ ];
770
+ }
771
+ __name(describeDurableStatement, "describeDurableStatement");
772
+ function splitTopLevel(text) {
773
+ const parts = [];
774
+ let depth = 0;
775
+ let quote;
776
+ let current = "";
777
+ for (const ch of text) {
778
+ if (quote) {
779
+ if (ch === quote) quote = void 0;
780
+ } else if (ch === "'" || ch === '"' || ch === "`") {
781
+ quote = ch;
782
+ } else if (ch === "(") {
783
+ depth++;
784
+ } else if (ch === ")") {
785
+ depth--;
786
+ } else if (ch === "," && depth === 0) {
787
+ parts.push(current.trim());
788
+ current = "";
789
+ continue;
790
+ }
791
+ current += ch;
792
+ }
793
+ if (current.trim()) parts.push(current.trim());
794
+ return parts;
795
+ }
796
+ __name(splitTopLevel, "splitTopLevel");
724
797
  function compareStrings(a, b) {
725
798
  return a < b ? -1 : a > b ? 1 : 0;
726
799
  }
@@ -839,7 +912,8 @@ async function releaseSchemaLock(orm, dialect) {
839
912
  }
840
913
  }
841
914
  __name(releaseSchemaLock, "releaseSchemaLock");
842
- async function ensureMikroOrmDurableSchema(orm) {
915
+ async function ensureMikroOrmDurableSchema(orm, options = {}) {
916
+ const logger = options.logger ?? console;
843
917
  const dialect = detectSqlDialect(orm);
844
918
  await orm.em.getConnection().execute(createMarkerTableSql());
845
919
  const expected = computeExpectedFingerprint(orm, DURABLE_TABLE_NAMES);
@@ -852,8 +926,9 @@ async function ensureMikroOrmDurableSchema(orm) {
852
926
  if (await readStoredFingerprint(orm) === expected) {
853
927
  return;
854
928
  }
855
- await healDurableSchema(orm);
929
+ const { repairs, freshInstall } = await healDurableSchema(orm);
856
930
  await writeFingerprint(orm, dialect, expected);
931
+ if (repairs.length > 0 && !freshInstall) logger.warn(mikroOrmSchemaRepairWarning(repairs));
857
932
  } finally {
858
933
  await releaseSchemaLock(orm, dialect);
859
934
  }
@@ -868,9 +943,14 @@ async function healDurableSchema(orm) {
868
943
  return table !== void 0 && DURABLE_TABLE_NAMES.has(table);
869
944
  });
870
945
  const connection = orm.em.getConnection();
946
+ const created = new Set(ours.filter((statement) => /^create\s+table\b/i.test(statement)).map((statement) => targetTable(statement)));
947
+ const repairs = [];
871
948
  for (const statement of ours) {
872
949
  try {
873
950
  await connection.execute(statement);
951
+ const table = targetTable(statement);
952
+ const isCreateTable = /^create\s+table\b/i.test(statement);
953
+ if (isCreateTable || !created.has(table)) repairs.push(...describeDurableStatement(statement));
874
954
  } catch (err) {
875
955
  if (isRequiredStructure(statement)) {
876
956
  throw err;
@@ -879,7 +959,17 @@ async function healDurableSchema(orm) {
879
959
  console.warn(`[nestjs-durable-store-mikro-orm] skipped a non-structural durable-schema statement that failed; the column is left as-is (functional) \u2014 repair the data out of band to converge its type. Statement: ${statement} \u2014 ${message}`);
880
960
  }
881
961
  }
882
- await alignDurableCollation(orm);
962
+ const converted = await alignDurableCollation(orm);
963
+ for (const { table, from, to } of converted) {
964
+ if (!created.has(table)) repairs.push(`converted ${table} collation from ${from} to ${to}`);
965
+ }
966
+ const freshInstall = [
967
+ ...DURABLE_TABLE_NAMES
968
+ ].every((table) => created.has(table));
969
+ return {
970
+ repairs,
971
+ freshInstall
972
+ };
883
973
  }
884
974
  __name(healDurableSchema, "healDurableSchema");
885
975
  function isSafeSqlIdentifier(value) {
@@ -887,17 +977,18 @@ function isSafeSqlIdentifier(value) {
887
977
  }
888
978
  __name(isSafeSqlIdentifier, "isSafeSqlIdentifier");
889
979
  async function alignDurableCollation(orm) {
980
+ const converted = [];
890
981
  const platform = String(orm.em.getPlatform().constructor.name).toLowerCase();
891
982
  if (!platform.includes("mysql") && !platform.includes("maria")) {
892
- return;
983
+ return converted;
893
984
  }
894
985
  const collate = orm.config.get("collate");
895
986
  if (typeof collate !== "string" || !isSafeSqlIdentifier(collate)) {
896
- return;
987
+ return converted;
897
988
  }
898
989
  const charset = collate.split("_")[0];
899
990
  if (!charset || !isSafeSqlIdentifier(charset)) {
900
- return;
991
+ return converted;
901
992
  }
902
993
  const connection = orm.em.getConnection();
903
994
  for (const table of DURABLE_TABLE_NAMES) {
@@ -910,11 +1001,17 @@ async function alignDurableCollation(orm) {
910
1001
  continue;
911
1002
  }
912
1003
  await connection.execute(`alter table \`${table}\` convert to character set ${charset} collate ${collate}`);
1004
+ converted.push({
1005
+ table,
1006
+ from: current,
1007
+ to: collate
1008
+ });
913
1009
  } catch (err) {
914
1010
  const message = err instanceof Error ? err.message : String(err);
915
1011
  console.warn(`[nestjs-durable-store-mikro-orm] could not align collation for \`${table}\` to ${collate} (left as-is): ${message}`);
916
1012
  }
917
1013
  }
1014
+ return converted;
918
1015
  }
919
1016
  __name(alignDurableCollation, "alignDurableCollation");
920
1017
 
@@ -924,10 +1021,12 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
924
1021
  __name(this, "MikroOrmStateStore");
925
1022
  }
926
1023
  orm;
1024
+ opts;
927
1025
  scopeNamespace;
928
- constructor(orm, opts) {
1026
+ constructor(orm, opts = {}) {
929
1027
  this.orm = orm;
930
- this.scopeNamespace = opts?.scope?.namespace;
1028
+ this.opts = opts;
1029
+ this.scopeNamespace = opts.scope?.namespace;
931
1030
  }
932
1031
  /**
933
1032
  * Derive a store confined to a single tenant `scope.namespace`, sharing this store's ORM (no new
@@ -937,6 +1036,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
937
1036
  */
938
1037
  withScope(scope) {
939
1038
  return new _MikroOrmStateStore(this.orm, {
1039
+ ...this.opts,
940
1040
  scope
941
1041
  });
942
1042
  }
@@ -954,7 +1054,9 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
954
1054
  return em;
955
1055
  }
956
1056
  async ensureSchema() {
957
- await ensureMikroOrmDurableSchema(this.orm);
1057
+ await ensureMikroOrmDurableSchema(this.orm, {
1058
+ logger: this.opts.schemaLogger
1059
+ });
958
1060
  }
959
1061
  async tryAdmitSingleton(runId, tag, workflow, limit, retryWakeAt) {
960
1062
  return this.fork().transactional(async (em) => {
@@ -980,6 +1082,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
980
1082
  }, {
981
1083
  status: "suspended",
982
1084
  wakeAt: new Date(patch.wakeAt),
1085
+ tags: patch.tags,
983
1086
  updatedAt: patch.updatedAt
984
1087
  });
985
1088
  return false;
@@ -1199,7 +1302,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1199
1302
  });
1200
1303
  return rows.map(fromRunEntity);
1201
1304
  }
1202
- async listOrphanedRuns(nowMs, limit, namespace) {
1305
+ async listOrphanedRuns(nowMs, limit, namespace, ownedBy) {
1203
1306
  if (limit <= 0) return [];
1204
1307
  const em = this.fork();
1205
1308
  const rows = await em.find(WorkflowRunEntity, {
@@ -1217,7 +1320,16 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1217
1320
  lockedUntil: {
1218
1321
  $lte: new Date(nowMs)
1219
1322
  }
1220
- }
1323
+ },
1324
+ // A live lease left by a previous process with the caller's own instance id.
1325
+ ...ownedBy ? [
1326
+ {
1327
+ lockedBy: ownedBy.owner,
1328
+ lockedUntil: {
1329
+ $lt: new Date(ownedBy.lockedBefore)
1330
+ }
1331
+ }
1332
+ ] : []
1221
1333
  ],
1222
1334
  ...namespace !== void 0 ? {
1223
1335
  namespace
@@ -1286,10 +1398,13 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1286
1398
  });
1287
1399
  return affected === 1;
1288
1400
  }
1289
- async releaseRunLock(runId) {
1401
+ async releaseRunLock(runId, owner) {
1290
1402
  const em = this.fork();
1291
- await em.nativeUpdate(WorkflowRunEntity, {
1403
+ await em.nativeUpdate(WorkflowRunEntity, owner === void 0 ? {
1292
1404
  id: runId
1405
+ } : {
1406
+ id: runId,
1407
+ lockedBy: owner
1293
1408
  }, {
1294
1409
  lockedBy: null,
1295
1410
  lockedUntil: null
@@ -1621,7 +1736,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1621
1736
  * single statement. */
1622
1737
  async updateSchedule(id, patch, expectedNextFireAt) {
1623
1738
  const data = {};
1624
- if (patch.namespace !== void 0) data.namespace = patch.namespace;
1739
+ if (patch.namespace !== void 0) data.namespace = patch.namespace || "default";
1625
1740
  if (patch.workflow !== void 0) data.workflow = patch.workflow;
1626
1741
  if (patch.paused !== void 0) data.paused = patch.paused;
1627
1742
  if ("nextFireAt" in patch) data.nextFireAt = patch.nextFireAt ?? null;
@@ -2007,7 +2122,7 @@ function runUpdateData(patch) {
2007
2122
  if ("tags" in patch) data.tags = patch.tags ?? null;
2008
2123
  if ("searchAttributes" in patch) data.searchAttributes = patch.searchAttributes ?? null;
2009
2124
  if ("priority" in patch) data.priority = patch.priority ?? null;
2010
- if ("namespace" in patch) data.namespace = patch.namespace ?? "default";
2125
+ if ("namespace" in patch) data.namespace = patch.namespace || "default";
2011
2126
  if ("origin" in patch) data.origin = patch.origin ?? null;
2012
2127
  if (patch.createdAt != null) data.createdAt = patch.createdAt;
2013
2128
  if (patch.updatedAt != null) data.updatedAt = patch.updatedAt;
@@ -2031,7 +2146,7 @@ function toRunEntity(run) {
2031
2146
  e.tags = run.tags ?? null;
2032
2147
  e.searchAttributes = run.searchAttributes ?? null;
2033
2148
  e.priority = run.priority ?? null;
2034
- e.namespace = run.namespace ?? "default";
2149
+ e.namespace = run.namespace || "default";
2035
2150
  e.origin = run.origin ?? null;
2036
2151
  e.createdAt = run.createdAt;
2037
2152
  e.updatedAt = run.updatedAt;
@@ -2174,6 +2289,7 @@ __name(fromScheduleEntity, "fromScheduleEntity");
2174
2289
  WorkflowRunRepository,
2175
2290
  durableEntities,
2176
2291
  durableManagedTables,
2177
- ensureMikroOrmDurableSchema
2292
+ ensureMikroOrmDurableSchema,
2293
+ mikroOrmSchemaRepairWarning
2178
2294
  });
2179
2295
  //# sourceMappingURL=index.cjs.map