@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 CHANGED
@@ -379,6 +379,19 @@ function durableEntities(options = {}) {
379
379
  class: StepCheckpointEntity,
380
380
  tableName: "durable_step_checkpoints",
381
381
  repository: /* @__PURE__ */ __name(() => StepCheckpointRepository, "repository"),
382
+ // `(runId, parallelGroup, seq)` serves the run detail's per-group reads — `checkpointStats` with a
383
+ // group and `queryCheckpoints` paging one fan in `seq` order — without scanning the whole run.
384
+ // Mirrors the Prisma adapter's index of the same name.
385
+ indexes: [
386
+ {
387
+ name: "durable_step_checkpoints_group_idx",
388
+ properties: [
389
+ "runId",
390
+ "parallelGroup",
391
+ "seq"
392
+ ]
393
+ }
394
+ ],
382
395
  properties: {
383
396
  runId: {
384
397
  type: "string",
@@ -1120,6 +1133,25 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1120
1133
  });
1121
1134
  return affected > 0;
1122
1135
  }
1136
+ async saveCheckpointsIfPending(checkpoints) {
1137
+ if (checkpoints.length === 0) return [];
1138
+ return this.fork().transactional(async (em) => {
1139
+ const wrote = [];
1140
+ for (const checkpoint of checkpoints) {
1141
+ const { runId: _runId, seq: _seq, ...data } = toCheckpointEntity(checkpoint);
1142
+ const affected = await em.nativeUpdate(StepCheckpointEntity, {
1143
+ runId: checkpoint.runId,
1144
+ seq: checkpoint.seq,
1145
+ status: "pending"
1146
+ }, {
1147
+ ...data,
1148
+ wakeAt: checkpoint.wakeAt != null ? new Date(checkpoint.wakeAt) : null
1149
+ });
1150
+ wrote.push(affected > 0);
1151
+ }
1152
+ return wrote;
1153
+ });
1154
+ }
1123
1155
  async transaction(work) {
1124
1156
  return this.fork().transactional(async (em) => work({
1125
1157
  raw: em,
@@ -1158,7 +1190,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1158
1190
  });
1159
1191
  return rows.map(fromRunEntity);
1160
1192
  }
1161
- async listDueTimers(nowMs, namespace) {
1193
+ async listDueTimers(nowMs, namespace, limit) {
1162
1194
  const em = this.fork();
1163
1195
  const rows = await em.find(WorkflowRunEntity, {
1164
1196
  status: "suspended",
@@ -1169,6 +1201,13 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1169
1201
  ...namespace !== void 0 ? {
1170
1202
  namespace
1171
1203
  } : {}
1204
+ }, {
1205
+ orderBy: {
1206
+ wakeAt: "asc"
1207
+ },
1208
+ ...limit !== void 0 ? {
1209
+ limit
1210
+ } : {}
1172
1211
  });
1173
1212
  return rows.map(fromRunEntity);
1174
1213
  }
@@ -1605,6 +1644,19 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1605
1644
  });
1606
1645
  return entity ? fromCheckpointEntity(entity) : void 0;
1607
1646
  }
1647
+ async countCheckpoints(runId, fromSeq, toSeq, statuses) {
1648
+ if (statuses.length === 0) return 0;
1649
+ return this.fork().count(StepCheckpointEntity, {
1650
+ runId,
1651
+ seq: {
1652
+ $gte: fromSeq,
1653
+ $lte: toSeq
1654
+ },
1655
+ status: {
1656
+ $in: statuses
1657
+ }
1658
+ });
1659
+ }
1608
1660
  async listCheckpointsByNamePrefix(runId, prefixes) {
1609
1661
  if (prefixes.length === 0) return [];
1610
1662
  const em = this.fork();
@@ -1620,6 +1672,103 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1620
1672
  seq: "asc"
1621
1673
  }
1622
1674
  });
