@dudousxd/nestjs-durable-store-mikro-orm 0.28.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +166 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +166 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -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, ScheduleRecord, ScheduleQuery, SignalWaiter, ChildWait } from '@dudousxd/nestjs-durable-core';
|
|
3
|
+
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, CheckpointStat, CheckpointQuery, SignalWaiter, ChildWait } 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
|
|
@@ -198,13 +198,14 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
198
198
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
199
199
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
200
200
|
saveCheckpointIfPending(checkpoint: StepCheckpoint): Promise<boolean>;
|
|
201
|
+
saveCheckpointsIfPending(checkpoints: StepCheckpoint[]): Promise<boolean[]>;
|
|
201
202
|
transaction<T>(work: (tx: {
|
|
202
203
|
raw: unknown;
|
|
203
204
|
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
204
205
|
}) => Promise<T>): Promise<T>;
|
|
205
206
|
listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
|
|
206
207
|
listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
|
|
207
|
-
listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
|
|
208
|
+
listDueTimers(nowMs: number, namespace?: string, limit?: number): Promise<WorkflowRun[]>;
|
|
208
209
|
tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
|
|
209
210
|
releaseRunLock(runId: string): Promise<void>;
|
|
210
211
|
renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
|
|
@@ -288,7 +289,21 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
288
289
|
listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
|
|
289
290
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
290
291
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
292
|
+
countCheckpoints(runId: string, fromSeq: number, toSeq: number, statuses: StepCheckpoint['status'][]): Promise<number>;
|
|
291
293
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
294
|
+
/** One `GROUP BY parallel_group, status, <name kind>` over the run's checkpoints — the buckets a
|
|
295
|
+
* run detail summarizes a fan from, without loading its rows. The name-kind `CASE` is the SQL of
|
|
296
|
+
* `checkpointNameKind`, and `min(coalesce(enqueued_at, started_at))` reads `enqueuedAt` the way
|
|
297
|
+
* {@link listCheckpoints} does. Raw SQL on metadata-resolved names, so a custom column naming
|
|
298
|
+
* strategy is honoured. */
|
|
299
|
+
checkpointStats(runId: string, opts?: {
|
|
300
|
+
group?: string | undefined;
|
|
301
|
+
}): Promise<CheckpointStat[]>;
|
|
302
|
+
/** The run's checkpoints matching `query`, in `seq` order and paged in the database — a fan's
|
|
303
|
+
* failures or one page of its members without the rest of the run. An excluded group keeps the
|
|
304
|
+
* ungrouped rows (`parallel_group is null or not in (...)`); an empty `statuses`/`namePrefixes`
|
|
305
|
+
* matches nothing, so it returns before querying. */
|
|
306
|
+
queryCheckpoints(runId: string, query: CheckpointQuery): Promise<StepCheckpoint[]>;
|
|
292
307
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
293
308
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
294
309
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
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, ScheduleRecord, ScheduleQuery, SignalWaiter, ChildWait } from '@dudousxd/nestjs-durable-core';
|
|
3
|
+
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, CheckpointStat, CheckpointQuery, SignalWaiter, ChildWait } 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
|
|
@@ -198,13 +198,14 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
198
198
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
199
199
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
200
200
|
saveCheckpointIfPending(checkpoint: StepCheckpoint): Promise<boolean>;
|
|
201
|
+
saveCheckpointsIfPending(checkpoints: StepCheckpoint[]): Promise<boolean[]>;
|
|
201
202
|
transaction<T>(work: (tx: {
|
|
202
203
|
raw: unknown;
|
|
203
204
|
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
204
205
|
}) => Promise<T>): Promise<T>;
|
|
205
206
|
listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
|
|
206
207
|
listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
|
|
207
|
-
listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
|
|
208
|
+
listDueTimers(nowMs: number, namespace?: string, limit?: number): Promise<WorkflowRun[]>;
|
|
208
209
|
tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
|
|
209
210
|
releaseRunLock(runId: string): Promise<void>;
|
|
210
211
|
renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
|
|
@@ -288,7 +289,21 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
288
289
|
listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
|
|
289
290
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
290
291
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
292
|
+
countCheckpoints(runId: string, fromSeq: number, toSeq: number, statuses: StepCheckpoint['status'][]): Promise<number>;
|
|
291
293
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
294
|
+
/** One `GROUP BY parallel_group, status, <name kind>` over the run's checkpoints — the buckets a
|
|
295
|
+
* run detail summarizes a fan from, without loading its rows. The name-kind `CASE` is the SQL of
|
|
296
|
+
* `checkpointNameKind`, and `min(coalesce(enqueued_at, started_at))` reads `enqueuedAt` the way
|
|
297
|
+
* {@link listCheckpoints} does. Raw SQL on metadata-resolved names, so a custom column naming
|
|
298
|
+
* strategy is honoured. */
|
|
299
|
+
checkpointStats(runId: string, opts?: {
|
|
300
|
+
group?: string | undefined;
|
|
301
|
+
}): Promise<CheckpointStat[]>;
|
|
302
|
+
/** The run's checkpoints matching `query`, in `seq` order and paged in the database — a fan's
|
|
303
|
+
* failures or one page of its members without the rest of the run. An excluded group keeps the
|
|
304
|
+
* ungrouped rows (`parallel_group is null or not in (...)`); an empty `statuses`/`namePrefixes`
|
|
305
|
+
* matches nothing, so it returns before querying. */
|
|
306
|
+
queryCheckpoints(runId: string, query: CheckpointQuery): Promise<StepCheckpoint[]>;
|
|
292
307
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
293
308
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
294
309
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
package/dist/index.js
CHANGED
|
@@ -337,6 +337,19 @@ function durableEntities(options = {}) {
|
|
|
337
337
|
class: StepCheckpointEntity,
|
|
338
338
|
tableName: "durable_step_checkpoints",
|
|
339
339
|
repository: /* @__PURE__ */ __name(() => StepCheckpointRepository, "repository"),
|
|
340
|
+
// `(runId, parallelGroup, seq)` serves the run detail's per-group reads — `checkpointStats` with a
|
|
341
|
+
// group and `queryCheckpoints` paging one fan in `seq` order — without scanning the whole run.
|
|
342
|
+
// Mirrors the Prisma adapter's index of the same name.
|
|
343
|
+
indexes: [
|
|
344
|
+
{
|
|
345
|
+
name: "durable_step_checkpoints_group_idx",
|
|
346
|
+
properties: [
|
|
347
|
+
"runId",
|
|
348
|
+
"parallelGroup",
|
|
349
|
+
"seq"
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
],
|
|
340
353
|
properties: {
|
|
341
354
|
runId: {
|
|
342
355
|
type: "string",
|
|
@@ -1078,6 +1091,25 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1078
1091
|
});
|
|
1079
1092
|
return affected > 0;
|
|
1080
1093
|
}
|
|
1094
|
+
async saveCheckpointsIfPending(checkpoints) {
|
|
1095
|
+
if (checkpoints.length === 0) return [];
|
|
1096
|
+
return this.fork().transactional(async (em) => {
|
|
1097
|
+
const wrote = [];
|
|
1098
|
+
for (const checkpoint of checkpoints) {
|
|
1099
|
+
const { runId: _runId, seq: _seq, ...data } = toCheckpointEntity(checkpoint);
|
|
1100
|
+
const affected = await em.nativeUpdate(StepCheckpointEntity, {
|
|
1101
|
+
runId: checkpoint.runId,
|
|
1102
|
+
seq: checkpoint.seq,
|
|
1103
|
+
status: "pending"
|
|
1104
|
+
}, {
|
|
1105
|
+
...data,
|
|
1106
|
+
wakeAt: checkpoint.wakeAt != null ? new Date(checkpoint.wakeAt) : null
|
|
1107
|
+
});
|
|
1108
|
+
wrote.push(affected > 0);
|
|
1109
|
+
}
|
|
1110
|
+
return wrote;
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1081
1113
|
async transaction(work) {
|
|
1082
1114
|
return this.fork().transactional(async (em) => work({
|
|
1083
1115
|
raw: em,
|
|
@@ -1116,7 +1148,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1116
1148
|
});
|
|
1117
1149
|
return rows.map(fromRunEntity);
|
|
1118
1150
|
}
|
|
1119
|
-
async listDueTimers(nowMs, namespace) {
|
|
1151
|
+
async listDueTimers(nowMs, namespace, limit) {
|
|
1120
1152
|
const em = this.fork();
|
|
1121
1153
|
const rows = await em.find(WorkflowRunEntity, {
|
|
1122
1154
|
status: "suspended",
|
|
@@ -1127,6 +1159,13 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1127
1159
|
...namespace !== void 0 ? {
|
|
1128
1160
|
namespace
|
|
1129
1161
|
} : {}
|
|
1162
|
+
}, {
|
|
1163
|
+
orderBy: {
|
|
1164
|
+
wakeAt: "asc"
|
|
1165
|
+
},
|
|
1166
|
+
...limit !== void 0 ? {
|
|
1167
|
+
limit
|
|
1168
|
+
} : {}
|
|
1130
1169
|
});
|
|
1131
1170
|
return rows.map(fromRunEntity);
|
|
1132
1171
|
}
|
|
@@ -1563,6 +1602,19 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1563
1602
|
});
|
|
1564
1603
|
return entity ? fromCheckpointEntity(entity) : void 0;
|
|
1565
1604
|
}
|
|
1605
|
+
async countCheckpoints(runId, fromSeq, toSeq, statuses) {
|
|
1606
|
+
if (statuses.length === 0) return 0;
|
|
1607
|
+
return this.fork().count(StepCheckpointEntity, {
|
|
1608
|
+
runId,
|
|
1609
|
+
seq: {
|
|
1610
|
+
$gte: fromSeq,
|
|
1611
|
+
$lte: toSeq
|
|
1612
|
+
},
|
|
1613
|
+
status: {
|
|
1614
|
+
$in: statuses
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1566
1618
|
async listCheckpointsByNamePrefix(runId, prefixes) {
|
|
1567
1619
|
if (prefixes.length === 0) return [];
|
|
1568
1620
|
const em = this.fork();
|
|
@@ -1578,6 +1630,103 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1578
1630
|
seq: "asc"
|
|
1579
1631
|
}
|
|
1580
1632
|
});
|
|
1633
|
+
return rows.filter((r) => prefixes.some((p) => r.name.startsWith(p))).map(fromCheckpointEntity);
|
|
1634
|
+
}
|
|
1635
|
+
/** One `GROUP BY parallel_group, status, <name kind>` over the run's checkpoints — the buckets a
|
|
1636
|
+
* run detail summarizes a fan from, without loading its rows. The name-kind `CASE` is the SQL of
|
|
1637
|
+
* `checkpointNameKind`, and `min(coalesce(enqueued_at, started_at))` reads `enqueuedAt` the way
|
|
1638
|
+
* {@link listCheckpoints} does. Raw SQL on metadata-resolved names, so a custom column naming
|
|
1639
|
+
* strategy is honoured. */
|
|
1640
|
+
async checkpointStats(runId, opts) {
|
|
1641
|
+
const em = this.fork();
|
|
1642
|
+
const quote = this.idQuote(em);
|
|
1643
|
+
const meta = em.getMetadata().get(StepCheckpointEntity);
|
|
1644
|
+
const c = /* @__PURE__ */ __name((prop) => quote(meta.properties[prop]?.fieldNames?.[0] ?? prop), "c");
|
|
1645
|
+
const nameKind = `case when ${c("name")} like 'signal:child:%' then 'child' when ${c("name")} like 'fan:%' then 'folded' when ${c("name")} like 'spawn:%' then 'spawn' else 'step' end`;
|
|
1646
|
+
const params = [
|
|
1647
|
+
runId
|
|
1648
|
+
];
|
|
1649
|
+
let where = `${c("runId")} = ?`;
|
|
1650
|
+
if (opts?.group !== void 0) {
|
|
1651
|
+
where += ` and ${c("parallelGroup")} = ?`;
|
|
1652
|
+
params.push(opts.group);
|
|
1653
|
+
}
|
|
1654
|
+
const sql = `select ${c("parallelGroup")} as ${quote("grp")}, ${c("status")} as ${quote("status")}, ${nameKind} as ${quote("name_kind")}, count(*) as ${quote("n")}, min(${c("seq")}) as ${quote("min_seq")}, max(${c("seq")}) as ${quote("max_seq")}, min(${c("name")}) as ${quote("min_name")}, max(${c("name")}) as ${quote("max_name")}, min(coalesce(${c("enqueuedAt")}, ${c("startedAt")})) as ${quote("min_enqueued_at")}, min(${c("startedAt")}) as ${quote("min_started_at")}, max(${c("startedAt")}) as ${quote("max_started_at")}, max(${c("finishedAt")}) as ${quote("max_finished_at")}, sum(${c("attempts")}) as ${quote("sum_attempts")} from ${quote(meta.tableName)} where ${where} group by ${c("parallelGroup")}, ${c("status")}, ${nameKind}`;
|
|
1655
|
+
const rows = await em.getConnection().execute(sql, params);
|
|
1656
|
+
const date = aggregateDateReader(em);
|
|
1657
|
+
return rows.map((row) => ({
|
|
1658
|
+
group: row.grp ?? null,
|
|
1659
|
+
status: row.status,
|
|
1660
|
+
nameKind: row.name_kind,
|
|
1661
|
+
count: Number(row.n),
|
|
1662
|
+
minSeq: Number(row.min_seq),
|
|
1663
|
+
maxSeq: Number(row.max_seq),
|
|
1664
|
+
minName: String(row.min_name),
|
|
1665
|
+
maxName: String(row.max_name),
|
|
1666
|
+
minEnqueuedAt: date(row.min_enqueued_at),
|
|
1667
|
+
minStartedAt: date(row.min_started_at),
|
|
1668
|
+
maxStartedAt: date(row.max_started_at),
|
|
1669
|
+
maxFinishedAt: date(row.max_finished_at),
|
|
1670
|
+
sumAttempts: Number(row.sum_attempts)
|
|
1671
|
+
}));
|
|
1672
|
+
}
|
|
1673
|
+
/** The run's checkpoints matching `query`, in `seq` order and paged in the database — a fan's
|
|
1674
|
+
* failures or one page of its members without the rest of the run. An excluded group keeps the
|
|
1675
|
+
* ungrouped rows (`parallel_group is null or not in (...)`); an empty `statuses`/`namePrefixes`
|
|
1676
|
+
* matches nothing, so it returns before querying. */
|
|
1677
|
+
async queryCheckpoints(runId, query) {
|
|
1678
|
+
if (query.statuses?.length === 0 || query.namePrefixes?.length === 0) return [];
|
|
1679
|
+
const and = [
|
|
1680
|
+
{
|
|
1681
|
+
runId
|
|
1682
|
+
}
|
|
1683
|
+
];
|
|
1684
|
+
if (query.group !== void 0) and.push({
|
|
1685
|
+
parallelGroup: query.group
|
|
1686
|
+
});
|
|
1687
|
+
if (query.excludeGroups?.length) {
|
|
1688
|
+
and.push({
|
|
1689
|
+
$or: [
|
|
1690
|
+
{
|
|
1691
|
+
parallelGroup: null
|
|
1692
|
+
},
|
|
1693
|
+
{
|
|
1694
|
+
parallelGroup: {
|
|
1695
|
+
$nin: query.excludeGroups
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
]
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
if (query.statuses) and.push({
|
|
1702
|
+
status: {
|
|
1703
|
+
$in: query.statuses
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
if (query.namePrefixes) {
|
|
1707
|
+
and.push({
|
|
1708
|
+
$or: query.namePrefixes.map((p) => ({
|
|
1709
|
+
name: {
|
|
1710
|
+
$like: `${p}%`
|
|
1711
|
+
}
|
|
1712
|
+
}))
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
const rows = await this.fork().find(StepCheckpointEntity, {
|
|
1716
|
+
$and: and
|
|
1717
|
+
}, {
|
|
1718
|
+
orderBy: {
|
|
1719
|
+
seq: "asc"
|
|
1720
|
+
},
|
|
1721
|
+
// An offset alone is not valid SQL on SQLite/MySQL, so an unbounded page gets a limit no
|
|
1722
|
+
// run reaches.
|
|
1723
|
+
...query.limit !== void 0 || query.offset ? {
|
|
1724
|
+
limit: query.limit ?? Number.MAX_SAFE_INTEGER
|
|
1725
|
+
} : {},
|
|
1726
|
+
...query.offset ? {
|
|
1727
|
+
offset: query.offset
|
|
1728
|
+
} : {}
|
|
1729
|
+
});
|
|
1581
1730
|
return rows.map(fromCheckpointEntity);
|
|
1582
1731
|
}
|
|
1583
1732
|
async putSignalWaiter(waiter) {
|
|
@@ -1612,7 +1761,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1612
1761
|
$like: `${prefix}%`
|
|
1613
1762
|
}
|
|
1614
1763
|
});
|
|
1615
|
-
return rows.map((e) => ({
|
|
1764
|
+
return rows.filter((e) => e.token.startsWith(prefix)).map((e) => ({
|
|
1616
1765
|
token: e.token,
|
|
1617
1766
|
runId: e.runId,
|
|
1618
1767
|
seq: e.seq,
|
|
@@ -1794,6 +1943,21 @@ function fromRunEntity(e) {
|
|
|
1794
1943
|
};
|
|
1795
1944
|
}
|
|
1796
1945
|
__name(fromRunEntity, "fromRunEntity");
|
|
1946
|
+
function aggregateDateReader(em) {
|
|
1947
|
+
const platform = em.getPlatform();
|
|
1948
|
+
const tz = platform.getTimezone();
|
|
1949
|
+
return (value) => {
|
|
1950
|
+
if (value instanceof Date) return value;
|
|
1951
|
+
if (typeof value === "bigint") return platform.parseDate(Number(value));
|
|
1952
|
+
if (typeof value === "number" || !tz || tz === "local") {
|
|
1953
|
+
return platform.parseDate(value);
|
|
1954
|
+
}
|
|
1955
|
+
const text = String(value);
|
|
1956
|
+
const zoned = text.includes("+") || text.lastIndexOf("-") > 10 || text.endsWith("Z");
|
|
1957
|
+
return platform.parseDate(zoned ? text : `${text}${tz}`);
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
__name(aggregateDateReader, "aggregateDateReader");
|
|
1797
1961
|
function toCheckpointEntity(cp) {
|
|
1798
1962
|
const e = new StepCheckpointEntity();
|
|
1799
1963
|
e.runId = cp.runId;
|