@dudousxd/nestjs-durable-store-mikro-orm 0.29.0 → 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 +145 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +145 -1
- 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,6 +198,7 @@ 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>;
|
|
@@ -290,6 +291,19 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
290
291
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
291
292
|
countCheckpoints(runId: string, fromSeq: number, toSeq: number, statuses: StepCheckpoint['status'][]): Promise<number>;
|
|
292
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[]>;
|
|
293
307
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
294
308
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
295
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,6 +198,7 @@ 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>;
|
|
@@ -290,6 +291,19 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
290
291
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
291
292
|
countCheckpoints(runId: string, fromSeq: number, toSeq: number, statuses: StepCheckpoint['status'][]): Promise<number>;
|
|
292
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[]>;
|
|
293
307
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
294
308
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
295
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,
|
|
@@ -1598,6 +1630,103 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1598
1630
|
seq: "asc"
|
|
1599
1631
|
}
|
|
1600
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
|
+
});
|
|
1601
1730
|
return rows.map(fromCheckpointEntity);
|
|
1602
1731
|
}
|
|
1603
1732
|
async putSignalWaiter(waiter) {
|
|
@@ -1632,7 +1761,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1632
1761
|
$like: `${prefix}%`
|
|
1633
1762
|
}
|
|
1634
1763
|
});
|
|
1635
|
-
return rows.map((e) => ({
|
|
1764
|
+
return rows.filter((e) => e.token.startsWith(prefix)).map((e) => ({
|
|
1636
1765
|
token: e.token,
|
|
1637
1766
|
runId: e.runId,
|
|
1638
1767
|
seq: e.seq,
|
|
@@ -1814,6 +1943,21 @@ function fromRunEntity(e) {
|
|
|
1814
1943
|
};
|
|
1815
1944
|
}
|
|
1816
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");
|
|
1817
1961
|
function toCheckpointEntity(cp) {
|
|
1818
1962
|
const e = new StepCheckpointEntity();
|
|
1819
1963
|
e.runId = cp.runId;
|