@dudousxd/nestjs-durable-store-typeorm 0.21.0 → 0.22.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 +113 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +113 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -298,6 +298,18 @@ function durableEntities(options = {}) {
|
|
|
298
298
|
name: "StepCheckpointEntity",
|
|
299
299
|
target: StepCheckpointEntity,
|
|
300
300
|
tableName: "durable_step_checkpoints",
|
|
301
|
+
// A run's fan, in member order: `checkpointStats`/`queryCheckpoints` narrow by group and page by
|
|
302
|
+
// seq. The DDL that actually creates it lives in `ensureTypeOrmDurableSchema`.
|
|
303
|
+
indices: [
|
|
304
|
+
{
|
|
305
|
+
name: "durable_step_checkpoints_group_idx",
|
|
306
|
+
columns: [
|
|
307
|
+
"runId",
|
|
308
|
+
"parallelGroup",
|
|
309
|
+
"seq"
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
],
|
|
301
313
|
columns: {
|
|
302
314
|
runId: {
|
|
303
315
|
type: "text",
|
|
@@ -840,6 +852,13 @@ async function ensureTypeOrmDurableSchema(dataSource) {
|
|
|
840
852
|
"durable_runs_namespace_status_idx",
|
|
841
853
|
`${runs} (${runsCol("namespace")}, ${runsCol("status")}, ${runsCol("createdAt")})`
|
|
842
854
|
],
|
|
855
|
+
// A run's checkpoints by fan, in seq order: the run detail's per-group aggregates and fan
|
|
856
|
+
// pages. `parallel_group` is `varchar(191)` in this DDL (also when added by ALTER above), so
|
|
857
|
+
// MySQL can key it without a prefix length.
|
|
858
|
+
[
|
|
859
|
+
"durable_step_checkpoints_group_idx",
|
|
860
|
+
`${checkpoints} (${cpCol("runId")}, ${cpCol("parallelGroup")}, ${cpCol("seq")})`
|
|
861
|
+
],
|
|
843
862
|
// A run's waiters: the `ctx.all` join barrier probes them once per settled child.
|
|
844
863
|
[
|
|
845
864
|
"durable_signal_waiters_run_id_idx",
|
|
@@ -899,6 +918,7 @@ __name(existingColumns, "existingColumns");
|
|
|
899
918
|
// src/typeorm-state-store.ts
|
|
900
919
|
var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
|
|
901
920
|
var import_typeorm2 = require("typeorm");
|
|
921
|
+
var CHECKPOINT_NAME_KIND_SQL = "CASE WHEN cp.name LIKE 'signal:child:%' THEN 'child' WHEN cp.name LIKE 'fan:%' THEN 'folded' WHEN cp.name LIKE 'spawn:%' THEN 'spawn' ELSE 'step' END";
|
|
902
922
|
var sqliteSingletonClaims = /* @__PURE__ */ new WeakMap();
|
|
903
923
|
var TypeOrmStateStore = class {
|
|
904
924
|
static {
|
|
@@ -1105,20 +1125,16 @@ var TypeOrmStateStore = class {
|
|
|
1105
1125
|
]);
|
|
1106
1126
|
}
|
|
1107
1127
|
async saveCheckpointIfPending(checkpoint) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
seq,
|
|
1119
|
-
status: "pending"
|
|
1120
|
-
}).execute();
|
|
1121
|
-
return (result.affected ?? 0) > 0;
|
|
1128
|
+
return updateIfPending(this.checkpoints(), checkpoint);
|
|
1129
|
+
}
|
|
1130
|
+
async saveCheckpointsIfPending(checkpoints) {
|
|
1131
|
+
if (checkpoints.length === 0) return [];
|
|
1132
|
+
return this.dataSource.transaction(async (em) => {
|
|
1133
|
+
const repo = em.getRepository(StepCheckpointEntity);
|
|
1134
|
+
const wrote = [];
|
|
1135
|
+
for (const checkpoint of checkpoints) wrote.push(await updateIfPending(repo, checkpoint));
|
|
1136
|
+
return wrote;
|
|
1137
|
+
});
|
|
1122
1138
|
}
|
|
1123
1139
|
async transaction(work) {
|
|
1124
1140
|
return this.dataSource.transaction(async (em) => work({
|
|
@@ -1440,7 +1456,71 @@ var TypeOrmStateStore = class {
|
|
|
1440
1456
|
seq: "ASC"
|
|
1441
1457
|
}
|
|
1442
1458
|
});
|
|
1443
|
-
return rows.map(fromCheckpointEntity);
|
|
1459
|
+
return rows.filter((r) => prefixes.some((p) => r.name.startsWith(p))).map(fromCheckpointEntity);
|
|
1460
|
+
}
|
|
1461
|
+
/** `GROUP BY parallelGroup, status, nameKind` over one run's checkpoints — one aggregate, so a run
|
|
1462
|
+
* detail draws a wide fan from a few bucket rows instead of loading every member. The `CASE` is
|
|
1463
|
+
* {@link checkpointNameKind} in SQL, and the date aggregates are hydrated through the driver the
|
|
1464
|
+
* way an entity read hydrates the column, so a bucket compares equal to the in-memory reference. */
|
|
1465
|
+
async checkpointStats(runId, opts) {
|
|
1466
|
+
const qb = this.checkpoints().createQueryBuilder("cp").select("cp.parallelGroup", "group").addSelect("cp.status", "status").addSelect(CHECKPOINT_NAME_KIND_SQL, "nameKind").addSelect("COUNT(*)", "count").addSelect("MIN(cp.seq)", "minSeq").addSelect("MAX(cp.seq)", "maxSeq").addSelect("MIN(cp.name)", "minName").addSelect("MAX(cp.name)", "maxName").addSelect("MIN(COALESCE(cp.enqueuedAt, cp.startedAt))", "minEnqueuedAt").addSelect("MIN(cp.startedAt)", "minStartedAt").addSelect("MAX(cp.startedAt)", "maxStartedAt").addSelect("MAX(cp.finishedAt)", "maxFinishedAt").addSelect("SUM(cp.attempts)", "sumAttempts").where("cp.runId = :runId", {
|
|
1467
|
+
runId
|
|
1468
|
+
});
|
|
1469
|
+
if (opts?.group !== void 0) qb.andWhere("cp.parallelGroup = :group", {
|
|
1470
|
+
group: opts.group
|
|
1471
|
+
});
|
|
1472
|
+
const rows = await qb.groupBy("cp.parallelGroup").addGroupBy("cp.status").addGroupBy(CHECKPOINT_NAME_KIND_SQL).getRawMany();
|
|
1473
|
+
const dateColumn = this.dataSource.getMetadata(StepCheckpointEntity).findColumnWithPropertyName("startedAt");
|
|
1474
|
+
const toDate = /* @__PURE__ */ __name((raw) => dateColumn ? this.dataSource.driver.prepareHydratedValue(raw, dateColumn) : new Date(raw), "toDate");
|
|
1475
|
+
return rows.map((r) => ({
|
|
1476
|
+
group: r.group ?? null,
|
|
1477
|
+
status: r.status,
|
|
1478
|
+
nameKind: r.nameKind,
|
|
1479
|
+
count: Number(r.count),
|
|
1480
|
+
minSeq: Number(r.minSeq),
|
|
1481
|
+
maxSeq: Number(r.maxSeq),
|
|
1482
|
+
minName: r.minName,
|
|
1483
|
+
maxName: r.maxName,
|
|
1484
|
+
minEnqueuedAt: toDate(r.minEnqueuedAt),
|
|
1485
|
+
minStartedAt: toDate(r.minStartedAt),
|
|
1486
|
+
maxStartedAt: toDate(r.maxStartedAt),
|
|
1487
|
+
maxFinishedAt: toDate(r.maxFinishedAt),
|
|
1488
|
+
sumAttempts: Number(r.sumAttempts)
|
|
1489
|
+
}));
|
|
1490
|
+
}
|
|
1491
|
+
/** One run's checkpoints matching `query`, in `seq` order and paged in SQL — the read the run
|
|
1492
|
+
* detail uses for a fan's failures, its folded checkpoints and one page of members. An empty
|
|
1493
|
+
* `statuses`/`namePrefixes` matches nothing; an empty `excludeGroups` excludes nothing. */
|
|
1494
|
+
async queryCheckpoints(runId, query) {
|
|
1495
|
+
if (query.statuses?.length === 0 || query.namePrefixes?.length === 0) return [];
|
|
1496
|
+
const qb = this.checkpoints().createQueryBuilder("cp").where("cp.runId = :runId", {
|
|
1497
|
+
runId
|
|
1498
|
+
});
|
|
1499
|
+
if (query.group !== void 0) qb.andWhere("cp.parallelGroup = :group", {
|
|
1500
|
+
group: query.group
|
|
1501
|
+
});
|
|
1502
|
+
if (query.excludeGroups?.length) {
|
|
1503
|
+
qb.andWhere("(cp.parallelGroup IS NULL OR cp.parallelGroup NOT IN (:...excludeGroups))", {
|
|
1504
|
+
excludeGroups: query.excludeGroups
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
if (query.statuses) qb.andWhere("cp.status IN (:...statuses)", {
|
|
1508
|
+
statuses: query.statuses
|
|
1509
|
+
});
|
|
1510
|
+
if (query.namePrefixes) {
|
|
1511
|
+
const prefixes = query.namePrefixes.map((p) => `${p.replace(/[!%_]/g, "!$&")}%`);
|
|
1512
|
+
const branches = prefixes.map((_, i) => `cp.name LIKE :namePrefix${i} ESCAPE '!'`);
|
|
1513
|
+
qb.andWhere(`(${branches.join(" OR ")})`, Object.fromEntries(prefixes.map((p, i) => [
|
|
1514
|
+
`namePrefix${i}`,
|
|
1515
|
+
p
|
|
1516
|
+
])));
|
|
1517
|
+
}
|
|
1518
|
+
qb.orderBy("cp.seq", "ASC");
|
|
1519
|
+
if (query.offset != null) qb.offset(query.offset);
|
|
1520
|
+
if (query.limit != null || query.offset != null) {
|
|
1521
|
+
qb.limit(query.limit ?? Number.MAX_SAFE_INTEGER);
|
|
1522
|
+
}
|
|
1523
|
+
return (await qb.getMany()).map(fromCheckpointEntity);
|
|
1444
1524
|
}
|
|
1445
1525
|
async putSignalWaiter(waiter) {
|
|
1446
1526
|
await this.waiters().save({
|
|
@@ -1456,7 +1536,7 @@ var TypeOrmStateStore = class {
|
|
|
1456
1536
|
token: (0, import_typeorm2.Like)(`${prefix}%`)
|
|
1457
1537
|
}
|
|
1458
1538
|
});
|
|
1459
|
-
return rows.map((e) => ({
|
|
1539
|
+
return rows.filter((e) => e.token.startsWith(prefix)).map((e) => ({
|
|
1460
1540
|
token: e.token,
|
|
1461
1541
|
runId: e.runId,
|
|
1462
1542
|
seq: e.seq,
|
|
@@ -1747,6 +1827,23 @@ function fromScheduleEntity(e) {
|
|
|
1747
1827
|
};
|
|
1748
1828
|
}
|
|
1749
1829
|
__name(fromScheduleEntity, "fromScheduleEntity");
|
|
1830
|
+
async function updateIfPending(repo, checkpoint) {
|
|
1831
|
+
const { runId, seq, ...data } = toCheckpointEntity(checkpoint);
|
|
1832
|
+
const set = {
|
|
1833
|
+
...data,
|
|
1834
|
+
wakeAt: checkpoint.wakeAt != null ? new Date(checkpoint.wakeAt) : () => "NULL"
|
|
1835
|
+
};
|
|
1836
|
+
for (const key of Object.keys(set)) {
|
|
1837
|
+
if (set[key] === void 0) delete set[key];
|
|
1838
|
+
}
|
|
1839
|
+
const result = await repo.createQueryBuilder().update().set(set).where({
|
|
1840
|
+
runId,
|
|
1841
|
+
seq,
|
|
1842
|
+
status: "pending"
|
|
1843
|
+
}).execute();
|
|
1844
|
+
return (result.affected ?? 0) > 0;
|
|
1845
|
+
}
|
|
1846
|
+
__name(updateIfPending, "updateIfPending");
|
|
1750
1847
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1751
1848
|
0 && (module.exports = {
|
|
1752
1849
|
BufferedEventEntity,
|