@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.cjs +130 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -47
- package/dist/index.d.ts +83 -47
- package/dist/index.js +128 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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.
|
|
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
|