@dudousxd/nestjs-durable-store-prisma 0.20.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 +259 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +259 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/prisma/schema.prisma +2 -0
package/dist/index.js
CHANGED
|
@@ -74,6 +74,22 @@ var PrismaStateStore = class {
|
|
|
74
74
|
});
|
|
75
75
|
if ("searchAttributes" in patch) await this.reindexAttributes(runId, patch.searchAttributes);
|
|
76
76
|
}
|
|
77
|
+
async updateRunIf(runId, expectedStatuses, inputPatch) {
|
|
78
|
+
if (expectedStatuses.length === 0) return false;
|
|
79
|
+
const patch = inputPatch.status && TERMINAL_RUN_STATUSES.includes(inputPatch.status) ? await clearTerminalSingletonAdmission(runId, inputPatch, (id) => this.getRun(id)) : inputPatch;
|
|
80
|
+
const { count } = await this.db.durableWorkflowRun.updateMany({
|
|
81
|
+
where: {
|
|
82
|
+
id: runId,
|
|
83
|
+
status: {
|
|
84
|
+
in: expectedStatuses
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
data: toRunPatch(patch)
|
|
88
|
+
});
|
|
89
|
+
if (count === 0) return false;
|
|
90
|
+
if ("searchAttributes" in patch) await this.reindexAttributes(runId, patch.searchAttributes);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
77
93
|
/** Rewrite a run's normalized attribute rows: delete the old set, insert the current one. Mirrors
|
|
78
94
|
* the in-memory store's reindex so the side-table always reflects the run's live searchAttributes. */
|
|
79
95
|
async reindexAttributes(runId, attributes) {
|
|
@@ -246,6 +262,25 @@ var PrismaStateStore = class {
|
|
|
246
262
|
});
|
|
247
263
|
return count > 0;
|
|
248
264
|
}
|
|
265
|
+
async saveCheckpointsIfPending(checkpoints) {
|
|
266
|
+
if (checkpoints.length === 0) return [];
|
|
267
|
+
return this.db.$transaction(async (tx) => {
|
|
268
|
+
const wrote = [];
|
|
269
|
+
for (const checkpoint of checkpoints) {
|
|
270
|
+
const { runId, seq, ...data } = toCheckpointData(checkpoint);
|
|
271
|
+
const { count } = await tx.durableStepCheckpoint.updateMany({
|
|
272
|
+
where: {
|
|
273
|
+
runId,
|
|
274
|
+
seq,
|
|
275
|
+
status: "pending"
|
|
276
|
+
},
|
|
277
|
+
data
|
|
278
|
+
});
|
|
279
|
+
wrote.push(count > 0);
|
|
280
|
+
}
|
|
281
|
+
return wrote;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
249
284
|
async transaction(work) {
|
|
250
285
|
return this.db.$transaction(async (tx) => work({
|
|
251
286
|
raw: tx,
|
|
@@ -278,6 +313,35 @@ var PrismaStateStore = class {
|
|
|
278
313
|
});
|
|
279
314
|
return rows.map(fromRunRow);
|
|
280
315
|
}
|
|
316
|
+
async listOrphanedRuns(nowMs, limit, namespace) {
|
|
317
|
+
if (limit <= 0) return [];
|
|
318
|
+
const rows = await this.db.durableWorkflowRun.findMany({
|
|
319
|
+
where: {
|
|
320
|
+
status: {
|
|
321
|
+
in: [
|
|
322
|
+
"running",
|
|
323
|
+
"cancelling"
|
|
324
|
+
]
|
|
325
|
+
},
|
|
326
|
+
OR: [
|
|
327
|
+
{
|
|
328
|
+
lockedUntil: null
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
lockedUntil: {
|
|
332
|
+
lte: new Date(nowMs)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
],
|
|
336
|
+
...namespaceWhere(namespace)
|
|
337
|
+
},
|
|
338
|
+
orderBy: {
|
|
339
|
+
createdAt: "asc"
|
|
340
|
+
},
|
|
341
|
+
take: limit
|
|
342
|
+
});
|
|
343
|
+
return rows.map(fromRunRow);
|
|
344
|
+
}
|
|
281
345
|
async listPendingRuns(limit, namespace) {
|
|
282
346
|
const rows = await this.db.durableWorkflowRun.findMany({
|
|
283
347
|
where: {
|
|
@@ -610,6 +674,137 @@ var PrismaStateStore = class {
|
|
|
610
674
|
seq: "asc"
|
|
611
675
|
}
|
|
612
676
|
});
|
|
677
|
+
return rows.filter((r) => prefixes.some((p) => r.name.startsWith(p))).map(fromCheckpointRow);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* One `groupBy(parallelGroup, status)` per {@link CheckpointNameKind}, each narrowed to its kind by
|
|
681
|
+
* a name-prefix predicate, run concurrently — Prisma cannot group by a `CASE`, so the kind is the
|
|
682
|
+
* `where` instead. `minEnqueuedAt` reads a NULL `enqueuedAt` as `startedAt` (as
|
|
683
|
+
* {@link fromCheckpointRow} does): a bucket whose non-null `enqueuedAt` count falls short of its
|
|
684
|
+
* row count has such rows, and only for those kinds is the NULL subset's `min(startedAt)` asked for.
|
|
685
|
+
*
|
|
686
|
+
* On SQLite `startsWith` is a case-insensitive `LIKE`, as in {@link listCheckpointsByNamePrefix};
|
|
687
|
+
* the kind prefixes are lower-case and wildcard-free, so only a step whose name starts with one of
|
|
688
|
+
* them in another case would be classified differently there.
|
|
689
|
+
*/
|
|
690
|
+
async checkpointStats(runId, opts) {
|
|
691
|
+
const scope = opts?.group === void 0 ? {
|
|
692
|
+
runId
|
|
693
|
+
} : {
|
|
694
|
+
runId,
|
|
695
|
+
parallelGroup: opts.group
|
|
696
|
+
};
|
|
697
|
+
const grouped = await Promise.all(NAME_KINDS.map(async ({ kind, where }) => ({
|
|
698
|
+
kind,
|
|
699
|
+
where,
|
|
700
|
+
rows: await this.db.durableStepCheckpoint.groupBy({
|
|
701
|
+
by: [
|
|
702
|
+
"parallelGroup",
|
|
703
|
+
"status"
|
|
704
|
+
],
|
|
705
|
+
where: {
|
|
706
|
+
...scope,
|
|
707
|
+
...where
|
|
708
|
+
},
|
|
709
|
+
_count: {
|
|
710
|
+
_all: true,
|
|
711
|
+
enqueuedAt: true
|
|
712
|
+
},
|
|
713
|
+
_min: {
|
|
714
|
+
seq: true,
|
|
715
|
+
name: true,
|
|
716
|
+
enqueuedAt: true,
|
|
717
|
+
startedAt: true
|
|
718
|
+
},
|
|
719
|
+
_max: {
|
|
720
|
+
seq: true,
|
|
721
|
+
name: true,
|
|
722
|
+
startedAt: true,
|
|
723
|
+
finishedAt: true
|
|
724
|
+
},
|
|
725
|
+
_sum: {
|
|
726
|
+
attempts: true
|
|
727
|
+
}
|
|
728
|
+
})
|
|
729
|
+
})));
|
|
730
|
+
const stats = await Promise.all(grouped.map(async ({ kind, where, rows }) => {
|
|
731
|
+
const unenqueued = rows.some((r) => r._count.enqueuedAt < r._count._all) ? await this.unenqueuedStartedAt({
|
|
732
|
+
...scope,
|
|
733
|
+
...where
|
|
734
|
+
}) : /* @__PURE__ */ new Map();
|
|
735
|
+
return rows.map((r) => toCheckpointStat(kind, r, unenqueued));
|
|
736
|
+
}));
|
|
737
|
+
return stats.flat();
|
|
738
|
+
}
|
|
739
|
+
/** `min(startedAt)` per `(parallelGroup, status)` over the rows whose `enqueuedAt` is NULL. */
|
|
740
|
+
async unenqueuedStartedAt(where) {
|
|
741
|
+
const rows = await this.db.durableStepCheckpoint.groupBy({
|
|
742
|
+
by: [
|
|
743
|
+
"parallelGroup",
|
|
744
|
+
"status"
|
|
745
|
+
],
|
|
746
|
+
where: {
|
|
747
|
+
...where,
|
|
748
|
+
enqueuedAt: null
|
|
749
|
+
},
|
|
750
|
+
_min: {
|
|
751
|
+
startedAt: true
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
return new Map(rows.map((r) => [
|
|
755
|
+
bucketKey(r.parallelGroup, r.status),
|
|
756
|
+
r._min.startedAt
|
|
757
|
+
]));
|
|
758
|
+
}
|
|
759
|
+
/** {@link CheckpointQuery} as one `findMany`, in `seq` order, paged by `skip`/`take`. Ungrouped rows
|
|
760
|
+
* are kept past `excludeGroups` explicitly: `NOT IN` alone drops a NULL `parallelGroup`. */
|
|
761
|
+
async queryCheckpoints(runId, query) {
|
|
762
|
+
if (query.statuses?.length === 0 || query.namePrefixes?.length === 0) return [];
|
|
763
|
+
const and = [];
|
|
764
|
+
if (query.excludeGroups?.length) {
|
|
765
|
+
and.push({
|
|
766
|
+
OR: [
|
|
767
|
+
{
|
|
768
|
+
parallelGroup: null
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
parallelGroup: {
|
|
772
|
+
notIn: query.excludeGroups
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
]
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
if (query.namePrefixes) {
|
|
779
|
+
and.push({
|
|
780
|
+
OR: query.namePrefixes.map((p) => ({
|
|
781
|
+
name: {
|
|
782
|
+
startsWith: p
|
|
783
|
+
}
|
|
784
|
+
}))
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
const rows = await this.db.durableStepCheckpoint.findMany({
|
|
788
|
+
where: {
|
|
789
|
+
runId,
|
|
790
|
+
...query.group === void 0 ? {} : {
|
|
791
|
+
parallelGroup: query.group
|
|
792
|
+
},
|
|
793
|
+
...query.statuses ? {
|
|
794
|
+
status: {
|
|
795
|
+
in: query.statuses
|
|
796
|
+
}
|
|
797
|
+
} : {},
|
|
798
|
+
...and.length ? {
|
|
799
|
+
AND: and
|
|
800
|
+
} : {}
|
|
801
|
+
},
|
|
802
|
+
orderBy: {
|
|
803
|
+
seq: "asc"
|
|
804
|
+
},
|
|
805
|
+
skip: query.offset,
|
|
806
|
+
take: query.limit
|
|
807
|
+
});
|
|
613
808
|
return rows.map(fromCheckpointRow);
|
|
614
809
|
}
|
|
615
810
|
async putSignalWaiter(waiter) {
|
|
@@ -657,7 +852,7 @@ var PrismaStateStore = class {
|
|
|
657
852
|
}
|
|
658
853
|
}
|
|
659
854
|
});
|
|
660
|
-
return rows.map((r) => ({
|
|
855
|
+
return rows.filter((r) => r.token.startsWith(prefix)).map((r) => ({
|
|
661
856
|
token: r.token,
|
|
662
857
|
runId: r.runId,
|
|
663
858
|
seq: r.seq,
|
|
@@ -947,6 +1142,69 @@ function fromRunRow(row) {
|
|
|
947
1142
|
};
|
|
948
1143
|
}
|
|
949
1144
|
__name(fromRunRow, "fromRunRow");
|
|
1145
|
+
var KIND_PREFIXES = [
|
|
1146
|
+
[
|
|
1147
|
+
"child",
|
|
1148
|
+
"signal:child:"
|
|
1149
|
+
],
|
|
1150
|
+
[
|
|
1151
|
+
"folded",
|
|
1152
|
+
"fan:"
|
|
1153
|
+
],
|
|
1154
|
+
[
|
|
1155
|
+
"spawn",
|
|
1156
|
+
"spawn:"
|
|
1157
|
+
]
|
|
1158
|
+
];
|
|
1159
|
+
var NAME_KINDS = [
|
|
1160
|
+
...KIND_PREFIXES.map(([kind, prefix]) => ({
|
|
1161
|
+
kind,
|
|
1162
|
+
where: {
|
|
1163
|
+
name: {
|
|
1164
|
+
startsWith: prefix
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
})),
|
|
1168
|
+
{
|
|
1169
|
+
kind: "step",
|
|
1170
|
+
where: {
|
|
1171
|
+
NOT: KIND_PREFIXES.map(([, prefix]) => ({
|
|
1172
|
+
name: {
|
|
1173
|
+
startsWith: prefix
|
|
1174
|
+
}
|
|
1175
|
+
}))
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
];
|
|
1179
|
+
function bucketKey(group, status) {
|
|
1180
|
+
return JSON.stringify([
|
|
1181
|
+
group,
|
|
1182
|
+
status
|
|
1183
|
+
]);
|
|
1184
|
+
}
|
|
1185
|
+
__name(bucketKey, "bucketKey");
|
|
1186
|
+
function toCheckpointStat(nameKind, r, unenqueued) {
|
|
1187
|
+
const fallback = unenqueued.get(bucketKey(r.parallelGroup, r.status));
|
|
1188
|
+
const enqueued = r._min.enqueuedAt;
|
|
1189
|
+
const minEnqueuedAt = enqueued && fallback ? enqueued < fallback ? enqueued : fallback : enqueued ?? fallback;
|
|
1190
|
+
return {
|
|
1191
|
+
group: r.parallelGroup,
|
|
1192
|
+
status: r.status,
|
|
1193
|
+
nameKind,
|
|
1194
|
+
count: r._count._all,
|
|
1195
|
+
minSeq: r._min.seq,
|
|
1196
|
+
maxSeq: r._max.seq,
|
|
1197
|
+
minName: r._min.name,
|
|
1198
|
+
maxName: r._max.name,
|
|
1199
|
+
// Every row has `enqueuedAt` or is in the NULL subset, so one of the two is set.
|
|
1200
|
+
minEnqueuedAt,
|
|
1201
|
+
minStartedAt: r._min.startedAt,
|
|
1202
|
+
maxStartedAt: r._max.startedAt,
|
|
1203
|
+
maxFinishedAt: r._max.finishedAt,
|
|
1204
|
+
sumAttempts: Number(r._sum.attempts ?? 0)
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
__name(toCheckpointStat, "toCheckpointStat");
|
|
950
1208
|
function toCheckpointData(cp) {
|
|
951
1209
|
return {
|
|
952
1210
|
runId: cp.runId,
|