@dudousxd/nestjs-durable-store-mikro-orm 0.32.0 → 0.33.1

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.d.cts CHANGED
@@ -145,6 +145,79 @@ declare function durableEntities(options?: {
145
145
  /** Durable entity schemas with the canonical `'snake_case'` column names. */
146
146
  declare const ENTITIES: EntitySchema<any, never, _mikro_orm_core.EntityCtor<any>>[];
147
147
 
148
+ /**
149
+ * Tables this store creates and manages at boot (autoSchema). Feed to your ORM's migration differ
150
+ * exclude/skipTables so it never tries to drop them.
151
+ *
152
+ * ```ts
153
+ * await MikroORM.init({
154
+ * // ...your entities/driver config
155
+ * schemaGenerator: { skipTables: durableManagedTables() },
156
+ * });
157
+ * ```
158
+ *
159
+ * Derived from the same {@link DURABLE_TABLE_NAMES} set `ensureMikroOrmDurableSchema` scopes its own
160
+ * heal to — one list, so a consumer's denylist can never drift from what this store actually owns.
161
+ */
162
+ declare function durableManagedTables(): string[];
163
+ /**
164
+ * Where {@link ensureMikroOrmDurableSchema} reports a schema repair it actually applied. `console`
165
+ * (the default) satisfies it, and so does NestJS's `Logger`, so an app can route the warning into
166
+ * its own log stream.
167
+ */
168
+ interface DurableSchemaLogger {
169
+ warn(message: string): void;
170
+ }
171
+ interface EnsureMikroOrmDurableSchemaOptions {
172
+ /** Sink for the schema-repair warning. Defaults to `console`. */
173
+ logger?: DurableSchemaLogger | undefined;
174
+ }
175
+ /**
176
+ * The warning logged when the boot-time heal changed an EXISTING durable schema: created a table
177
+ * that was missing next to ones that exist, added a column or index, changed a column type, or
178
+ * converted a table's collation. Never logged when the schema was already current, and never on a
179
+ * fresh install (no durable table existed, so creating them all is the expected first boot).
180
+ *
181
+ * The discriminator matters more than the wording: a warning on every boot would be filtered as
182
+ * noise, and a silent repair hides a migration-managed schema that has drifted from the library.
183
+ */
184
+ declare function mikroOrmSchemaRepairWarning(repairs: readonly string[]): string;
185
+ /**
186
+ * Idempotently create/extend ONLY the durable tables (additive — never drops). Safe to run on every
187
+ * boot (auto-schema) and from a MikroORM migration when you disable auto-schema:
188
+ *
189
+ * ```ts
190
+ * export class AddDurableTables extends Migration {
191
+ * async up() { await ensureMikroOrmDurableSchema(this.getEntityManager().getOrm()); }
192
+ * }
193
+ * ```
194
+ *
195
+ * It does NOT run a whole-ORM `schema.update()`. When the store shares the host app's ORM (the
196
+ * recommended setup — a single ORM avoids MikroORM's global-metadata clobber between instances),
197
+ * a full `schema.update()` would try to reconcile EVERY app table to the entity metadata: on a
198
+ * migration-managed schema that means recreating the migrations table and dropping app foreign keys.
199
+ * Instead we compute the safe additive diff and execute only the statements that target our own
200
+ * `durable_*` tables. `getUpdateSchemaSQL` already emits a `create table` only for a missing table
201
+ * and `alter table ... add` only for missing columns, so existing tables — and the entire rest of
202
+ * the host schema — are left untouched.
203
+ *
204
+ * This runs on every boot of every pod, and `getUpdateSchemaSQL({ safe: true })` introspects the
205
+ * WHOLE database's `information_schema` (the store shares the app ORM) before string-filtering down
206
+ * to our tables — wasted round-trips when nothing changed. So a fingerprint GATE fronts it: a marker
207
+ * table records the fingerprint of the metadata last applied; if the in-memory expected fingerprint
208
+ * matches the stored one we return after two cheap round-trips (a `CREATE TABLE IF NOT EXISTS` for
209
+ * the marker + one PK read), skipping BOTH the introspection AND the collation probes. Only a fresh
210
+ * DB (no marker), an entity change, or a {@link SCHEMA_REVISION} bump runs the full heal — under a
211
+ * best-effort advisory lock, with a re-check in case a sibling pod healed while we waited.
212
+ *
213
+ * When the heal changes an existing durable schema (a missing table, column or index, a column type,
214
+ * a MySQL collation), it logs ONE warning listing each change to `options.logger` (default
215
+ * `console`); see {@link mikroOrmSchemaRepairWarning}. A current schema and a fresh install log
216
+ * nothing. Because of the fingerprint gate, a column dropped by hand AFTER the marker was written is
217
+ * not noticed until the fingerprint changes (an upgrade) or the marker row is deleted.
218
+ */
219
+ declare function ensureMikroOrmDurableSchema(orm: MikroORM, options?: EnsureMikroOrmDurableSchemaOptions): Promise<void>;
220
+
148
221
  /** Options for scoping a `MikroOrmStateStore` to a single tenant namespace. */
149
222
  interface MikroOrmStateStoreOptions {
150
223
  /**
@@ -155,6 +228,12 @@ interface MikroOrmStateStoreOptions {
155
228
  scope?: {
156
229
  namespace?: string;
157
230
  };
231
+ /**
232
+ * Where `ensureSchema()` (the boot-time auto-schema) reports a schema repair it actually applied
233
+ * — a missing table, column or index it had to add. Defaults to `console`. NestJS's `Logger`
234
+ * satisfies it.
235
+ */
236
+ schemaLogger?: DurableSchemaLogger | undefined;
158
237
  }
159
238
  /**
160
239
  * MikroORM-backed `StateStore`. Works on any MikroORM driver — Postgres, MySQL, SQLite (tested);
@@ -166,6 +245,7 @@ interface MikroOrmStateStoreOptions {
166
245
  */
167
246
  declare class MikroOrmStateStore implements StateStore {
168
247
  private readonly orm;
248
+ private readonly opts;
169
249
  private readonly scopeNamespace;
170
250
  constructor(orm: MikroORM, opts?: MikroOrmStateStoreOptions);
171
251
  /**
@@ -193,6 +273,7 @@ declare class MikroOrmStateStore implements StateStore {
193
273
  * the in-memory store's reindex so the side-table always reflects the run's live searchAttributes. */
194
274
  private reindexAttributes;
195
275
  getRun(runId: string): Promise<WorkflowRun | null>;
276
+ getRuns(runIds: string[]): Promise<WorkflowRun[]>;
196
277
  deleteRun(runId: string): Promise<void>;
197
278
  deleteRuns(runIds: string[]): Promise<void>;
198
279
  pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
@@ -209,7 +290,7 @@ declare class MikroOrmStateStore implements StateStore {
209
290
  listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
210
291
  listDueTimers(nowMs: number, namespace?: string, limit?: number): Promise<WorkflowRun[]>;
211
292
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
212
- releaseRunLock(runId: string): Promise<void>;
293
+ releaseRunLock(runId: string, owner?: string): Promise<void>;
213
294
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
214
295
  /** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
215
296
  * Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
@@ -332,49 +413,4 @@ declare class MikroOrmStateStore implements StateStore {
332
413
  removeBufferedEvent(id: string): Promise<boolean>;
333
414
  }
334
415
 
335
- /**
336
- * Tables this store creates and manages at boot (autoSchema). Feed to your ORM's migration differ
337
- * exclude/skipTables so it never tries to drop them.
338
- *
339
- * ```ts
340
- * await MikroORM.init({
341
- * // ...your entities/driver config
342
- * schemaGenerator: { skipTables: durableManagedTables() },
343
- * });
344
- * ```
345
- *
346
- * Derived from the same {@link DURABLE_TABLE_NAMES} set `ensureMikroOrmDurableSchema` scopes its own
347
- * heal to — one list, so a consumer's denylist can never drift from what this store actually owns.
348
- */
349
- declare function durableManagedTables(): string[];
350
- /**
351
- * Idempotently create/extend ONLY the durable tables (additive — never drops). Safe to run on every
352
- * boot (auto-schema) and from a MikroORM migration when you disable auto-schema:
353
- *
354
- * ```ts
355
- * export class AddDurableTables extends Migration {
356
- * async up() { await ensureMikroOrmDurableSchema(this.getEntityManager().getOrm()); }
357
- * }
358
- * ```
359
- *
360
- * It does NOT run a whole-ORM `schema.update()`. When the store shares the host app's ORM (the
361
- * recommended setup — a single ORM avoids MikroORM's global-metadata clobber between instances),
362
- * a full `schema.update()` would try to reconcile EVERY app table to the entity metadata: on a
363
- * migration-managed schema that means recreating the migrations table and dropping app foreign keys.
364
- * Instead we compute the safe additive diff and execute only the statements that target our own
365
- * `durable_*` tables. `getUpdateSchemaSQL` already emits a `create table` only for a missing table
366
- * and `alter table ... add` only for missing columns, so existing tables — and the entire rest of
367
- * the host schema — are left untouched.
368
- *
369
- * This runs on every boot of every pod, and `getUpdateSchemaSQL({ safe: true })` introspects the
370
- * WHOLE database's `information_schema` (the store shares the app ORM) before string-filtering down
371
- * to our tables — wasted round-trips when nothing changed. So a fingerprint GATE fronts it: a marker
372
- * table records the fingerprint of the metadata last applied; if the in-memory expected fingerprint
373
- * matches the stored one we return after two cheap round-trips (a `CREATE TABLE IF NOT EXISTS` for
374
- * the marker + one PK read), skipping BOTH the introspection AND the collation probes. Only a fresh
375
- * DB (no marker), an entity change, or a {@link SCHEMA_REVISION} bump runs the full heal — under a
376
- * best-effort advisory lock, with a re-check in case a sibling pod healed while we waited.
377
- */
378
- declare function ensureMikroOrmDurableSchema(orm: MikroORM): Promise<void>;
379
-
380
- export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, ScheduleEntity, ScheduleRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
416
+ export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, type DurableSchemaLogger, ENTITIES, type EnsureMikroOrmDurableSchemaOptions, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, ScheduleEntity, ScheduleRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema, mikroOrmSchemaRepairWarning };
package/dist/index.d.ts CHANGED
@@ -145,6 +145,79 @@ declare function durableEntities(options?: {
145
145
  /** Durable entity schemas with the canonical `'snake_case'` column names. */
146
146
  declare const ENTITIES: EntitySchema<any, never, _mikro_orm_core.EntityCtor<any>>[];
147
147
 
148
+ /**
149
+ * Tables this store creates and manages at boot (autoSchema). Feed to your ORM's migration differ
150
+ * exclude/skipTables so it never tries to drop them.
151
+ *
152
+ * ```ts
153
+ * await MikroORM.init({
154
+ * // ...your entities/driver config
155
+ * schemaGenerator: { skipTables: durableManagedTables() },
156
+ * });
157
+ * ```
158
+ *
159
+ * Derived from the same {@link DURABLE_TABLE_NAMES} set `ensureMikroOrmDurableSchema` scopes its own
160
+ * heal to — one list, so a consumer's denylist can never drift from what this store actually owns.
161
+ */
162
+ declare function durableManagedTables(): string[];
163
+ /**
164
+ * Where {@link ensureMikroOrmDurableSchema} reports a schema repair it actually applied. `console`
165
+ * (the default) satisfies it, and so does NestJS's `Logger`, so an app can route the warning into
166
+ * its own log stream.
167
+ */
168
+ interface DurableSchemaLogger {
169
+ warn(message: string): void;
170
+ }
171
+ interface EnsureMikroOrmDurableSchemaOptions {
172
+ /** Sink for the schema-repair warning. Defaults to `console`. */
173
+ logger?: DurableSchemaLogger | undefined;
174
+ }
175
+ /**
176
+ * The warning logged when the boot-time heal changed an EXISTING durable schema: created a table
177
+ * that was missing next to ones that exist, added a column or index, changed a column type, or
178
+ * converted a table's collation. Never logged when the schema was already current, and never on a
179
+ * fresh install (no durable table existed, so creating them all is the expected first boot).
180
+ *
181
+ * The discriminator matters more than the wording: a warning on every boot would be filtered as
182
+ * noise, and a silent repair hides a migration-managed schema that has drifted from the library.
183
+ */
184
+ declare function mikroOrmSchemaRepairWarning(repairs: readonly string[]): string;
185
+ /**
186
+ * Idempotently create/extend ONLY the durable tables (additive — never drops). Safe to run on every
187
+ * boot (auto-schema) and from a MikroORM migration when you disable auto-schema:
188
+ *
189
+ * ```ts
190
+ * export class AddDurableTables extends Migration {
191
+ * async up() { await ensureMikroOrmDurableSchema(this.getEntityManager().getOrm()); }
192
+ * }
193
+ * ```
194
+ *
195
+ * It does NOT run a whole-ORM `schema.update()`. When the store shares the host app's ORM (the
196
+ * recommended setup — a single ORM avoids MikroORM's global-metadata clobber between instances),
197
+ * a full `schema.update()` would try to reconcile EVERY app table to the entity metadata: on a
198
+ * migration-managed schema that means recreating the migrations table and dropping app foreign keys.
199
+ * Instead we compute the safe additive diff and execute only the statements that target our own
200
+ * `durable_*` tables. `getUpdateSchemaSQL` already emits a `create table` only for a missing table
201
+ * and `alter table ... add` only for missing columns, so existing tables — and the entire rest of
202
+ * the host schema — are left untouched.
203
+ *
204
+ * This runs on every boot of every pod, and `getUpdateSchemaSQL({ safe: true })` introspects the
205
+ * WHOLE database's `information_schema` (the store shares the app ORM) before string-filtering down
206
+ * to our tables — wasted round-trips when nothing changed. So a fingerprint GATE fronts it: a marker
207
+ * table records the fingerprint of the metadata last applied; if the in-memory expected fingerprint
208
+ * matches the stored one we return after two cheap round-trips (a `CREATE TABLE IF NOT EXISTS` for
209
+ * the marker + one PK read), skipping BOTH the introspection AND the collation probes. Only a fresh
210
+ * DB (no marker), an entity change, or a {@link SCHEMA_REVISION} bump runs the full heal — under a
211
+ * best-effort advisory lock, with a re-check in case a sibling pod healed while we waited.
212
+ *
213
+ * When the heal changes an existing durable schema (a missing table, column or index, a column type,
214
+ * a MySQL collation), it logs ONE warning listing each change to `options.logger` (default
215
+ * `console`); see {@link mikroOrmSchemaRepairWarning}. A current schema and a fresh install log
216
+ * nothing. Because of the fingerprint gate, a column dropped by hand AFTER the marker was written is
217
+ * not noticed until the fingerprint changes (an upgrade) or the marker row is deleted.
218
+ */
219
+ declare function ensureMikroOrmDurableSchema(orm: MikroORM, options?: EnsureMikroOrmDurableSchemaOptions): Promise<void>;
220
+
148
221
  /** Options for scoping a `MikroOrmStateStore` to a single tenant namespace. */
149
222
  interface MikroOrmStateStoreOptions {
150
223
  /**
@@ -155,6 +228,12 @@ interface MikroOrmStateStoreOptions {
155
228
  scope?: {
156
229
  namespace?: string;
157
230
  };
231
+ /**
232
+ * Where `ensureSchema()` (the boot-time auto-schema) reports a schema repair it actually applied
233
+ * — a missing table, column or index it had to add. Defaults to `console`. NestJS's `Logger`
234
+ * satisfies it.
235
+ */
236
+ schemaLogger?: DurableSchemaLogger | undefined;
158
237
  }
159
238
  /**
160
239
  * MikroORM-backed `StateStore`. Works on any MikroORM driver — Postgres, MySQL, SQLite (tested);
@@ -166,6 +245,7 @@ interface MikroOrmStateStoreOptions {
166
245
  */
167
246
  declare class MikroOrmStateStore implements StateStore {
168
247
  private readonly orm;
248
+ private readonly opts;
169
249
  private readonly scopeNamespace;
170
250
  constructor(orm: MikroORM, opts?: MikroOrmStateStoreOptions);
171
251
  /**
@@ -193,6 +273,7 @@ declare class MikroOrmStateStore implements StateStore {
193
273
  * the in-memory store's reindex so the side-table always reflects the run's live searchAttributes. */
194
274
  private reindexAttributes;
195
275
  getRun(runId: string): Promise<WorkflowRun | null>;
276
+ getRuns(runIds: string[]): Promise<WorkflowRun[]>;
196
277
  deleteRun(runId: string): Promise<void>;
197
278
  deleteRuns(runIds: string[]): Promise<void>;
198
279
  pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
@@ -209,7 +290,7 @@ declare class MikroOrmStateStore implements StateStore {
209
290
  listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
210
291
  listDueTimers(nowMs: number, namespace?: string, limit?: number): Promise<WorkflowRun[]>;
211
292
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
212
- releaseRunLock(runId: string): Promise<void>;
293
+ releaseRunLock(runId: string, owner?: string): Promise<void>;
213
294
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
214
295
  /** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
215
296
  * Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
@@ -332,49 +413,4 @@ declare class MikroOrmStateStore implements StateStore {
332
413
  removeBufferedEvent(id: string): Promise<boolean>;
333
414
  }
334
415
 
335
- /**
336
- * Tables this store creates and manages at boot (autoSchema). Feed to your ORM's migration differ
337
- * exclude/skipTables so it never tries to drop them.
338
- *
339
- * ```ts
340
- * await MikroORM.init({
341
- * // ...your entities/driver config
342
- * schemaGenerator: { skipTables: durableManagedTables() },
343
- * });
344
- * ```
345
- *
346
- * Derived from the same {@link DURABLE_TABLE_NAMES} set `ensureMikroOrmDurableSchema` scopes its own
347
- * heal to — one list, so a consumer's denylist can never drift from what this store actually owns.
348
- */
349
- declare function durableManagedTables(): string[];
350
- /**
351
- * Idempotently create/extend ONLY the durable tables (additive — never drops). Safe to run on every
352
- * boot (auto-schema) and from a MikroORM migration when you disable auto-schema:
353
- *
354
- * ```ts
355
- * export class AddDurableTables extends Migration {
356
- * async up() { await ensureMikroOrmDurableSchema(this.getEntityManager().getOrm()); }
357
- * }
358
- * ```
359
- *
360
- * It does NOT run a whole-ORM `schema.update()`. When the store shares the host app's ORM (the
361
- * recommended setup — a single ORM avoids MikroORM's global-metadata clobber between instances),
362
- * a full `schema.update()` would try to reconcile EVERY app table to the entity metadata: on a
363
- * migration-managed schema that means recreating the migrations table and dropping app foreign keys.
364
- * Instead we compute the safe additive diff and execute only the statements that target our own
365
- * `durable_*` tables. `getUpdateSchemaSQL` already emits a `create table` only for a missing table
366
- * and `alter table ... add` only for missing columns, so existing tables — and the entire rest of
367
- * the host schema — are left untouched.
368
- *
369
- * This runs on every boot of every pod, and `getUpdateSchemaSQL({ safe: true })` introspects the
370
- * WHOLE database's `information_schema` (the store shares the app ORM) before string-filtering down
371
- * to our tables — wasted round-trips when nothing changed. So a fingerprint GATE fronts it: a marker
372
- * table records the fingerprint of the metadata last applied; if the in-memory expected fingerprint
373
- * matches the stored one we return after two cheap round-trips (a `CREATE TABLE IF NOT EXISTS` for
374
- * the marker + one PK read), skipping BOTH the introspection AND the collation probes. Only a fresh
375
- * DB (no marker), an entity change, or a {@link SCHEMA_REVISION} bump runs the full heal — under a
376
- * best-effort advisory lock, with a re-check in case a sibling pod healed while we waited.
377
- */
378
- declare function ensureMikroOrmDurableSchema(orm: MikroORM): Promise<void>;
379
-
380
- export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, ScheduleEntity, ScheduleRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
416
+ export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, type DurableSchemaLogger, ENTITIES, type EnsureMikroOrmDurableSchemaOptions, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, ScheduleEntity, ScheduleRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema, mikroOrmSchemaRepairWarning };
package/dist/index.js CHANGED
@@ -679,6 +679,78 @@ var MARKER_TABLE = "durable_schema_meta";
679
679
  var MARKER_ROW_ID = "durable";
680
680
  var SCHEMA_LOCK_NAME = "durable_schema";
681
681
  var SCHEMA_REVISION = 1;
682
+ function mikroOrmSchemaRepairWarning(repairs) {
683
+ return [
684
+ `[nestjs-durable] store-mikro-orm repaired the durable schema at boot: ${repairs.join("; ")}.`,
685
+ "The database had drifted from the schema this library version expects (it was created by an",
686
+ "older version, or a migration left these out), so they were applied at runtime and nothing",
687
+ "recorded them in your migration history. To make them permanent, add a migration that calls",
688
+ "`ensureMikroOrmDurableSchema(orm)`. This warning appears only when a repair was applied; a",
689
+ "schema that is already current logs nothing."
690
+ ].join(" ");
691
+ }
692
+ __name(mikroOrmSchemaRepairWarning, "mikroOrmSchemaRepairWarning");
693
+ function unquote(identifier) {
694
+ return identifier.replace(/^[`"']|[`"']$/g, "");
695
+ }
696
+ __name(unquote, "unquote");
697
+ function describeDurableStatement(statement) {
698
+ const table = targetTable(statement) ?? "?";
699
+ if (/^create\s+table\b/i.test(statement)) return [
700
+ `created table ${table}`
701
+ ];
702
+ const createIndex = statement.match(/^create\s+(?:unique\s+)?index\s+(?:if\s+not\s+exists\s+)?([`"']?[\w$]+[`"']?)/i);
703
+ if (createIndex?.[1]) return [
704
+ `created index ${unquote(createIndex[1])} on ${table}`
705
+ ];
706
+ if (/^alter\s+table\b/i.test(statement)) {
707
+ const out = [];
708
+ let other = false;
709
+ for (const action of splitTopLevel(statement.replace(/^alter\s+table\s+[`"']?[\w$]+[`"']?\s*/i, ""))) {
710
+ const index = action.match(/^add\s+(?:unique\s+|fulltext\s+)?(?:index|key)\s+([`"']?[\w$]+[`"']?)/i);
711
+ const constraint = action.match(/^add\s+constraint\s+([`"']?[\w$]+[`"']?)/i);
712
+ const column = action.match(/^add\s+(?:column\s+)?([`"']?[\w$]+[`"']?)/i);
713
+ if (index?.[1]) out.push(`created index ${unquote(index[1])} on ${table}`);
714
+ else if (constraint?.[1]) out.push(`added constraint ${unquote(constraint[1])} on ${table}`);
715
+ else if (column?.[1] && !/^add\s+(?:primary|unique|fulltext|foreign)\b/i.test(action)) out.push(`added column ${table}.${unquote(column[1])}`);
716
+ else other = true;
717
+ }
718
+ if (other || out.length === 0) {
719
+ const flat = statement.replace(/\s+/g, " ");
720
+ out.push(`altered ${table} (${flat.length > 160 ? `${flat.slice(0, 157)}...` : flat})`);
721
+ }
722
+ return out;
723
+ }
724
+ return [
725
+ `ran ${statement.replace(/\s+/g, " ").slice(0, 160)}`
726
+ ];
727
+ }
728
+ __name(describeDurableStatement, "describeDurableStatement");
729
+ function splitTopLevel(text) {
730
+ const parts = [];
731
+ let depth = 0;
732
+ let quote;
733
+ let current = "";
734
+ for (const ch of text) {
735
+ if (quote) {
736
+ if (ch === quote) quote = void 0;
737
+ } else if (ch === "'" || ch === '"' || ch === "`") {
738
+ quote = ch;
739
+ } else if (ch === "(") {
740
+ depth++;
741
+ } else if (ch === ")") {
742
+ depth--;
743
+ } else if (ch === "," && depth === 0) {
744
+ parts.push(current.trim());
745
+ current = "";
746
+ continue;
747
+ }
748
+ current += ch;
749
+ }
750
+ if (current.trim()) parts.push(current.trim());
751
+ return parts;
752
+ }
753
+ __name(splitTopLevel, "splitTopLevel");
682
754
  function compareStrings(a, b) {
683
755
  return a < b ? -1 : a > b ? 1 : 0;
684
756
  }
@@ -797,7 +869,8 @@ async function releaseSchemaLock(orm, dialect) {
797
869
  }
798
870
  }
799
871
  __name(releaseSchemaLock, "releaseSchemaLock");
800
- async function ensureMikroOrmDurableSchema(orm) {
872
+ async function ensureMikroOrmDurableSchema(orm, options = {}) {
873
+ const logger = options.logger ?? console;
801
874
  const dialect = detectSqlDialect(orm);
802
875
  await orm.em.getConnection().execute(createMarkerTableSql());
803
876
  const expected = computeExpectedFingerprint(orm, DURABLE_TABLE_NAMES);
@@ -810,8 +883,9 @@ async function ensureMikroOrmDurableSchema(orm) {
810
883
  if (await readStoredFingerprint(orm) === expected) {
811
884
  return;
812
885
  }
813
- await healDurableSchema(orm);
886
+ const { repairs, freshInstall } = await healDurableSchema(orm);
814
887
  await writeFingerprint(orm, dialect, expected);
888
+ if (repairs.length > 0 && !freshInstall) logger.warn(mikroOrmSchemaRepairWarning(repairs));
815
889
  } finally {
816
890
  await releaseSchemaLock(orm, dialect);
817
891
  }
@@ -826,9 +900,14 @@ async function healDurableSchema(orm) {
826
900
  return table !== void 0 && DURABLE_TABLE_NAMES.has(table);
827
901
  });
828
902
  const connection = orm.em.getConnection();
903
+ const created = new Set(ours.filter((statement) => /^create\s+table\b/i.test(statement)).map((statement) => targetTable(statement)));
904
+ const repairs = [];
829
905
  for (const statement of ours) {
830
906
  try {
831
907
  await connection.execute(statement);
908
+ const table = targetTable(statement);
909
+ const isCreateTable = /^create\s+table\b/i.test(statement);
910
+ if (isCreateTable || !created.has(table)) repairs.push(...describeDurableStatement(statement));
832
911
  } catch (err) {
833
912
  if (isRequiredStructure(statement)) {
834
913
  throw err;
@@ -837,7 +916,17 @@ async function healDurableSchema(orm) {
837
916
  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}`);
838
917
  }
839
918
  }
840
- await alignDurableCollation(orm);
919
+ const converted = await alignDurableCollation(orm);
920
+ for (const { table, from, to } of converted) {
921
+ if (!created.has(table)) repairs.push(`converted ${table} collation from ${from} to ${to}`);
922
+ }
923
+ const freshInstall = [
924
+ ...DURABLE_TABLE_NAMES
925
+ ].every((table) => created.has(table));
926
+ return {
927
+ repairs,
928
+ freshInstall
929
+ };
841
930
  }
842
931
  __name(healDurableSchema, "healDurableSchema");
843
932
  function isSafeSqlIdentifier(value) {
@@ -845,17 +934,18 @@ function isSafeSqlIdentifier(value) {
845
934
  }
846
935
  __name(isSafeSqlIdentifier, "isSafeSqlIdentifier");
847
936
  async function alignDurableCollation(orm) {
937
+ const converted = [];
848
938
  const platform = String(orm.em.getPlatform().constructor.name).toLowerCase();
849
939
  if (!platform.includes("mysql") && !platform.includes("maria")) {
850
- return;
940
+ return converted;
851
941
  }
852
942
  const collate = orm.config.get("collate");
853
943
  if (typeof collate !== "string" || !isSafeSqlIdentifier(collate)) {
854
- return;
944
+ return converted;
855
945
  }
856
946
  const charset = collate.split("_")[0];
857
947
  if (!charset || !isSafeSqlIdentifier(charset)) {
858
- return;
948
+ return converted;
859
949
  }
860
950
  const connection = orm.em.getConnection();
861
951
  for (const table of DURABLE_TABLE_NAMES) {
@@ -868,11 +958,17 @@ async function alignDurableCollation(orm) {
868
958
  continue;
869
959
  }
870
960
  await connection.execute(`alter table \`${table}\` convert to character set ${charset} collate ${collate}`);
961
+ converted.push({
962
+ table,
963
+ from: current,
964
+ to: collate
965
+ });
871
966
  } catch (err) {
872
967
  const message = err instanceof Error ? err.message : String(err);
873
968
  console.warn(`[nestjs-durable-store-mikro-orm] could not align collation for \`${table}\` to ${collate} (left as-is): ${message}`);
874
969
  }
875
970
  }
971
+ return converted;
876
972
  }
877
973
  __name(alignDurableCollation, "alignDurableCollation");
878
974
 
@@ -882,10 +978,12 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
882
978
  __name(this, "MikroOrmStateStore");
883
979
  }
884
980
  orm;
981
+ opts;
885
982
  scopeNamespace;
886
- constructor(orm, opts) {
983
+ constructor(orm, opts = {}) {
887
984
  this.orm = orm;
888
- this.scopeNamespace = opts?.scope?.namespace;
985
+ this.opts = opts;
986
+ this.scopeNamespace = opts.scope?.namespace;
889
987
  }
890
988
  /**
891
989
  * Derive a store confined to a single tenant `scope.namespace`, sharing this store's ORM (no new
@@ -895,6 +993,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
895
993
  */
896
994
  withScope(scope) {
897
995
  return new _MikroOrmStateStore(this.orm, {
996
+ ...this.opts,
898
997
  scope
899
998
  });
900
999
  }
@@ -912,7 +1011,9 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
912
1011
  return em;
913
1012
  }
914
1013
  async ensureSchema() {
915
- await ensureMikroOrmDurableSchema(this.orm);
1014
+ await ensureMikroOrmDurableSchema(this.orm, {
1015
+ logger: this.opts.schemaLogger
1016
+ });
916
1017
  }
917
1018
  async tryAdmitSingleton(runId, tag, workflow, limit, retryWakeAt) {
918
1019
  return this.fork().transactional(async (em) => {
@@ -1011,6 +1112,17 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1011
1112
  });
1012
1113
  return entity ? fromRunEntity(entity) : null;
1013
1114
  }
1115
+ async getRuns(runIds) {
1116
+ if (runIds.length === 0) return [];
1117
+ const rows = await this.fork().find(WorkflowRunEntity, {
1118
+ id: {
1119
+ $in: [
1120
+ ...new Set(runIds)
1121
+ ]
1122
+ }
1123
+ });
1124
+ return rows.map(fromRunEntity);
1125
+ }
1014
1126
  async deleteRun(runId) {
1015
1127
  const em = this.fork();
1016
1128
  await em.nativeDelete(StepCheckpointEntity, {
@@ -1254,10 +1366,13 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1254
1366
  });
1255
1367
  return affected === 1;
1256
1368
  }
1257
- async releaseRunLock(runId) {
1369
+ async releaseRunLock(runId, owner) {
1258
1370
  const em = this.fork();
1259
- await em.nativeUpdate(WorkflowRunEntity, {
1371
+ await em.nativeUpdate(WorkflowRunEntity, owner === void 0 ? {
1260
1372
  id: runId
1373
+ } : {
1374
+ id: runId,
1375
+ lockedBy: owner
1261
1376
  }, {
1262
1377
  lockedBy: null,
1263
1378
  lockedUntil: null
@@ -2141,6 +2256,7 @@ export {
2141
2256
  WorkflowRunRepository,
2142
2257
  durableEntities,
2143
2258
  durableManagedTables,
2144
- ensureMikroOrmDurableSchema
2259
+ ensureMikroOrmDurableSchema,
2260
+ mikroOrmSchemaRepairWarning
2145
2261
  };
2146
2262
  //# sourceMappingURL=index.js.map