@dudousxd/nestjs-durable-core 0.30.0 → 0.32.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 +218 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -114
- package/dist/index.d.ts +233 -114
- package/dist/index.js +214 -143
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -25,9 +25,11 @@ __export(index_exports, {
|
|
|
25
25
|
CodecStateStore: () => CodecStateStore,
|
|
26
26
|
ContinueAsNew: () => ContinueAsNew,
|
|
27
27
|
DURABLE_OPTIONS: () => DURABLE_OPTIONS,
|
|
28
|
+
DURABLE_OPTIONS_CANONICAL: () => DURABLE_OPTIONS_CANONICAL,
|
|
28
29
|
Entities: () => Entities,
|
|
29
30
|
EventAccumulators: () => EventAccumulators,
|
|
30
31
|
FatalError: () => FatalError,
|
|
32
|
+
InMemoryAdmissionBackend: () => InMemoryAdmissionBackend,
|
|
31
33
|
InMemoryStateStore: () => InMemoryStateStore,
|
|
32
34
|
InMemoryTransport: () => InMemoryTransport,
|
|
33
35
|
NonDeterminismError: () => NonDeterminismError,
|
|
@@ -36,9 +38,11 @@ __export(index_exports, {
|
|
|
36
38
|
RemoteStepTimeout: () => RemoteStepTimeout,
|
|
37
39
|
RemoteWorkflowExecutor: () => RemoteWorkflowExecutor,
|
|
38
40
|
STATE_STORE: () => STATE_STORE,
|
|
41
|
+
STATE_STORE_CANONICAL: () => STATE_STORE_CANONICAL,
|
|
39
42
|
SignalTimeoutError: () => SignalTimeoutError,
|
|
40
43
|
SingletonQueueFullError: () => SingletonQueueFullError,
|
|
41
44
|
TRANSPORT: () => TRANSPORT,
|
|
45
|
+
TRANSPORT_CANONICAL: () => TRANSPORT_CANONICAL,
|
|
42
46
|
WORKFLOW_NAME_KEY: () => WORKFLOW_NAME_KEY,
|
|
43
47
|
WorkflowEngine: () => WorkflowEngine,
|
|
44
48
|
WorkflowSuspended: () => WorkflowSuspended,
|
|
@@ -65,6 +69,161 @@ __export(index_exports, {
|
|
|
65
69
|
});
|
|
66
70
|
module.exports = __toCommonJS(index_exports);
|
|
67
71
|
|
|
72
|
+
// src/queue.ts
|
|
73
|
+
var QueueController = class {
|
|
74
|
+
static {
|
|
75
|
+
__name(this, "QueueController");
|
|
76
|
+
}
|
|
77
|
+
config;
|
|
78
|
+
clock;
|
|
79
|
+
inFlight = 0;
|
|
80
|
+
windowStart = 0;
|
|
81
|
+
windowCount = 0;
|
|
82
|
+
/** Registered waiters by id (only used when priority/fairness ordering is in play). */
|
|
83
|
+
waiters = /* @__PURE__ */ new Map();
|
|
84
|
+
/** Monotonic counter stamping each newly-seen waiter for a stable FIFO tiebreak. */
|
|
85
|
+
waiterSeq = 0;
|
|
86
|
+
/** Per-key "last served" order for round-robin fairness — higher = more recently served. */
|
|
87
|
+
keyServed = /* @__PURE__ */ new Map();
|
|
88
|
+
servedTick = 0;
|
|
89
|
+
constructor(config, clock) {
|
|
90
|
+
this.config = config;
|
|
91
|
+
this.clock = clock;
|
|
92
|
+
}
|
|
93
|
+
/** Whether this controller does any priority/fairness ordering (else it's a plain FIFO gate). */
|
|
94
|
+
get ordered() {
|
|
95
|
+
return this.config.fairness === "key" || this.config.order === "lifo";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Try to admit one unit of work. On `ok`, the caller has taken a concurrency slot and must call
|
|
99
|
+
* {@link release} when the step settles. On block, `retryAt` is when admission may next succeed.
|
|
100
|
+
*
|
|
101
|
+
* `item` carries optional priority/fairness hints; omitting it is the legacy FIFO path. When
|
|
102
|
+
* ordering is configured (or a priority is given), a blocked call REGISTERS as a waiter and is only
|
|
103
|
+
* admitted on a later retry once it is the rightful next under (priority desc, fairness, FIFO).
|
|
104
|
+
*/
|
|
105
|
+
tryAdmit(item) {
|
|
106
|
+
const now = this.clock();
|
|
107
|
+
const rl = this.config.rateLimit;
|
|
108
|
+
if (rl) {
|
|
109
|
+
if (now - this.windowStart >= rl.periodMs) {
|
|
110
|
+
this.windowStart = now;
|
|
111
|
+
this.windowCount = 0;
|
|
112
|
+
}
|
|
113
|
+
if (this.windowCount >= rl.limit) return {
|
|
114
|
+
ok: false,
|
|
115
|
+
retryAt: this.windowStart + rl.periodMs
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const usingOrder = this.ordered || item?.priority != null || item?.waiterId != null;
|
|
119
|
+
const waiterId = item?.waiterId;
|
|
120
|
+
const full = this.config.concurrency != null && this.inFlight >= this.config.concurrency;
|
|
121
|
+
if (full) {
|
|
122
|
+
if (usingOrder && waiterId != null) this.register(waiterId, item);
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (usingOrder && this.waiters.size > 0) {
|
|
129
|
+
if (waiterId != null) this.register(waiterId, item);
|
|
130
|
+
if (!this.isNext(waiterId, item)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (rl) this.windowCount += 1;
|
|
138
|
+
this.inFlight += 1;
|
|
139
|
+
if (waiterId != null) this.waiters.delete(waiterId);
|
|
140
|
+
if (item?.key != null) this.keyServed.set(item.key, ++this.servedTick);
|
|
141
|
+
return {
|
|
142
|
+
ok: true
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Release a concurrency slot taken by a successful {@link tryAdmit}. */
|
|
146
|
+
release() {
|
|
147
|
+
if (this.inFlight > 0) this.inFlight -= 1;
|
|
148
|
+
}
|
|
149
|
+
/** Track (or refresh) a blocked call as a waiter; its FIFO seq is stamped only on first sight. */
|
|
150
|
+
register(waiterId, item) {
|
|
151
|
+
const existing = this.waiters.get(waiterId);
|
|
152
|
+
if (existing) {
|
|
153
|
+
existing.priority = item?.priority ?? 0;
|
|
154
|
+
existing.key = item?.key;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.waiters.set(waiterId, {
|
|
158
|
+
priority: item?.priority ?? 0,
|
|
159
|
+
key: item?.key,
|
|
160
|
+
seq: this.waiterSeq++
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Is the calling item the best candidate among all registered waiters? Ordering, best first:
|
|
165
|
+
* 1. higher `priority`;
|
|
166
|
+
* 2. fairness: with `fairness: 'key'`, the least-recently-served key wins (round-robin);
|
|
167
|
+
* 3. earliest registration (FIFO) — stable tiebreak.
|
|
168
|
+
* A bare item (no waiterId) is compared on its hints directly.
|
|
169
|
+
*/
|
|
170
|
+
isNext(waiterId, item) {
|
|
171
|
+
const self = (waiterId != null ? this.waiters.get(waiterId) : void 0) ?? {
|
|
172
|
+
priority: item?.priority ?? 0,
|
|
173
|
+
key: item?.key,
|
|
174
|
+
seq: Number.POSITIVE_INFINITY
|
|
175
|
+
};
|
|
176
|
+
let best = self;
|
|
177
|
+
for (const w of this.waiters.values()) {
|
|
178
|
+
if (this.better(w, best)) best = w;
|
|
179
|
+
}
|
|
180
|
+
return best === self;
|
|
181
|
+
}
|
|
182
|
+
/** Strict "a should be admitted before b" comparison under the active policy. */
|
|
183
|
+
better(a, b) {
|
|
184
|
+
if (a.priority !== b.priority) return a.priority > b.priority;
|
|
185
|
+
if (this.config.fairness === "key") {
|
|
186
|
+
const sa = this.servedOf(a.key);
|
|
187
|
+
const sb = this.servedOf(b.key);
|
|
188
|
+
if (sa !== sb) return sa < sb;
|
|
189
|
+
}
|
|
190
|
+
return this.config.order === "lifo" ? a.seq > b.seq : a.seq < b.seq;
|
|
191
|
+
}
|
|
192
|
+
/** Last-served tick for a key (0 = never served, so an unseen key is admitted before a busy one). */
|
|
193
|
+
servedOf(key) {
|
|
194
|
+
if (key == null) return 0;
|
|
195
|
+
return this.keyServed.get(key) ?? 0;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/admission.ts
|
|
200
|
+
var InMemoryAdmissionBackend = class {
|
|
201
|
+
static {
|
|
202
|
+
__name(this, "InMemoryAdmissionBackend");
|
|
203
|
+
}
|
|
204
|
+
clock;
|
|
205
|
+
controllers = /* @__PURE__ */ new Map();
|
|
206
|
+
constructor(clock) {
|
|
207
|
+
this.clock = clock;
|
|
208
|
+
}
|
|
209
|
+
register(config) {
|
|
210
|
+
this.controllers.set(config.name, new QueueController(config, this.clock));
|
|
211
|
+
}
|
|
212
|
+
handles(queue) {
|
|
213
|
+
return this.controllers.has(queue);
|
|
214
|
+
}
|
|
215
|
+
async tryAdmit(queue, item) {
|
|
216
|
+
const controller = this.controllers.get(queue);
|
|
217
|
+
if (!controller) return {
|
|
218
|
+
ok: true
|
|
219
|
+
};
|
|
220
|
+
return controller.tryAdmit(item);
|
|
221
|
+
}
|
|
222
|
+
async release(queue, _slotId) {
|
|
223
|
+
this.controllers.get(queue)?.release();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
68
227
|
// src/duration.ts
|
|
69
228
|
var UNIT_MS = {
|
|
70
229
|
ms: 1,
|
|
@@ -581,133 +740,6 @@ function eventMatches(payload, match) {
|
|
|
581
740
|
}
|
|
582
741
|
__name(eventMatches, "eventMatches");
|
|
583
742
|
|
|
584
|
-
// src/queue.ts
|
|
585
|
-
var QueueController = class {
|
|
586
|
-
static {
|
|
587
|
-
__name(this, "QueueController");
|
|
588
|
-
}
|
|
589
|
-
config;
|
|
590
|
-
clock;
|
|
591
|
-
inFlight = 0;
|
|
592
|
-
windowStart = 0;
|
|
593
|
-
windowCount = 0;
|
|
594
|
-
/** Registered waiters by id (only used when priority/fairness ordering is in play). */
|
|
595
|
-
waiters = /* @__PURE__ */ new Map();
|
|
596
|
-
/** Monotonic counter stamping each newly-seen waiter for a stable FIFO tiebreak. */
|
|
597
|
-
waiterSeq = 0;
|
|
598
|
-
/** Per-key "last served" order for round-robin fairness — higher = more recently served. */
|
|
599
|
-
keyServed = /* @__PURE__ */ new Map();
|
|
600
|
-
servedTick = 0;
|
|
601
|
-
constructor(config, clock) {
|
|
602
|
-
this.config = config;
|
|
603
|
-
this.clock = clock;
|
|
604
|
-
}
|
|
605
|
-
/** Whether this controller does any priority/fairness ordering (else it's a plain FIFO gate). */
|
|
606
|
-
get ordered() {
|
|
607
|
-
return this.config.fairness === "key";
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Try to admit one unit of work. On `ok`, the caller has taken a concurrency slot and must call
|
|
611
|
-
* {@link release} when the step settles. On block, `retryAt` is when admission may next succeed.
|
|
612
|
-
*
|
|
613
|
-
* `item` carries optional priority/fairness hints; omitting it is the legacy FIFO path. When
|
|
614
|
-
* ordering is configured (or a priority is given), a blocked call REGISTERS as a waiter and is only
|
|
615
|
-
* admitted on a later retry once it is the rightful next under (priority desc, fairness, FIFO).
|
|
616
|
-
*/
|
|
617
|
-
tryAdmit(item) {
|
|
618
|
-
const now = this.clock();
|
|
619
|
-
const rl = this.config.rateLimit;
|
|
620
|
-
if (rl) {
|
|
621
|
-
if (now - this.windowStart >= rl.periodMs) {
|
|
622
|
-
this.windowStart = now;
|
|
623
|
-
this.windowCount = 0;
|
|
624
|
-
}
|
|
625
|
-
if (this.windowCount >= rl.limit) return {
|
|
626
|
-
ok: false,
|
|
627
|
-
retryAt: this.windowStart + rl.periodMs
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
const usingOrder = this.ordered || item?.priority != null || item?.waiterId != null;
|
|
631
|
-
const waiterId = item?.waiterId;
|
|
632
|
-
const full = this.config.concurrency != null && this.inFlight >= this.config.concurrency;
|
|
633
|
-
if (full) {
|
|
634
|
-
if (usingOrder && waiterId != null) this.register(waiterId, item);
|
|
635
|
-
return {
|
|
636
|
-
ok: false,
|
|
637
|
-
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
if (usingOrder && this.waiters.size > 0) {
|
|
641
|
-
if (waiterId != null) this.register(waiterId, item);
|
|
642
|
-
if (!this.isNext(waiterId, item)) {
|
|
643
|
-
return {
|
|
644
|
-
ok: false,
|
|
645
|
-
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (rl) this.windowCount += 1;
|
|
650
|
-
this.inFlight += 1;
|
|
651
|
-
if (waiterId != null) this.waiters.delete(waiterId);
|
|
652
|
-
if (item?.key != null) this.keyServed.set(item.key, ++this.servedTick);
|
|
653
|
-
return {
|
|
654
|
-
ok: true
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
/** Release a concurrency slot taken by a successful {@link tryAdmit}. */
|
|
658
|
-
release() {
|
|
659
|
-
if (this.inFlight > 0) this.inFlight -= 1;
|
|
660
|
-
}
|
|
661
|
-
/** Track (or refresh) a blocked call as a waiter; its FIFO seq is stamped only on first sight. */
|
|
662
|
-
register(waiterId, item) {
|
|
663
|
-
const existing = this.waiters.get(waiterId);
|
|
664
|
-
if (existing) {
|
|
665
|
-
existing.priority = item?.priority ?? 0;
|
|
666
|
-
existing.key = item?.key;
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
this.waiters.set(waiterId, {
|
|
670
|
-
priority: item?.priority ?? 0,
|
|
671
|
-
key: item?.key,
|
|
672
|
-
seq: this.waiterSeq++
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Is the calling item the best candidate among all registered waiters? Ordering, best first:
|
|
677
|
-
* 1. higher `priority`;
|
|
678
|
-
* 2. fairness: with `fairness: 'key'`, the least-recently-served key wins (round-robin);
|
|
679
|
-
* 3. earliest registration (FIFO) — stable tiebreak.
|
|
680
|
-
* A bare item (no waiterId) is compared on its hints directly.
|
|
681
|
-
*/
|
|
682
|
-
isNext(waiterId, item) {
|
|
683
|
-
const self = (waiterId != null ? this.waiters.get(waiterId) : void 0) ?? {
|
|
684
|
-
priority: item?.priority ?? 0,
|
|
685
|
-
key: item?.key,
|
|
686
|
-
seq: Number.POSITIVE_INFINITY
|
|
687
|
-
};
|
|
688
|
-
let best = self;
|
|
689
|
-
for (const w of this.waiters.values()) {
|
|
690
|
-
if (this.better(w, best)) best = w;
|
|
691
|
-
}
|
|
692
|
-
return best === self;
|
|
693
|
-
}
|
|
694
|
-
/** Strict "a should be admitted before b" comparison under the active policy. */
|
|
695
|
-
better(a, b) {
|
|
696
|
-
if (a.priority !== b.priority) return a.priority > b.priority;
|
|
697
|
-
if (this.config.fairness === "key") {
|
|
698
|
-
const sa = this.servedOf(a.key);
|
|
699
|
-
const sb = this.servedOf(b.key);
|
|
700
|
-
if (sa !== sb) return sa < sb;
|
|
701
|
-
}
|
|
702
|
-
return a.seq < b.seq;
|
|
703
|
-
}
|
|
704
|
-
/** Last-served tick for a key (0 = never served, so an unseen key is admitted before a busy one). */
|
|
705
|
-
servedOf(key) {
|
|
706
|
-
if (key == null) return 0;
|
|
707
|
-
return this.keyServed.get(key) ?? 0;
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
|
|
711
743
|
// src/singleton-gate.ts
|
|
712
744
|
var SINGLETON_RETRY_MS = 1e3;
|
|
713
745
|
var SINGLETON_RETRY_JITTER_MS = 250;
|
|
@@ -901,6 +933,12 @@ function workflowName(ref) {
|
|
|
901
933
|
__name(workflowName, "workflowName");
|
|
902
934
|
|
|
903
935
|
// src/workflow-ctx.ts
|
|
936
|
+
function normalizeChildOptions(options) {
|
|
937
|
+
return typeof options === "string" ? {
|
|
938
|
+
childId: options
|
|
939
|
+
} : options ?? {};
|
|
940
|
+
}
|
|
941
|
+
__name(normalizeChildOptions, "normalizeChildOptions");
|
|
904
942
|
var Position = class Position2 {
|
|
905
943
|
static {
|
|
906
944
|
__name(this, "Position");
|
|
@@ -1150,7 +1188,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1150
1188
|
await step(`task:dispatch:${name}`, dispatch, options);
|
|
1151
1189
|
return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
|
|
1152
1190
|
}, "task");
|
|
1153
|
-
const child = /* @__PURE__ */ __name(async (workflow2, input,
|
|
1191
|
+
const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
1192
|
+
const { childId, priority } = normalizeChildOptions(options);
|
|
1154
1193
|
const current = pos.next();
|
|
1155
1194
|
const id = childId ?? `${runId}.child.${current}`;
|
|
1156
1195
|
const existing = await readCheckpoint(current);
|
|
@@ -1162,7 +1201,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1162
1201
|
runId,
|
|
1163
1202
|
seq: current
|
|
1164
1203
|
});
|
|
1165
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id);
|
|
1204
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority);
|
|
1166
1205
|
if (!existing) {
|
|
1167
1206
|
await writeCheckpoint(instantCheckpoint({
|
|
1168
1207
|
runId,
|
|
@@ -1174,12 +1213,13 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1174
1213
|
}
|
|
1175
1214
|
throw new WorkflowSuspended();
|
|
1176
1215
|
}, "child");
|
|
1177
|
-
const startChild = /* @__PURE__ */ __name(async (workflow2, input,
|
|
1216
|
+
const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
1217
|
+
const { childId, priority } = normalizeChildOptions(options);
|
|
1178
1218
|
const current = pos.next();
|
|
1179
1219
|
const id = childId ?? `${runId}.child.${current}`;
|
|
1180
1220
|
const existing = await readCheckpoint(current);
|
|
1181
1221
|
if (existing && existing.status === "completed") return existing.output;
|
|
1182
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id);
|
|
1222
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority);
|
|
1183
1223
|
await writeCheckpoint(instantCheckpoint({
|
|
1184
1224
|
runId,
|
|
1185
1225
|
seq: current,
|
|
@@ -1403,8 +1443,10 @@ var WorkflowEngine = class {
|
|
|
1403
1443
|
updateValidators = /* @__PURE__ */ new Map();
|
|
1404
1444
|
/** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
|
|
1405
1445
|
cancelRequested = /* @__PURE__ */ new Set();
|
|
1406
|
-
/** Flow-control
|
|
1407
|
-
|
|
1446
|
+
/** Flow-control admission backend for remote steps (see {@link registerQueue}). */
|
|
1447
|
+
admission;
|
|
1448
|
+
/** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
|
|
1449
|
+
queueWaiters = /* @__PURE__ */ new Map();
|
|
1408
1450
|
/** Which queue a dispatched step took a slot from, by stepId — so the result can release it. */
|
|
1409
1451
|
stepQueue = /* @__PURE__ */ new Map();
|
|
1410
1452
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
@@ -1420,6 +1462,8 @@ var WorkflowEngine = class {
|
|
|
1420
1462
|
] : []));
|
|
1421
1463
|
this.controlPlane = deps.controlPlane;
|
|
1422
1464
|
this.clock = deps.clock ?? Date.now;
|
|
1465
|
+
this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
|
|
1466
|
+
this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
|
|
1423
1467
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
1424
1468
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
1425
1469
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
@@ -1583,7 +1627,7 @@ var WorkflowEngine = class {
|
|
|
1583
1627
|
* durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
|
|
1584
1628
|
*/
|
|
1585
1629
|
registerQueue(config) {
|
|
1586
|
-
this.
|
|
1630
|
+
this.admission.register(config);
|
|
1587
1631
|
}
|
|
1588
1632
|
/** Subscribe to lifecycle events. Returns an unsubscribe function. */
|
|
1589
1633
|
subscribe(listener) {
|
|
@@ -1704,6 +1748,7 @@ var WorkflowEngine = class {
|
|
|
1704
1748
|
input,
|
|
1705
1749
|
tags,
|
|
1706
1750
|
searchAttributes: opts?.searchAttributes,
|
|
1751
|
+
priority: opts?.priority,
|
|
1707
1752
|
createdAt: now,
|
|
1708
1753
|
updatedAt: now
|
|
1709
1754
|
};
|
|
@@ -2930,8 +2975,10 @@ var WorkflowEngine = class {
|
|
|
2930
2975
|
failStep: /* @__PURE__ */ __name((s) => this.failStep(s), "failStep"),
|
|
2931
2976
|
callRemote: /* @__PURE__ */ __name((runId, seq, step, input, queue, transport, admission) => this.callRemote(runId, seq, step, input, queue, transport, replay, admission), "callRemote"),
|
|
2932
2977
|
// Defer so a fast child can't reentrantly resume a still-running parent.
|
|
2933
|
-
startChild: /* @__PURE__ */ __name((workflow, input, id) => {
|
|
2934
|
-
queueMicrotask(() => void this.start(workflow, input, id
|
|
2978
|
+
startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
|
|
2979
|
+
queueMicrotask(() => void this.start(workflow, input, id, {
|
|
2980
|
+
priority
|
|
2981
|
+
}).catch(() => void 0));
|
|
2935
2982
|
}, "startChild"),
|
|
2936
2983
|
signalEntity: /* @__PURE__ */ __name((name, key, op, arg, reply) => {
|
|
2937
2984
|
queueMicrotask(() => void this.entities.dispatch(name, key, op, arg, reply).catch(() => void 0));
|
|
@@ -3032,14 +3079,19 @@ var WorkflowEngine = class {
|
|
|
3032
3079
|
attempt = existing.attempts + 1;
|
|
3033
3080
|
}
|
|
3034
3081
|
const id = stepId(runId, seq);
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
const decision = controller.tryAdmit({
|
|
3082
|
+
if (queue && this.admission.handles(queue)) {
|
|
3083
|
+
const decision = await this.admission.tryAdmit(queue, {
|
|
3038
3084
|
priority: admission?.priority,
|
|
3039
3085
|
key: admission?.fairnessKey ?? runId,
|
|
3040
3086
|
waiterId: id
|
|
3041
3087
|
});
|
|
3042
|
-
if (!decision.ok)
|
|
3088
|
+
if (!decision.ok) {
|
|
3089
|
+
const waiters = this.queueWaiters.get(queue) ?? /* @__PURE__ */ new Set();
|
|
3090
|
+
waiters.add(runId);
|
|
3091
|
+
this.queueWaiters.set(queue, waiters);
|
|
3092
|
+
throw new WorkflowSuspended(decision.retryAt);
|
|
3093
|
+
}
|
|
3094
|
+
this.queueWaiters.get(queue)?.delete(runId);
|
|
3043
3095
|
this.stepQueue.set(id, queue);
|
|
3044
3096
|
}
|
|
3045
3097
|
const validInput = step.input.parse(input);
|
|
@@ -3067,6 +3119,7 @@ var WorkflowEngine = class {
|
|
|
3067
3119
|
input: validInput,
|
|
3068
3120
|
traceparent: this.traceparent?.(),
|
|
3069
3121
|
context: this.context?.(),
|
|
3122
|
+
priority: admission?.priority,
|
|
3070
3123
|
attempt
|
|
3071
3124
|
}, transport);
|
|
3072
3125
|
this.emit({
|
|
@@ -3086,7 +3139,7 @@ var WorkflowEngine = class {
|
|
|
3086
3139
|
async completeRemoteResult(result) {
|
|
3087
3140
|
const cp = await this.store.getCheckpoint(result.runId, result.seq);
|
|
3088
3141
|
if (!cp || cp.status !== "pending") return;
|
|
3089
|
-
this.releaseQueueSlot(cp.stepId);
|
|
3142
|
+
void this.releaseQueueSlot(cp.stepId).catch(() => void 0);
|
|
3090
3143
|
const run = await this.store.getRun(result.runId);
|
|
3091
3144
|
if (run && (run.status === "cancelled" || run.status === "completed")) return;
|
|
3092
3145
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
@@ -3113,12 +3166,26 @@ var WorkflowEngine = class {
|
|
|
3113
3166
|
});
|
|
3114
3167
|
await this.resume(result.runId);
|
|
3115
3168
|
}
|
|
3169
|
+
/**
|
|
3170
|
+
* A slot freed on `queue` (a fleet-wide signal): resume this instance's runs blocked on it so they
|
|
3171
|
+
* re-contend now instead of at their retry tick. Snapshot-and-clear — a run still blocked after the
|
|
3172
|
+
* retry re-registers itself, and one that's gone (cancelled/admitted) is dropped. Best-effort.
|
|
3173
|
+
*/
|
|
3174
|
+
wakeQueueWaiters(queue) {
|
|
3175
|
+
const waiters = this.queueWaiters.get(queue);
|
|
3176
|
+
if (!waiters || waiters.size === 0) return;
|
|
3177
|
+
const runIds = [
|
|
3178
|
+
...waiters
|
|
3179
|
+
];
|
|
3180
|
+
waiters.clear();
|
|
3181
|
+
for (const runId of runIds) void this.resume(runId).catch(() => void 0);
|
|
3182
|
+
}
|
|
3116
3183
|
/** Release the flow-control slot a dispatched step held (if any), by its stepId. */
|
|
3117
|
-
releaseQueueSlot(id) {
|
|
3184
|
+
async releaseQueueSlot(id) {
|
|
3118
3185
|
const queue = this.stepQueue.get(id);
|
|
3119
3186
|
if (queue === void 0) return;
|
|
3120
3187
|
this.stepQueue.delete(id);
|
|
3121
|
-
this.
|
|
3188
|
+
await this.admission.release(queue, id);
|
|
3122
3189
|
}
|
|
3123
3190
|
/** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
|
|
3124
3191
|
async callRemoteInMemory(runId, seq, step, input, transport) {
|
|
@@ -3279,6 +3346,7 @@ var RemoteWorkflowExecutor = class {
|
|
|
3279
3346
|
input: run.input,
|
|
3280
3347
|
history,
|
|
3281
3348
|
group: this.group,
|
|
3349
|
+
priority: run.priority,
|
|
3282
3350
|
attempt: 1
|
|
3283
3351
|
};
|
|
3284
3352
|
const decision = new Promise((resolve, reject) => {
|
|
@@ -3693,6 +3761,9 @@ __name(attributeOperand, "attributeOperand");
|
|
|
3693
3761
|
var STATE_STORE = Symbol.for("nestjs-durable:STATE_STORE");
|
|
3694
3762
|
var TRANSPORT = Symbol.for("nestjs-durable:TRANSPORT");
|
|
3695
3763
|
var DURABLE_OPTIONS = Symbol.for("nestjs-durable:DURABLE_OPTIONS");
|
|
3764
|
+
var STATE_STORE_CANONICAL = Symbol.for("@dudousxd/nestjs-durable:state-store");
|
|
3765
|
+
var TRANSPORT_CANONICAL = Symbol.for("@dudousxd/nestjs-durable:transport");
|
|
3766
|
+
var DURABLE_OPTIONS_CANONICAL = Symbol.for("@dudousxd/nestjs-durable:options");
|
|
3696
3767
|
|
|
3697
3768
|
// src/testing/in-memory-state-store.ts
|
|
3698
3769
|
var InMemoryStateStore = class {
|
|
@@ -4008,9 +4079,11 @@ var InMemoryTransport = class {
|
|
|
4008
4079
|
CodecStateStore,
|
|
4009
4080
|
ContinueAsNew,
|
|
4010
4081
|
DURABLE_OPTIONS,
|
|
4082
|
+
DURABLE_OPTIONS_CANONICAL,
|
|
4011
4083
|
Entities,
|
|
4012
4084
|
EventAccumulators,
|
|
4013
4085
|
FatalError,
|
|
4086
|
+
InMemoryAdmissionBackend,
|
|
4014
4087
|
InMemoryStateStore,
|
|
4015
4088
|
InMemoryTransport,
|
|
4016
4089
|
NonDeterminismError,
|
|
@@ -4019,9 +4092,11 @@ var InMemoryTransport = class {
|
|
|
4019
4092
|
RemoteStepTimeout,
|
|
4020
4093
|
RemoteWorkflowExecutor,
|
|
4021
4094
|
STATE_STORE,
|
|
4095
|
+
STATE_STORE_CANONICAL,
|
|
4022
4096
|
SignalTimeoutError,
|
|
4023
4097
|
SingletonQueueFullError,
|
|
4024
4098
|
TRANSPORT,
|
|
4099
|
+
TRANSPORT_CANONICAL,
|
|
4025
4100
|
WORKFLOW_NAME_KEY,
|
|
4026
4101
|
WorkflowEngine,
|
|
4027
4102
|
WorkflowSuspended,
|