@dudousxd/nestjs-durable-store-prisma 0.20.0 → 0.21.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 +214 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +214 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/prisma/schema.prisma +2 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, SignalWaiter, ChildWait } from '@dudousxd/nestjs-durable-core';
|
|
1
|
+
import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, CheckpointStat, CheckpointQuery, SignalWaiter, ChildWait } from '@dudousxd/nestjs-durable-core';
|
|
2
2
|
|
|
3
3
|
interface RunRow {
|
|
4
4
|
id: string;
|
|
@@ -83,8 +83,8 @@ interface Delegate<Row> {
|
|
|
83
83
|
count: number;
|
|
84
84
|
}>;
|
|
85
85
|
count(args?: Args): Promise<number>;
|
|
86
|
-
/** Aggregate form — used
|
|
87
|
-
* model rows, so the result type is stated at the call site rather than derived from `Row`. */
|
|
86
|
+
/** Aggregate form — used by the run facets and `checkpointStats`, whose rows are aggregate cells,
|
|
87
|
+
* not model rows, so the result type is stated at the call site rather than derived from `Row`. */
|
|
88
88
|
groupBy(args: Args): Promise<Record<string, unknown>[]>;
|
|
89
89
|
}
|
|
90
90
|
interface ScheduleRow {
|
|
@@ -139,6 +139,7 @@ declare class PrismaStateStore implements StateStore {
|
|
|
139
139
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
140
140
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
141
141
|
saveCheckpointIfPending(checkpoint: StepCheckpoint): Promise<boolean>;
|
|
142
|
+
saveCheckpointsIfPending(checkpoints: StepCheckpoint[]): Promise<boolean[]>;
|
|
142
143
|
transaction<T>(work: (tx: {
|
|
143
144
|
raw: unknown;
|
|
144
145
|
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
@@ -173,6 +174,25 @@ declare class PrismaStateStore implements StateStore {
|
|
|
173
174
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
174
175
|
countCheckpoints(runId: string, fromSeq: number, toSeq: number, statuses: StepCheckpoint['status'][]): Promise<number>;
|
|
175
176
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
177
|
+
/**
|
|
178
|
+
* One `groupBy(parallelGroup, status)` per {@link CheckpointNameKind}, each narrowed to its kind by
|
|
179
|
+
* a name-prefix predicate, run concurrently — Prisma cannot group by a `CASE`, so the kind is the
|
|
180
|
+
* `where` instead. `minEnqueuedAt` reads a NULL `enqueuedAt` as `startedAt` (as
|
|
181
|
+
* {@link fromCheckpointRow} does): a bucket whose non-null `enqueuedAt` count falls short of its
|
|
182
|
+
* row count has such rows, and only for those kinds is the NULL subset's `min(startedAt)` asked for.
|
|
183
|
+
*
|
|
184
|
+
* On SQLite `startsWith` is a case-insensitive `LIKE`, as in {@link listCheckpointsByNamePrefix};
|
|
185
|
+
* the kind prefixes are lower-case and wildcard-free, so only a step whose name starts with one of
|
|
186
|
+
* them in another case would be classified differently there.
|
|
187
|
+
*/
|
|
188
|
+
checkpointStats(runId: string, opts?: {
|
|
189
|
+
group?: string | undefined;
|
|
190
|
+
}): Promise<CheckpointStat[]>;
|
|
191
|
+
/** `min(startedAt)` per `(parallelGroup, status)` over the rows whose `enqueuedAt` is NULL. */
|
|
192
|
+
private unenqueuedStartedAt;
|
|
193
|
+
/** {@link CheckpointQuery} as one `findMany`, in `seq` order, paged by `skip`/`take`. Ungrouped rows
|
|
194
|
+
* are kept past `excludeGroups` explicitly: `NOT IN` alone drops a NULL `parallelGroup`. */
|
|
195
|
+
queryCheckpoints(runId: string, query: CheckpointQuery): Promise<StepCheckpoint[]>;
|
|
176
196
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
177
197
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
178
198
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, SignalWaiter, ChildWait } from '@dudousxd/nestjs-durable-core';
|
|
1
|
+
import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, CheckpointStat, CheckpointQuery, SignalWaiter, ChildWait } from '@dudousxd/nestjs-durable-core';
|
|
2
2
|
|
|
3
3
|
interface RunRow {
|
|
4
4
|
id: string;
|
|
@@ -83,8 +83,8 @@ interface Delegate<Row> {
|
|
|
83
83
|
count: number;
|
|
84
84
|
}>;
|
|
85
85
|
count(args?: Args): Promise<number>;
|
|
86
|
-
/** Aggregate form — used
|
|
87
|
-
* model rows, so the result type is stated at the call site rather than derived from `Row`. */
|
|
86
|
+
/** Aggregate form — used by the run facets and `checkpointStats`, whose rows are aggregate cells,
|
|
87
|
+
* not model rows, so the result type is stated at the call site rather than derived from `Row`. */
|
|
88
88
|
groupBy(args: Args): Promise<Record<string, unknown>[]>;
|
|
89
89
|
}
|
|
90
90
|
interface ScheduleRow {
|
|
@@ -139,6 +139,7 @@ declare class PrismaStateStore implements StateStore {
|
|
|
139
139
|
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
140
140
|
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
141
141
|
saveCheckpointIfPending(checkpoint: StepCheckpoint): Promise<boolean>;
|
|
142
|
+
saveCheckpointsIfPending(checkpoints: StepCheckpoint[]): Promise<boolean[]>;
|
|
142
143
|
transaction<T>(work: (tx: {
|
|
143
144
|
raw: unknown;
|
|
144
145
|
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
@@ -173,6 +174,25 @@ declare class PrismaStateStore implements StateStore {
|
|
|
173
174
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
174
175
|
countCheckpoints(runId: string, fromSeq: number, toSeq: number, statuses: StepCheckpoint['status'][]): Promise<number>;
|
|
175
176
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
177
|
+
/**
|
|
178
|
+
* One `groupBy(parallelGroup, status)` per {@link CheckpointNameKind}, each narrowed to its kind by
|
|
179
|
+
* a name-prefix predicate, run concurrently — Prisma cannot group by a `CASE`, so the kind is the
|
|
180
|
+
* `where` instead. `minEnqueuedAt` reads a NULL `enqueuedAt` as `startedAt` (as
|
|
181
|
+
* {@link fromCheckpointRow} does): a bucket whose non-null `enqueuedAt` count falls short of its
|
|
182
|
+
* row count has such rows, and only for those kinds is the NULL subset's `min(startedAt)` asked for.
|
|
183
|
+
*
|
|
184
|
+
* On SQLite `startsWith` is a case-insensitive `LIKE`, as in {@link listCheckpointsByNamePrefix};
|
|
185
|
+
* the kind prefixes are lower-case and wildcard-free, so only a step whose name starts with one of
|
|
186
|
+
* them in another case would be classified differently there.
|
|
187
|
+
*/
|
|
188
|
+
checkpointStats(runId: string, opts?: {
|
|
189
|
+
group?: string | undefined;
|
|
190
|
+
}): Promise<CheckpointStat[]>;
|
|
191
|
+
/** `min(startedAt)` per `(parallelGroup, status)` over the rows whose `enqueuedAt` is NULL. */
|
|
192
|
+
private unenqueuedStartedAt;
|
|
193
|
+
/** {@link CheckpointQuery} as one `findMany`, in `seq` order, paged by `skip`/`take`. Ungrouped rows
|
|
194
|
+
* are kept past `excludeGroups` explicitly: `NOT IN` alone drops a NULL `parallelGroup`. */
|
|
195
|
+
queryCheckpoints(runId: string, query: CheckpointQuery): Promise<StepCheckpoint[]>;
|
|
176
196
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
177
197
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
178
198
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
package/dist/index.js
CHANGED
|
@@ -246,6 +246,25 @@ var PrismaStateStore = class {
|
|
|
246
246
|
});
|
|
247
247
|
return count > 0;
|
|
248
248
|
}
|
|
249
|
+
async saveCheckpointsIfPending(checkpoints) {
|
|
250
|
+
if (checkpoints.length === 0) return [];
|
|
251
|
+
return this.db.$transaction(async (tx) => {
|
|
252
|
+
const wrote = [];
|
|
253
|
+
for (const checkpoint of checkpoints) {
|
|
254
|
+
const { runId, seq, ...data } = toCheckpointData(checkpoint);
|
|
255
|
+
const { count } = await tx.durableStepCheckpoint.updateMany({
|
|
256
|
+
where: {
|
|
257
|
+
runId,
|
|
258
|
+
seq,
|
|
259
|
+
status: "pending"
|
|
260
|
+
},
|
|
261
|
+
data
|
|
262
|
+
});
|
|
263
|
+
wrote.push(count > 0);
|
|
264
|
+
}
|
|
265
|
+
return wrote;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
249
268
|
async transaction(work) {
|
|
250
269
|
return this.db.$transaction(async (tx) => work({
|
|
251
270
|
raw: tx,
|
|
@@ -610,6 +629,137 @@ var PrismaStateStore = class {
|
|
|
610
629
|
seq: "asc"
|
|
611
630
|
}
|
|
612
631
|
});
|
|
632
|
+
return rows.filter((r) => prefixes.some((p) => r.name.startsWith(p))).map(fromCheckpointRow);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* One `groupBy(parallelGroup, status)` per {@link CheckpointNameKind}, each narrowed to its kind by
|
|
636
|
+
* a name-prefix predicate, run concurrently — Prisma cannot group by a `CASE`, so the kind is the
|
|
637
|
+
* `where` instead. `minEnqueuedAt` reads a NULL `enqueuedAt` as `startedAt` (as
|
|
638
|
+
* {@link fromCheckpointRow} does): a bucket whose non-null `enqueuedAt` count falls short of its
|
|
639
|
+
* row count has such rows, and only for those kinds is the NULL subset's `min(startedAt)` asked for.
|
|
640
|
+
*
|
|
641
|
+
* On SQLite `startsWith` is a case-insensitive `LIKE`, as in {@link listCheckpointsByNamePrefix};
|
|
642
|
+
* the kind prefixes are lower-case and wildcard-free, so only a step whose name starts with one of
|
|
643
|
+
* them in another case would be classified differently there.
|
|
644
|
+
*/
|
|
645
|
+
async checkpointStats(runId, opts) {
|
|
646
|
+
const scope = opts?.group === void 0 ? {
|
|
647
|
+
runId
|
|
648
|
+
} : {
|
|
649
|
+
runId,
|
|
650
|
+
parallelGroup: opts.group
|
|
651
|
+
};
|
|
652
|
+
const grouped = await Promise.all(NAME_KINDS.map(async ({ kind, where }) => ({
|
|
653
|
+
kind,
|
|
654
|
+
where,
|
|
655
|
+
rows: await this.db.durableStepCheckpoint.groupBy({
|
|
656
|
+
by: [
|
|
657
|
+
"parallelGroup",
|
|
658
|
+
"status"
|
|
659
|
+
],
|
|
660
|
+
where: {
|
|
661
|
+
...scope,
|
|
662
|
+
...where
|
|
663
|
+
},
|
|
664
|
+
_count: {
|
|
665
|
+
_all: true,
|
|
666
|
+
enqueuedAt: true
|
|
667
|
+
},
|
|
668
|
+
_min: {
|
|
669
|
+
seq: true,
|
|
670
|
+
name: true,
|
|
671
|
+
enqueuedAt: true,
|
|
672
|
+
startedAt: true
|
|
673
|
+
},
|
|
674
|
+
_max: {
|
|
675
|
+
seq: true,
|
|
676
|
+
name: true,
|
|
677
|
+
startedAt: true,
|
|
678
|
+
finishedAt: true
|
|
679
|
+
},
|
|
680
|
+
_sum: {
|
|
681
|
+
attempts: true
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
})));
|
|
685
|
+
const stats = await Promise.all(grouped.map(async ({ kind, where, rows }) => {
|
|
686
|
+
const unenqueued = rows.some((r) => r._count.enqueuedAt < r._count._all) ? await this.unenqueuedStartedAt({
|
|
687
|
+
...scope,
|
|
688
|
+
...where
|
|
689
|
+
}) : /* @__PURE__ */ new Map();
|
|
690
|
+
return rows.map((r) => toCheckpointStat(kind, r, unenqueued));
|
|
691
|
+
}));
|
|
692
|
+
return stats.flat();
|
|
693
|
+
}
|
|
694
|
+
/** `min(startedAt)` per `(parallelGroup, status)` over the rows whose `enqueuedAt` is NULL. */
|
|
695
|
+
async unenqueuedStartedAt(where) {
|
|
696
|
+
const rows = await this.db.durableStepCheckpoint.groupBy({
|
|
697
|
+
by: [
|
|
698
|
+
"parallelGroup",
|
|
699
|
+
"status"
|
|
700
|
+
],
|
|
701
|
+
where: {
|
|
702
|
+
...where,
|
|
703
|
+
enqueuedAt: null
|
|
704
|
+
},
|
|
705
|
+
_min: {
|
|
706
|
+
startedAt: true
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
return new Map(rows.map((r) => [
|
|
710
|
+
bucketKey(r.parallelGroup, r.status),
|
|
711
|
+
r._min.startedAt
|
|
712
|
+
]));
|
|
713
|
+
}
|
|
714
|
+
/** {@link CheckpointQuery} as one `findMany`, in `seq` order, paged by `skip`/`take`. Ungrouped rows
|
|
715
|
+
* are kept past `excludeGroups` explicitly: `NOT IN` alone drops a NULL `parallelGroup`. */
|
|
716
|
+
async queryCheckpoints(runId, query) {
|
|
717
|
+
if (query.statuses?.length === 0 || query.namePrefixes?.length === 0) return [];
|
|
718
|
+
const and = [];
|
|
719
|
+
if (query.excludeGroups?.length) {
|
|
720
|
+
and.push({
|
|
721
|
+
OR: [
|
|
722
|
+
{
|
|
723
|
+
parallelGroup: null
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
parallelGroup: {
|
|
727
|
+
notIn: query.excludeGroups
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
]
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (query.namePrefixes) {
|
|
734
|
+
and.push({
|
|
735
|
+
OR: query.namePrefixes.map((p) => ({
|
|
736
|
+
name: {
|
|
737
|
+
startsWith: p
|
|
738
|
+
}
|
|
739
|
+
}))
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const rows = await this.db.durableStepCheckpoint.findMany({
|
|
743
|
+
where: {
|
|
744
|
+
runId,
|
|
745
|
+
...query.group === void 0 ? {} : {
|
|
746
|
+
parallelGroup: query.group
|
|
747
|
+
},
|
|
748
|
+
...query.statuses ? {
|
|
749
|
+
status: {
|
|
750
|
+
in: query.statuses
|
|
751
|
+
}
|
|
752
|
+
} : {},
|
|
753
|
+
...and.length ? {
|
|
754
|
+
AND: and
|
|
755
|
+
} : {}
|
|
756
|
+
},
|
|
757
|
+
orderBy: {
|
|
758
|
+
seq: "asc"
|
|
759
|
+
},
|
|
760
|
+
skip: query.offset,
|
|
761
|
+
take: query.limit
|
|
762
|
+
});
|
|
613
763
|
return rows.map(fromCheckpointRow);
|
|
614
764
|
}
|
|
615
765
|
async putSignalWaiter(waiter) {
|
|
@@ -657,7 +807,7 @@ var PrismaStateStore = class {
|
|
|
657
807
|
}
|
|
658
808
|
}
|
|
659
809
|
});
|
|
660
|
-
return rows.map((r) => ({
|
|
810
|
+
return rows.filter((r) => r.token.startsWith(prefix)).map((r) => ({
|
|
661
811
|
token: r.token,
|
|
662
812
|
runId: r.runId,
|
|
663
813
|
seq: r.seq,
|
|
@@ -947,6 +1097,69 @@ function fromRunRow(row) {
|
|
|
947
1097
|
};
|
|
948
1098
|
}
|
|
949
1099
|
__name(fromRunRow, "fromRunRow");
|
|
1100
|
+
var KIND_PREFIXES = [
|
|
1101
|
+
[
|
|
1102
|
+
"child",
|
|
1103
|
+
"signal:child:"
|
|
1104
|
+
],
|
|
1105
|
+
[
|
|
1106
|
+
"folded",
|
|
1107
|
+
"fan:"
|
|
1108
|
+
],
|
|
1109
|
+
[
|
|
1110
|
+
"spawn",
|
|
1111
|
+
"spawn:"
|
|
1112
|
+
]
|
|
1113
|
+
];
|
|
1114
|
+
var NAME_KINDS = [
|
|
1115
|
+
...KIND_PREFIXES.map(([kind, prefix]) => ({
|
|
1116
|
+
kind,
|
|
1117
|
+
where: {
|
|
1118
|
+
name: {
|
|
1119
|
+
startsWith: prefix
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
})),
|
|
1123
|
+
{
|
|
1124
|
+
kind: "step",
|
|
1125
|
+
where: {
|
|
1126
|
+
NOT: KIND_PREFIXES.map(([, prefix]) => ({
|
|
1127
|
+
name: {
|
|
1128
|
+
startsWith: prefix
|
|
1129
|
+
}
|
|
1130
|
+
}))
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
];
|
|
1134
|
+
function bucketKey(group, status) {
|
|
1135
|
+
return JSON.stringify([
|
|
1136
|
+
group,
|
|
1137
|
+
status
|
|
1138
|
+
]);
|
|
1139
|
+
}
|
|
1140
|
+
__name(bucketKey, "bucketKey");
|
|
1141
|
+
function toCheckpointStat(nameKind, r, unenqueued) {
|
|
1142
|
+
const fallback = unenqueued.get(bucketKey(r.parallelGroup, r.status));
|
|
1143
|
+
const enqueued = r._min.enqueuedAt;
|
|
1144
|
+
const minEnqueuedAt = enqueued && fallback ? enqueued < fallback ? enqueued : fallback : enqueued ?? fallback;
|
|
1145
|
+
return {
|
|
1146
|
+
group: r.parallelGroup,
|
|
1147
|
+
status: r.status,
|
|
1148
|
+
nameKind,
|
|
1149
|
+
count: r._count._all,
|
|
1150
|
+
minSeq: r._min.seq,
|
|
1151
|
+
maxSeq: r._max.seq,
|
|
1152
|
+
minName: r._min.name,
|
|
1153
|
+
maxName: r._max.name,
|
|
1154
|
+
// Every row has `enqueuedAt` or is in the NULL subset, so one of the two is set.
|
|
1155
|
+
minEnqueuedAt,
|
|
1156
|
+
minStartedAt: r._min.startedAt,
|
|
1157
|
+
maxStartedAt: r._max.startedAt,
|
|
1158
|
+
maxFinishedAt: r._max.finishedAt,
|
|
1159
|
+
sumAttempts: Number(r._sum.attempts ?? 0)
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
__name(toCheckpointStat, "toCheckpointStat");
|
|
950
1163
|
function toCheckpointData(cp) {
|
|
951
1164
|
return {
|
|
952
1165
|
runId: cp.runId,
|