@dudousxd/nestjs-durable-store-mikro-orm 0.26.0 → 0.27.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 +283 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +282 -51
- package/dist/index.js.map +1 -1
- package/package.json +14 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _mikro_orm_core from '@mikro-orm/core';
|
|
2
2
|
import { EntityRepository, EntityRepositoryType, EntitySchema, MikroORM } from '@mikro-orm/core';
|
|
3
|
-
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
3
|
+
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Custom repositories for the durable entities, so a host app can `@InjectRepository`/inject a
|
|
@@ -29,6 +29,8 @@ declare class BufferedSignalRepository extends EntityRepository<BufferedSignalEn
|
|
|
29
29
|
}
|
|
30
30
|
declare class BufferedEventRepository extends EntityRepository<BufferedEventEntity> {
|
|
31
31
|
}
|
|
32
|
+
declare class ScheduleRepository extends EntityRepository<ScheduleEntity> {
|
|
33
|
+
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* How property names map to physical column names. The durable tables are adapter-agnostic: a run
|
|
@@ -121,6 +123,22 @@ declare class BufferedEventEntity {
|
|
|
121
123
|
payload?: unknown;
|
|
122
124
|
publishedAt: Date;
|
|
123
125
|
}
|
|
126
|
+
/** A persisted schedule (`engine.schedules`). `spec`/`state` are engine-owned JSON documents. */
|
|
127
|
+
declare class ScheduleEntity {
|
|
128
|
+
[EntityRepositoryType]?: ScheduleRepository;
|
|
129
|
+
id: string;
|
|
130
|
+
namespace: string;
|
|
131
|
+
workflow: string;
|
|
132
|
+
paused: boolean;
|
|
133
|
+
/** Epoch ms as a `bigint` column — exact, so `updateSchedule`'s compare-and-set can't be defeated
|
|
134
|
+
* by a DATETIME's truncated milliseconds. Drivers may hand it back as a string. */
|
|
135
|
+
nextFireAt?: number | string | bigint | null;
|
|
136
|
+
tags?: string[] | null;
|
|
137
|
+
spec: Record<string, unknown>;
|
|
138
|
+
state: Record<string, unknown>;
|
|
139
|
+
createdAt: Date;
|
|
140
|
+
updatedAt: Date;
|
|
141
|
+
}
|
|
124
142
|
declare function durableEntities(options?: {
|
|
125
143
|
naming?: DurableColumnNaming;
|
|
126
144
|
}): EntitySchema[];
|
|
@@ -167,13 +185,15 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
167
185
|
*/
|
|
168
186
|
private fork;
|
|
169
187
|
ensureSchema(): Promise<void>;
|
|
188
|
+
tryAdmitSingleton(runId: string, tag: string, workflow: string, limit: number, retryWakeAt?: number): Promise<boolean>;
|
|
170
189
|
createRun(run: WorkflowRun): Promise<void>;
|
|
171
|
-
updateRun(runId: string,
|
|
190
|
+
updateRun(runId: string, inputPatch: Partial<WorkflowRun>): Promise<void>;
|
|
172
191
|
/** Rewrite a run's normalized attribute rows: delete the old set, insert the current one. Mirrors
|
|
173
192
|
* the in-memory store's reindex so the side-table always reflects the run's live searchAttributes. */
|
|
174
193
|
private reindexAttributes;
|
|
175
194
|
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
176
195
|
deleteRun(runId: string): Promise<void>;
|
|
196
|
+
deleteRuns(runIds: string[]): Promise<void>;
|
|
177
197
|
pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
|
|
178
198
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
179
199
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
@@ -199,6 +219,7 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
199
219
|
/** `tags LIKE '%"etl"%'` — the quoted token, so `etl` doesn't match `etl-foo`. See the note in
|
|
200
220
|
* {@link listRuns} for why this is raw SQL rather than a MikroORM condition. */
|
|
201
221
|
private tagLikeSql;
|
|
222
|
+
countRuns(query: Omit<RunQuery, 'limit' | 'offset'>): Promise<number>;
|
|
202
223
|
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
203
224
|
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
204
225
|
* count them in the browser. */
|
|
@@ -257,6 +278,13 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
257
278
|
private jsonAsText;
|
|
258
279
|
/** Quote an identifier per the active SQL driver (MySQL/MariaDB backtick, others double-quote). */
|
|
259
280
|
private idQuote;
|
|
281
|
+
saveSchedule(record: ScheduleRecord): Promise<void>;
|
|
282
|
+
getSchedule(id: string): Promise<ScheduleRecord | null>;
|
|
283
|
+
/** One conditional `nativeUpdate`: the compare-and-set on `next_fire_at` (an exact bigint) is a
|
|
284
|
+
* single statement. */
|
|
285
|
+
updateSchedule(id: string, patch: Partial<Omit<ScheduleRecord, 'id' | 'createdAt'>>, expectedNextFireAt?: number | null): Promise<boolean>;
|
|
286
|
+
deleteSchedule(id: string): Promise<boolean>;
|
|
287
|
+
listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
|
|
260
288
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
261
289
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
262
290
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
@@ -327,4 +355,4 @@ declare function durableManagedTables(): string[];
|
|
|
327
355
|
*/
|
|
328
356
|
declare function ensureMikroOrmDurableSchema(orm: MikroORM): Promise<void>;
|
|
329
357
|
|
|
330
|
-
export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
|
|
358
|
+
export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, ScheduleEntity, ScheduleRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _mikro_orm_core from '@mikro-orm/core';
|
|
2
2
|
import { EntityRepository, EntityRepositoryType, EntitySchema, MikroORM } from '@mikro-orm/core';
|
|
3
|
-
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
3
|
+
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Custom repositories for the durable entities, so a host app can `@InjectRepository`/inject a
|
|
@@ -29,6 +29,8 @@ declare class BufferedSignalRepository extends EntityRepository<BufferedSignalEn
|
|
|
29
29
|
}
|
|
30
30
|
declare class BufferedEventRepository extends EntityRepository<BufferedEventEntity> {
|
|
31
31
|
}
|
|
32
|
+
declare class ScheduleRepository extends EntityRepository<ScheduleEntity> {
|
|
33
|
+
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* How property names map to physical column names. The durable tables are adapter-agnostic: a run
|
|
@@ -121,6 +123,22 @@ declare class BufferedEventEntity {
|
|
|
121
123
|
payload?: unknown;
|
|
122
124
|
publishedAt: Date;
|
|
123
125
|
}
|
|
126
|
+
/** A persisted schedule (`engine.schedules`). `spec`/`state` are engine-owned JSON documents. */
|
|
127
|
+
declare class ScheduleEntity {
|
|
128
|
+
[EntityRepositoryType]?: ScheduleRepository;
|
|
129
|
+
id: string;
|
|
130
|
+
namespace: string;
|
|
131
|
+
workflow: string;
|
|
132
|
+
paused: boolean;
|
|
133
|
+
/** Epoch ms as a `bigint` column — exact, so `updateSchedule`'s compare-and-set can't be defeated
|
|
134
|
+
* by a DATETIME's truncated milliseconds. Drivers may hand it back as a string. */
|
|
135
|
+
nextFireAt?: number | string | bigint | null;
|
|
136
|
+
tags?: string[] | null;
|
|
137
|
+
spec: Record<string, unknown>;
|
|
138
|
+
state: Record<string, unknown>;
|
|
139
|
+
createdAt: Date;
|
|
140
|
+
updatedAt: Date;
|
|
141
|
+
}
|
|
124
142
|
declare function durableEntities(options?: {
|
|
125
143
|
naming?: DurableColumnNaming;
|
|
126
144
|
}): EntitySchema[];
|
|
@@ -167,13 +185,15 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
167
185
|
*/
|
|
168
186
|
private fork;
|
|
169
187
|
ensureSchema(): Promise<void>;
|
|
188
|
+
tryAdmitSingleton(runId: string, tag: string, workflow: string, limit: number, retryWakeAt?: number): Promise<boolean>;
|
|
170
189
|
createRun(run: WorkflowRun): Promise<void>;
|
|
171
|
-
updateRun(runId: string,
|
|
190
|
+
updateRun(runId: string, inputPatch: Partial<WorkflowRun>): Promise<void>;
|
|
172
191
|
/** Rewrite a run's normalized attribute rows: delete the old set, insert the current one. Mirrors
|
|
173
192
|
* the in-memory store's reindex so the side-table always reflects the run's live searchAttributes. */
|
|
174
193
|
private reindexAttributes;
|
|
175
194
|
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
176
195
|
deleteRun(runId: string): Promise<void>;
|
|
196
|
+
deleteRuns(runIds: string[]): Promise<void>;
|
|
177
197
|
pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
|
|
178
198
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
179
199
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
@@ -199,6 +219,7 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
199
219
|
/** `tags LIKE '%"etl"%'` — the quoted token, so `etl` doesn't match `etl-foo`. See the note in
|
|
200
220
|
* {@link listRuns} for why this is raw SQL rather than a MikroORM condition. */
|
|
201
221
|
private tagLikeSql;
|
|
222
|
+
countRuns(query: Omit<RunQuery, 'limit' | 'offset'>): Promise<number>;
|
|
202
223
|
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
203
224
|
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
204
225
|
* count them in the browser. */
|
|
@@ -257,6 +278,13 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
257
278
|
private jsonAsText;
|
|
258
279
|
/** Quote an identifier per the active SQL driver (MySQL/MariaDB backtick, others double-quote). */
|
|
259
280
|
private idQuote;
|
|
281
|
+
saveSchedule(record: ScheduleRecord): Promise<void>;
|
|
282
|
+
getSchedule(id: string): Promise<ScheduleRecord | null>;
|
|
283
|
+
/** One conditional `nativeUpdate`: the compare-and-set on `next_fire_at` (an exact bigint) is a
|
|
284
|
+
* single statement. */
|
|
285
|
+
updateSchedule(id: string, patch: Partial<Omit<ScheduleRecord, 'id' | 'createdAt'>>, expectedNextFireAt?: number | null): Promise<boolean>;
|
|
286
|
+
deleteSchedule(id: string): Promise<boolean>;
|
|
287
|
+
listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
|
|
260
288
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
261
289
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
262
290
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
@@ -327,4 +355,4 @@ declare function durableManagedTables(): string[];
|
|
|
327
355
|
*/
|
|
328
356
|
declare function ensureMikroOrmDurableSchema(orm: MikroORM): Promise<void>;
|
|
329
357
|
|
|
330
|
-
export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
|
|
358
|
+
export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, ScheduleEntity, ScheduleRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,11 @@ var BufferedEventRepository = class extends EntityRepository {
|
|
|
36
36
|
__name(this, "BufferedEventRepository");
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
var ScheduleRepository = class extends EntityRepository {
|
|
40
|
+
static {
|
|
41
|
+
__name(this, "ScheduleRepository");
|
|
42
|
+
}
|
|
43
|
+
};
|
|
39
44
|
|
|
40
45
|
// src/entities.ts
|
|
41
46
|
function snakeCase(property) {
|
|
@@ -135,6 +140,24 @@ var BufferedEventEntity = class {
|
|
|
135
140
|
payload;
|
|
136
141
|
publishedAt;
|
|
137
142
|
};
|
|
143
|
+
var ScheduleEntity = class {
|
|
144
|
+
static {
|
|
145
|
+
__name(this, "ScheduleEntity");
|
|
146
|
+
}
|
|
147
|
+
[EntityRepositoryType];
|
|
148
|
+
id;
|
|
149
|
+
namespace;
|
|
150
|
+
workflow;
|
|
151
|
+
paused;
|
|
152
|
+
/** Epoch ms as a `bigint` column — exact, so `updateSchedule`'s compare-and-set can't be defeated
|
|
153
|
+
* by a DATETIME's truncated milliseconds. Drivers may hand it back as a string. */
|
|
154
|
+
nextFireAt;
|
|
155
|
+
tags;
|
|
156
|
+
spec;
|
|
157
|
+
state;
|
|
158
|
+
createdAt;
|
|
159
|
+
updatedAt;
|
|
160
|
+
};
|
|
138
161
|
function namespaceFilterCond(args) {
|
|
139
162
|
if (args?.namespace === void 0) return {};
|
|
140
163
|
return {
|
|
@@ -534,20 +557,83 @@ function durableEntities(options = {}) {
|
|
|
534
557
|
}
|
|
535
558
|
}
|
|
536
559
|
});
|
|
560
|
+
const schedules = new EntitySchema({
|
|
561
|
+
class: ScheduleEntity,
|
|
562
|
+
tableName: "durable_schedules",
|
|
563
|
+
repository: /* @__PURE__ */ __name(() => ScheduleRepository, "repository"),
|
|
564
|
+
// The poll's "due by T" scan: `paused = false AND next_fire_at <= T ORDER BY next_fire_at`.
|
|
565
|
+
indexes: [
|
|
566
|
+
{
|
|
567
|
+
name: "durable_schedules_due_idx",
|
|
568
|
+
properties: [
|
|
569
|
+
"paused",
|
|
570
|
+
"nextFireAt"
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
properties: {
|
|
575
|
+
id: {
|
|
576
|
+
type: "string",
|
|
577
|
+
primary: true,
|
|
578
|
+
fieldName: col("id")
|
|
579
|
+
},
|
|
580
|
+
namespace: {
|
|
581
|
+
type: "string",
|
|
582
|
+
default: "default",
|
|
583
|
+
fieldName: col("namespace")
|
|
584
|
+
},
|
|
585
|
+
workflow: {
|
|
586
|
+
type: "string",
|
|
587
|
+
fieldName: col("workflow")
|
|
588
|
+
},
|
|
589
|
+
paused: {
|
|
590
|
+
type: "boolean",
|
|
591
|
+
default: false,
|
|
592
|
+
fieldName: col("paused")
|
|
593
|
+
},
|
|
594
|
+
nextFireAt: {
|
|
595
|
+
type: "bigint",
|
|
596
|
+
nullable: true,
|
|
597
|
+
fieldName: col("nextFireAt")
|
|
598
|
+
},
|
|
599
|
+
tags: {
|
|
600
|
+
type: "json",
|
|
601
|
+
nullable: true,
|
|
602
|
+
fieldName: col("tags")
|
|
603
|
+
},
|
|
604
|
+
spec: {
|
|
605
|
+
type: "json",
|
|
606
|
+
fieldName: col("spec")
|
|
607
|
+
},
|
|
608
|
+
state: {
|
|
609
|
+
type: "json",
|
|
610
|
+
fieldName: col("state")
|
|
611
|
+
},
|
|
612
|
+
createdAt: {
|
|
613
|
+
type: "Date",
|
|
614
|
+
fieldName: col("createdAt")
|
|
615
|
+
},
|
|
616
|
+
updatedAt: {
|
|
617
|
+
type: "Date",
|
|
618
|
+
fieldName: col("updatedAt")
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
537
622
|
return [
|
|
538
623
|
workflowRuns,
|
|
539
624
|
stepCheckpoints,
|
|
540
625
|
runAttributes,
|
|
541
626
|
signalWaiters,
|
|
542
627
|
bufferedSignals,
|
|
543
|
-
bufferedEvents
|
|
628
|
+
bufferedEvents,
|
|
629
|
+
schedules
|
|
544
630
|
];
|
|
545
631
|
}
|
|
546
632
|
__name(durableEntities, "durableEntities");
|
|
547
633
|
var ENTITIES = durableEntities();
|
|
548
634
|
|
|
549
635
|
// src/mikro-orm-state-store.ts
|
|
550
|
-
import { ENGINE_MINTED_TAG_PREFIXES, RUN_VALUE_FACET_LIMIT, attributePredicateOperands, axisIsRunColumn, facetOrigin, mergeRunFacetRows, normalizeAttributeRows, parseDuration } from "@dudousxd/nestjs-durable-core";
|
|
636
|
+
import { ENGINE_MINTED_TAG_PREFIXES, RUN_VALUE_FACET_LIMIT, SINGLETON_ACTIVE_STATUSES, TERMINAL_RUN_STATUSES, attributePredicateOperands, axisIsRunColumn, clearTerminalSingletonAdmission, facetOrigin, mergeRunFacetRows, normalizeAttributeRows, parseDuration, singletonAdmissionDenialPatch, singletonAdmissionTags } from "@dudousxd/nestjs-durable-core";
|
|
551
637
|
import { raw } from "@mikro-orm/core";
|
|
552
638
|
|
|
553
639
|
// src/schema.ts
|
|
@@ -558,7 +644,8 @@ var DURABLE_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
|
558
644
|
"durable_run_attributes",
|
|
559
645
|
"durable_signal_waiters",
|
|
560
646
|
"durable_buffered_signals",
|
|
561
|
-
"durable_buffered_events"
|
|
647
|
+
"durable_buffered_events",
|
|
648
|
+
"durable_schedules"
|
|
562
649
|
]);
|
|
563
650
|
function durableManagedTables() {
|
|
564
651
|
return [
|
|
@@ -805,13 +892,50 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
805
892
|
async ensureSchema() {
|
|
806
893
|
await ensureMikroOrmDurableSchema(this.orm);
|
|
807
894
|
}
|
|
895
|
+
async tryAdmitSingleton(runId, tag, workflow, limit, retryWakeAt) {
|
|
896
|
+
return this.fork().transactional(async (em) => {
|
|
897
|
+
const where = {
|
|
898
|
+
workflow,
|
|
899
|
+
status: {
|
|
900
|
+
$in: SINGLETON_ACTIVE_STATUSES
|
|
901
|
+
},
|
|
902
|
+
...this.scopeNamespace !== void 0 ? {
|
|
903
|
+
namespace: this.scopeNamespace
|
|
904
|
+
} : {}
|
|
905
|
+
};
|
|
906
|
+
await em.nativeUpdate(WorkflowRunEntity, where, {
|
|
907
|
+
workflow
|
|
908
|
+
});
|
|
909
|
+
const rows = await em.find(WorkflowRunEntity, where);
|
|
910
|
+
const runs = rows.map(fromRunEntity);
|
|
911
|
+
const tags = singletonAdmissionTags(runs, runId, tag, workflow, limit);
|
|
912
|
+
if (!tags) {
|
|
913
|
+
const patch = singletonAdmissionDenialPatch(runs, runId, tag, workflow, retryWakeAt);
|
|
914
|
+
if (patch) await em.nativeUpdate(WorkflowRunEntity, {
|
|
915
|
+
id: runId
|
|
916
|
+
}, {
|
|
917
|
+
status: "suspended",
|
|
918
|
+
wakeAt: new Date(patch.wakeAt),
|
|
919
|
+
updatedAt: patch.updatedAt
|
|
920
|
+
});
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
await em.nativeUpdate(WorkflowRunEntity, {
|
|
924
|
+
id: runId
|
|
925
|
+
}, {
|
|
926
|
+
tags
|
|
927
|
+
});
|
|
928
|
+
return true;
|
|
929
|
+
});
|
|
930
|
+
}
|
|
808
931
|
async createRun(run) {
|
|
809
932
|
const em = this.fork();
|
|
810
933
|
em.create(WorkflowRunEntity, toRunEntity(run));
|
|
811
934
|
await em.flush();
|
|
812
935
|
await this.reindexAttributes(run.id, run.searchAttributes);
|
|
813
936
|
}
|
|
814
|
-
async updateRun(runId,
|
|
937
|
+
async updateRun(runId, inputPatch) {
|
|
938
|
+
const patch = inputPatch.status && TERMINAL_RUN_STATUSES.includes(inputPatch.status) ? await clearTerminalSingletonAdmission(runId, inputPatch, (id) => this.withScope({}).getRun(id)) : inputPatch;
|
|
815
939
|
const em = this.fork();
|
|
816
940
|
const entity = await em.findOneOrFail(WorkflowRunEntity, {
|
|
817
941
|
id: runId
|
|
@@ -855,71 +979,69 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
855
979
|
id: runId
|
|
856
980
|
});
|
|
857
981
|
}
|
|
982
|
+
async deleteRuns(runIds) {
|
|
983
|
+
if (runIds.length === 0) return;
|
|
984
|
+
const runId = {
|
|
985
|
+
$in: runIds
|
|
986
|
+
};
|
|
987
|
+
await this.fork().transactional(async (tem) => {
|
|
988
|
+
await tem.nativeDelete(StepCheckpointEntity, {
|
|
989
|
+
runId
|
|
990
|
+
});
|
|
991
|
+
await tem.nativeDelete(SignalWaiterEntity, {
|
|
992
|
+
runId
|
|
993
|
+
});
|
|
994
|
+
await tem.nativeDelete(RunAttributeEntity, {
|
|
995
|
+
runId
|
|
996
|
+
});
|
|
997
|
+
await tem.nativeDelete(WorkflowRunEntity, {
|
|
998
|
+
id: runId
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
858
1002
|
async pruneTerminalRuns(policy, nowMs, limit) {
|
|
859
|
-
|
|
1003
|
+
const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
|
|
1004
|
+
if (statuses.length === 0 || limit <= 0) return 0;
|
|
1005
|
+
if (policy.maxAge == null && policy.maxCount == null) return 0;
|
|
860
1006
|
const em = this.fork();
|
|
861
|
-
const
|
|
862
|
-
|
|
1007
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1008
|
+
const idCol = meta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1009
|
+
const query = {
|
|
1010
|
+
...policy.scope ?? {},
|
|
1011
|
+
statuses
|
|
863
1012
|
};
|
|
1013
|
+
const scoped = /* @__PURE__ */ __name((extra) => this.runQueryBuilder(em, query, {
|
|
1014
|
+
...this.runWhere(query),
|
|
1015
|
+
...extra ?? {}
|
|
1016
|
+
}).select([
|
|
1017
|
+
`r.${idCol}`
|
|
1018
|
+
]), "scoped");
|
|
864
1019
|
const ids = /* @__PURE__ */ new Set();
|
|
865
1020
|
if (policy.maxAge != null) {
|
|
866
1021
|
const cutoff = new Date(nowMs - parseDuration(policy.maxAge));
|
|
867
|
-
const rows = await
|
|
868
|
-
status,
|
|
1022
|
+
const rows = await scoped({
|
|
869
1023
|
updatedAt: {
|
|
870
1024
|
$lt: cutoff
|
|
871
1025
|
}
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
orderBy: {
|
|
877
|
-
updatedAt: "asc"
|
|
878
|
-
},
|
|
879
|
-
limit
|
|
880
|
-
});
|
|
881
|
-
for (const r of rows) ids.add(r.id);
|
|
1026
|
+
}).orderBy({
|
|
1027
|
+
"r.updatedAt": "asc"
|
|
1028
|
+
}).limit(limit).execute();
|
|
1029
|
+
for (const r of rows) ids.add(String(r[idCol] ?? r.id));
|
|
882
1030
|
}
|
|
883
1031
|
if (policy.maxCount != null && ids.size < limit) {
|
|
884
|
-
const rows = await
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
"id"
|
|
889
|
-
],
|
|
890
|
-
orderBy: {
|
|
891
|
-
updatedAt: "desc",
|
|
892
|
-
id: "desc"
|
|
893
|
-
},
|
|
894
|
-
limit,
|
|
895
|
-
offset: policy.maxCount
|
|
896
|
-
});
|
|
1032
|
+
const rows = await scoped().orderBy({
|
|
1033
|
+
"r.updatedAt": "desc",
|
|
1034
|
+
"r.id": "desc"
|
|
1035
|
+
}).limit(limit).offset(policy.maxCount).execute();
|
|
897
1036
|
for (const r of rows) {
|
|
898
|
-
ids.add(r.id);
|
|
899
1037
|
if (ids.size >= limit) break;
|
|
1038
|
+
ids.add(String(r[idCol] ?? r.id));
|
|
900
1039
|
}
|
|
901
1040
|
}
|
|
902
|
-
if (ids.size === 0) return 0;
|
|
903
1041
|
const idList = [
|
|
904
1042
|
...ids
|
|
905
1043
|
].slice(0, limit);
|
|
906
|
-
|
|
907
|
-
$in: idList
|
|
908
|
-
};
|
|
909
|
-
await em.transactional(async (tem) => {
|
|
910
|
-
await tem.nativeDelete(StepCheckpointEntity, {
|
|
911
|
-
runId
|
|
912
|
-
});
|
|
913
|
-
await tem.nativeDelete(SignalWaiterEntity, {
|
|
914
|
-
runId
|
|
915
|
-
});
|
|
916
|
-
await tem.nativeDelete(RunAttributeEntity, {
|
|
917
|
-
runId
|
|
918
|
-
});
|
|
919
|
-
await tem.nativeDelete(WorkflowRunEntity, {
|
|
920
|
-
id: runId
|
|
921
|
-
});
|
|
922
|
-
});
|
|
1044
|
+
await this.deleteRuns(idList);
|
|
923
1045
|
return idList.length;
|
|
924
1046
|
}
|
|
925
1047
|
async getCheckpoint(runId, seq) {
|
|
@@ -1131,6 +1253,11 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1131
1253
|
const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(this.tagsColumn(em))}`);
|
|
1132
1254
|
return `${colExpr} LIKE '${pattern}'`;
|
|
1133
1255
|
}
|
|
1256
|
+
async countRuns(query) {
|
|
1257
|
+
const em = this.fork();
|
|
1258
|
+
const rows = await this.runQueryBuilder(em, query, this.runWhere(query)).select([]).addSelect(raw("count(*) as count")).execute();
|
|
1259
|
+
return Number(rows[0]?.count ?? 0);
|
|
1260
|
+
}
|
|
1134
1261
|
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
1135
1262
|
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
1136
1263
|
* count them in the browser. */
|
|
@@ -1322,6 +1449,76 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1322
1449
|
const ch = isMysql ? "`" : '"';
|
|
1323
1450
|
return (id) => `${ch}${id}${ch}`;
|
|
1324
1451
|
}
|
|
1452
|
+
// ---- persisted schedules ----------------------------------------------------------------------
|
|
1453
|
+
async saveSchedule(record) {
|
|
1454
|
+
const em = this.fork();
|
|
1455
|
+
await em.upsert(ScheduleEntity, toScheduleEntity(record));
|
|
1456
|
+
}
|
|
1457
|
+
async getSchedule(id) {
|
|
1458
|
+
const entity = await this.fork().findOne(ScheduleEntity, {
|
|
1459
|
+
id
|
|
1460
|
+
});
|
|
1461
|
+
return entity ? fromScheduleEntity(entity) : null;
|
|
1462
|
+
}
|
|
1463
|
+
/** One conditional `nativeUpdate`: the compare-and-set on `next_fire_at` (an exact bigint) is a
|
|
1464
|
+
* single statement. */
|
|
1465
|
+
async updateSchedule(id, patch, expectedNextFireAt) {
|
|
1466
|
+
const data = {};
|
|
1467
|
+
if (patch.namespace !== void 0) data.namespace = patch.namespace;
|
|
1468
|
+
if (patch.workflow !== void 0) data.workflow = patch.workflow;
|
|
1469
|
+
if (patch.paused !== void 0) data.paused = patch.paused;
|
|
1470
|
+
if ("nextFireAt" in patch) data.nextFireAt = patch.nextFireAt ?? null;
|
|
1471
|
+
if ("tags" in patch) data.tags = patch.tags ?? null;
|
|
1472
|
+
if (patch.spec !== void 0) data.spec = patch.spec;
|
|
1473
|
+
if (patch.state !== void 0) data.state = patch.state;
|
|
1474
|
+
if (patch.updatedAt !== void 0) data.updatedAt = patch.updatedAt;
|
|
1475
|
+
if (!Object.keys(data).length) return await this.getSchedule(id) !== null;
|
|
1476
|
+
const where = {
|
|
1477
|
+
id
|
|
1478
|
+
};
|
|
1479
|
+
if (expectedNextFireAt !== void 0) where.nextFireAt = expectedNextFireAt;
|
|
1480
|
+
const affected = await this.fork().nativeUpdate(ScheduleEntity, where, data);
|
|
1481
|
+
return affected > 0;
|
|
1482
|
+
}
|
|
1483
|
+
async deleteSchedule(id) {
|
|
1484
|
+
return await this.fork().nativeDelete(ScheduleEntity, {
|
|
1485
|
+
id
|
|
1486
|
+
}) > 0;
|
|
1487
|
+
}
|
|
1488
|
+
async listSchedules(query) {
|
|
1489
|
+
const em = this.fork();
|
|
1490
|
+
const where = {};
|
|
1491
|
+
if (query.namespace !== void 0) where.namespace = query.namespace;
|
|
1492
|
+
if (query.workflow !== void 0) where.workflow = query.workflow;
|
|
1493
|
+
if (query.paused !== void 0) where.paused = query.paused;
|
|
1494
|
+
if (query.dueBy !== void 0) {
|
|
1495
|
+
where.$and = [
|
|
1496
|
+
{
|
|
1497
|
+
paused: false
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
nextFireAt: {
|
|
1501
|
+
$lte: query.dueBy
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
];
|
|
1505
|
+
}
|
|
1506
|
+
const quote = this.idQuote(em);
|
|
1507
|
+
const meta = em.getMetadata().get(ScheduleEntity);
|
|
1508
|
+
const tagsCol = meta.properties.tags?.fieldNames?.[0] ?? "tags";
|
|
1509
|
+
const qb = em.createQueryBuilder(ScheduleEntity, "r").where(where);
|
|
1510
|
+
if (query.tag !== void 0) {
|
|
1511
|
+
const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
|
|
1512
|
+
qb.andWhere(`${this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`)} LIKE '${pattern}'`);
|
|
1513
|
+
}
|
|
1514
|
+
qb.orderBy({
|
|
1515
|
+
"r.nextFireAt": "asc nulls last",
|
|
1516
|
+
"r.id": "asc"
|
|
1517
|
+
});
|
|
1518
|
+
if (query.limit != null) qb.limit(query.limit);
|
|
1519
|
+
if (query.offset != null) qb.offset(query.offset);
|
|
1520
|
+
return (await qb.getResultList()).map(fromScheduleEntity);
|
|
1521
|
+
}
|
|
1325
1522
|
async listCheckpoints(runId) {
|
|
1326
1523
|
const em = this.fork();
|
|
1327
1524
|
const rows = await em.find(StepCheckpointEntity, {
|
|
@@ -1560,6 +1757,38 @@ function fromCheckpointEntity(e) {
|
|
|
1560
1757
|
};
|
|
1561
1758
|
}
|
|
1562
1759
|
__name(fromCheckpointEntity, "fromCheckpointEntity");
|
|
1760
|
+
function toScheduleEntity(r) {
|
|
1761
|
+
const e = new ScheduleEntity();
|
|
1762
|
+
e.id = r.id;
|
|
1763
|
+
e.namespace = r.namespace;
|
|
1764
|
+
e.workflow = r.workflow;
|
|
1765
|
+
e.paused = r.paused;
|
|
1766
|
+
e.nextFireAt = r.nextFireAt;
|
|
1767
|
+
e.tags = r.tags ?? null;
|
|
1768
|
+
e.spec = r.spec;
|
|
1769
|
+
e.state = r.state;
|
|
1770
|
+
e.createdAt = r.createdAt;
|
|
1771
|
+
e.updatedAt = r.updatedAt;
|
|
1772
|
+
return e;
|
|
1773
|
+
}
|
|
1774
|
+
__name(toScheduleEntity, "toScheduleEntity");
|
|
1775
|
+
function fromScheduleEntity(e) {
|
|
1776
|
+
return {
|
|
1777
|
+
id: e.id,
|
|
1778
|
+
namespace: e.namespace,
|
|
1779
|
+
workflow: e.workflow,
|
|
1780
|
+
paused: Boolean(e.paused),
|
|
1781
|
+
nextFireAt: e.nextFireAt == null ? null : Number(e.nextFireAt),
|
|
1782
|
+
...e.tags?.length ? {
|
|
1783
|
+
tags: e.tags
|
|
1784
|
+
} : {},
|
|
1785
|
+
spec: e.spec ?? {},
|
|
1786
|
+
state: e.state ?? {},
|
|
1787
|
+
createdAt: e.createdAt,
|
|
1788
|
+
updatedAt: e.updatedAt
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
__name(fromScheduleEntity, "fromScheduleEntity");
|
|
1563
1792
|
export {
|
|
1564
1793
|
BufferedEventEntity,
|
|
1565
1794
|
BufferedEventRepository,
|
|
@@ -1569,6 +1798,8 @@ export {
|
|
|
1569
1798
|
MikroOrmStateStore,
|
|
1570
1799
|
RunAttributeEntity,
|
|
1571
1800
|
RunAttributeRepository,
|
|
1801
|
+
ScheduleEntity,
|
|
1802
|
+
ScheduleRepository,
|
|
1572
1803
|
SignalWaiterEntity,
|
|
1573
1804
|
SignalWaiterRepository,
|
|
1574
1805
|
StepCheckpointEntity,
|