@dudousxd/nestjs-durable-store-typeorm 0.20.1 → 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 +124 -2
- 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 +124 -2
- 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 {
|
|
@@ -1104,6 +1124,18 @@ var TypeOrmStateStore = class {
|
|
|
1104
1124
|
"seq"
|
|
1105
1125
|
]);
|
|
1106
1126
|
}
|
|
1127
|
+
async saveCheckpointIfPending(checkpoint) {
|
|
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
|
+
});
|
|
1138
|
+
}
|
|
1107
1139
|
async transaction(work) {
|
|
1108
1140
|
return this.dataSource.transaction(async (em) => work({
|
|
1109
1141
|
raw: em,
|
|
@@ -1404,6 +1436,15 @@ var TypeOrmStateStore = class {
|
|
|
1404
1436
|
});
|
|
1405
1437
|
return e ? fromCheckpointEntity(e) : void 0;
|
|
1406
1438
|
}
|
|
1439
|
+
async countCheckpoints(runId, fromSeq, toSeq, statuses) {
|
|
1440
|
+
if (statuses.length === 0) return 0;
|
|
1441
|
+
const row = await this.checkpoints().createQueryBuilder("cp").select("COUNT(*)", "count").where({
|
|
1442
|
+
runId,
|
|
1443
|
+
seq: (0, import_typeorm2.Between)(fromSeq, toSeq),
|
|
1444
|
+
status: (0, import_typeorm2.In)(statuses)
|
|
1445
|
+
}).getRawOne();
|
|
1446
|
+
return Number(row?.count ?? 0);
|
|
1447
|
+
}
|
|
1407
1448
|
async listCheckpointsByNamePrefix(runId, prefixes) {
|
|
1408
1449
|
if (prefixes.length === 0) return [];
|
|
1409
1450
|
const rows = await this.checkpoints().find({
|
|
@@ -1415,7 +1456,71 @@ var TypeOrmStateStore = class {
|
|
|
1415
1456
|
seq: "ASC"
|
|
1416
1457
|
}
|
|
1417
1458
|
});
|
|
1418
|
-
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);
|
|
1419
1524
|
}
|
|
1420
1525
|
async putSignalWaiter(waiter) {
|
|
1421
1526
|
await this.waiters().save({
|
|
@@ -1431,7 +1536,7 @@ var TypeOrmStateStore = class {
|
|
|
1431
1536
|
token: (0, import_typeorm2.Like)(`${prefix}%`)
|
|
1432
1537
|
}
|
|
1433
1538
|
});
|
|
1434
|
-
return rows.map((e) => ({
|
|
1539
|
+
return rows.filter((e) => e.token.startsWith(prefix)).map((e) => ({
|
|
1435
1540
|
token: e.token,
|
|
1436
1541
|
runId: e.runId,
|
|
1437
1542
|
seq: e.seq,
|
|
@@ -1722,6 +1827,23 @@ function fromScheduleEntity(e) {
|
|
|
1722
1827
|
};
|
|
1723
1828
|
}
|
|
1724
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");
|
|
1725
1847
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1726
1848
|
0 && (module.exports = {
|
|
1727
1849
|
BufferedEventEntity,
|