1675
+ return rows.filter((r) => prefixes.some((p) => r.name.startsWith(p))).map(fromCheckpointEntity);
1676
+ }
1677
+ /** One `GROUP BY parallel_group, status, <name kind>` over the run's checkpoints — the buckets a
1678
+ * run detail summarizes a fan from, without loading its rows. The name-kind `CASE` is the SQL of
1679
+ * `checkpointNameKind`, and `min(coalesce(enqueued_at, started_at))` reads `enqueuedAt` the way
1680
+ * {@link listCheckpoints} does. Raw SQL on metadata-resolved names, so a custom column naming
1681
+ * strategy is honoured. */
1682
+ async checkpointStats(runId, opts) {
1683
+ const em = this.fork();
1684
+ const quote = this.idQuote(em);
1685
+ const meta = em.getMetadata().get(StepCheckpointEntity);
1686
+ const c = /* @__PURE__ */ __name((prop) => quote(meta.properties[prop]?.fieldNames?.[0] ?? prop), "c");
1687
+ 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`;
1688
+ const params = [
1689
+ runId
1690
+ ];
1691
+ let where = `${c("runId")} = ?`;
1692
+ if (opts?.group !== void 0) {
1693
+ where += ` and ${c("parallelGroup")} = ?`;
1694
+ params.push(opts.group);
1695
+ }
1696
+ 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}`;
1697
+ const rows = await em.getConnection().execute(sql, params);
1698
+ const date = aggregateDateReader(em);
1699
+ return rows.map((row) => ({
1700
+ group: row.grp ?? null,
1701
+ status: row.status,
1702
+ nameKind: row.name_kind,
1703
+ count: Number(row.n),
1704
+ minSeq: Number(row.min_seq),
1705
+ maxSeq: Number(row.max_seq),
1706
+ minName: String(row.min_name),
1707
+ maxName: String(row.max_name),
1708
+ minEnqueuedAt: date(row.min_enqueued_at),
1709
+ minStartedAt: date(row.min_started_at),
1710
+ maxStartedAt: date(row.max_started_at),
1711
+ maxFinishedAt: date(row.max_finished_at),
1712
+ sumAttempts: Number(row.sum_attempts)
1713
+ }));
1714
+ }
1715
+ /** The run's checkpoints matching `query`, in `seq` order and paged in the database — a fan's
1716
+ * failures or one page of its members without the rest of the run. An excluded group keeps the
1717
+ * ungrouped rows (`parallel_group is null or not in (...)`); an empty `statuses`/`namePrefixes`
1718
+ * matches nothing, so it returns before querying. */
1719
+ async queryCheckpoints(runId, query) {
1720
+ if (query.statuses?.length === 0 || query.namePrefixes?.length === 0) return [];
1721
+ const and = [
1722
+ {
1723
+ runId
1724
+ }
1725
+ ];
1726
+ if (query.group !== void 0) and.push({
1727
+ parallelGroup: query.group
1728
+ });
1729
+ if (query.excludeGroups?.length) {
1730
+ and.push({
1731
+ $or: [
1732
+ {
1733
+ parallelGroup: null
1734
+ },
1735
+ {
1736
+ parallelGroup: {
1737
+ $nin: query.excludeGroups
1738
+ }
1739
+ }
1740
+ ]
1741
+ });
1742
+ }
1743
+ if (query.statuses) and.push({
1744
+ status: {
1745
+ $in: query.statuses
1746
+ }
1747
+ });
1748
+ if (query.namePrefixes) {
1749
+ and.push({
1750
+ $or: query.namePrefixes.map((p) => ({
1751
+ name: {
1752
+ $like: `${p}%`
1753
+ }
1754
+ }))
1755
+ });
1756
+ }
1757
+ const rows = await this.fork().find(StepCheckpointEntity, {
1758
+ $and: and
1759
+ }, {
1760
+ orderBy: {
1761
+ seq: "asc"
1762
+ },
1763
+ // An offset alone is not valid SQL on SQLite/MySQL, so an unbounded page gets a limit no
1764
+ // run reaches.
1765
+ ...query.limit !== void 0 || query.offset ? {
1766
+ limit: query.limit ?? Number.MAX_SAFE_INTEGER
1767
+ } : {},
1768
+ ...query.offset ? {
1769
+ offset: query.offset
1770
+ } : {}
1771
+ });
1623
1772
  return rows.map(fromCheckpointEntity);
1624
1773
  }
1625
1774
  async putSignalWaiter(waiter) {
@@ -1654,7 +1803,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1654
1803
  $like: `${prefix}%`
1655
1804
  }
1656
1805
  });
1657
- return rows.map((e) => ({
1806
+ return rows.filter((e) => e.token.startsWith(prefix)).map((e) => ({
1658
1807
  token: e.token,
1659
1808
  runId: e.runId,
1660
1809
  seq: e.seq,
@@ -1836,6 +1985,21 @@ function fromRunEntity(e) {
1836
1985
  };
1837
1986
  }
1838
1987
  __name(fromRunEntity, "fromRunEntity");
1988
+ function aggregateDateReader(em) {
1989
+ const platform = em.getPlatform();
1990
+ const tz = platform.getTimezone();
1991
+ return (value) => {
1992
+ if (value instanceof Date) return value;
1993
+ if (typeof value === "bigint") return platform.parseDate(Number(value));
1994
+ if (typeof value === "number" || !tz || tz === "local") {
1995
+ return platform.parseDate(value);
1996
+ }
1997
+ const text = String(value);
1998
+ const zoned = text.includes("+") || text.lastIndexOf("-") > 10 || text.endsWith("Z");
1999
+ return platform.parseDate(zoned ? text : `${text}${tz}`);
2000
+ };
2001
+ }
2002
+ __name(aggregateDateReader, "aggregateDateReader");
1839
2003
  function toCheckpointEntity(cp) {
1840
2004
  const e = new StepCheckpointEntity();
1841
2005
  e.runId = cp.runId;