@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.js
CHANGED
|
@@ -1,6 +1,161 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
+
// src/queue.ts
|
|
5
|
+
var QueueController = class {
|
|
6
|
+
static {
|
|
7
|
+
__name(this, "QueueController");
|
|
8
|
+
}
|
|
9
|
+
config;
|
|
10
|
+
clock;
|
|
11
|
+
inFlight = 0;
|
|
12
|
+
windowStart = 0;
|
|
13
|
+
windowCount = 0;
|
|
14
|
+
/** Registered waiters by id (only used when priority/fairness ordering is in play). */
|
|
15
|
+
waiters = /* @__PURE__ */ new Map();
|
|
16
|
+
/** Monotonic counter stamping each newly-seen waiter for a stable FIFO tiebreak. */
|
|
17
|
+
waiterSeq = 0;
|
|
18
|
+
/** Per-key "last served" order for round-robin fairness — higher = more recently served. */
|
|
19
|
+
keyServed = /* @__PURE__ */ new Map();
|
|
20
|
+
servedTick = 0;
|
|
21
|
+
constructor(config, clock) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.clock = clock;
|
|
24
|
+
}
|
|
25
|
+
/** Whether this controller does any priority/fairness ordering (else it's a plain FIFO gate). */
|
|
26
|
+
get ordered() {
|
|
27
|
+
return this.config.fairness === "key" || this.config.order === "lifo";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Try to admit one unit of work. On `ok`, the caller has taken a concurrency slot and must call
|
|
31
|
+
* {@link release} when the step settles. On block, `retryAt` is when admission may next succeed.
|
|
32
|
+
*
|
|
33
|
+
* `item` carries optional priority/fairness hints; omitting it is the legacy FIFO path. When
|
|
34
|
+
* ordering is configured (or a priority is given), a blocked call REGISTERS as a waiter and is only
|
|
35
|
+
* admitted on a later retry once it is the rightful next under (priority desc, fairness, FIFO).
|
|
36
|
+
*/
|
|
37
|
+
tryAdmit(item) {
|
|
38
|
+
const now = this.clock();
|
|
39
|
+
const rl = this.config.rateLimit;
|
|
40
|
+
if (rl) {
|
|
41
|
+
if (now - this.windowStart >= rl.periodMs) {
|
|
42
|
+
this.windowStart = now;
|
|
43
|
+
this.windowCount = 0;
|
|
44
|
+
}
|
|
45
|
+
if (this.windowCount >= rl.limit) return {
|
|
46
|
+
ok: false,
|
|
47
|
+
retryAt: this.windowStart + rl.periodMs
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const usingOrder = this.ordered || item?.priority != null || item?.waiterId != null;
|
|
51
|
+
const waiterId = item?.waiterId;
|
|
52
|
+
const full = this.config.concurrency != null && this.inFlight >= this.config.concurrency;
|
|
53
|
+
if (full) {
|
|
54
|
+
if (usingOrder && waiterId != null) this.register(waiterId, item);
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (usingOrder && this.waiters.size > 0) {
|
|
61
|
+
if (waiterId != null) this.register(waiterId, item);
|
|
62
|
+
if (!this.isNext(waiterId, item)) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (rl) this.windowCount += 1;
|
|
70
|
+
this.inFlight += 1;
|
|
71
|
+
if (waiterId != null) this.waiters.delete(waiterId);
|
|
72
|
+
if (item?.key != null) this.keyServed.set(item.key, ++this.servedTick);
|
|
73
|
+
return {
|
|
74
|
+
ok: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Release a concurrency slot taken by a successful {@link tryAdmit}. */
|
|
78
|
+
release() {
|
|
79
|
+
if (this.inFlight > 0) this.inFlight -= 1;
|
|
80
|
+
}
|
|
81
|
+
/** Track (or refresh) a blocked call as a waiter; its FIFO seq is stamped only on first sight. */
|
|
82
|
+
register(waiterId, item) {
|
|
83
|
+
const existing = this.waiters.get(waiterId);
|
|
84
|
+
if (existing) {
|
|
85
|
+
existing.priority = item?.priority ?? 0;
|
|
86
|
+
existing.key = item?.key;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.waiters.set(waiterId, {
|
|
90
|
+
priority: item?.priority ?? 0,
|
|
91
|
+
key: item?.key,
|
|
92
|
+
seq: this.waiterSeq++
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Is the calling item the best candidate among all registered waiters? Ordering, best first:
|
|
97
|
+
* 1. higher `priority`;
|
|
98
|
+
* 2. fairness: with `fairness: 'key'`, the least-recently-served key wins (round-robin);
|
|
99
|
+
* 3. earliest registration (FIFO) — stable tiebreak.
|
|
100
|
+
* A bare item (no waiterId) is compared on its hints directly.
|
|
101
|
+
*/
|
|
102
|
+
isNext(waiterId, item) {
|
|
103
|
+
const self = (waiterId != null ? this.waiters.get(waiterId) : void 0) ?? {
|
|
104
|
+
priority: item?.priority ?? 0,
|
|
105
|
+
key: item?.key,
|
|
106
|
+
seq: Number.POSITIVE_INFINITY
|
|
107
|
+
};
|
|
108
|
+
let best = self;
|
|
109
|
+
for (const w of this.waiters.values()) {
|
|
110
|
+
if (this.better(w, best)) best = w;
|
|
111
|
+
}
|
|
112
|
+
return best === self;
|
|
113
|
+
}
|
|
114
|
+
/** Strict "a should be admitted before b" comparison under the active policy. */
|
|
115
|
+
better(a, b) {
|
|
116
|
+
if (a.priority !== b.priority) return a.priority > b.priority;
|
|
117
|
+
if (this.config.fairness === "key") {
|
|
118
|
+
const sa = this.servedOf(a.key);
|
|
119
|
+
const sb = this.servedOf(b.key);
|
|
120
|
+
if (sa !== sb) return sa < sb;
|
|
121
|
+
}
|
|
122
|
+
return this.config.order === "lifo" ? a.seq > b.seq : a.seq < b.seq;
|
|
123
|
+
}
|
|
124
|
+
/** Last-served tick for a key (0 = never served, so an unseen key is admitted before a busy one). */
|
|
125
|
+
servedOf(key) {
|
|
126
|
+
if (key == null) return 0;
|
|
127
|
+
return this.keyServed.get(key) ?? 0;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/admission.ts
|
|
132
|
+
var InMemoryAdmissionBackend = class {
|
|
133
|
+
static {
|
|
134
|
+
__name(this, "InMemoryAdmissionBackend");
|
|
135
|
+
}
|
|
136
|
+
clock;
|
|
137
|
+
controllers = /* @__PURE__ */ new Map();
|
|
138
|
+
constructor(clock) {
|
|
139
|
+
this.clock = clock;
|
|
140
|
+
}
|
|
141
|
+
register(config) {
|
|
142
|
+
this.controllers.set(config.name, new QueueController(config, this.clock));
|
|
143
|
+
}
|
|
144
|
+
handles(queue) {
|
|
145
|
+
return this.controllers.has(queue);
|
|
146
|
+
}
|
|
147
|
+
async tryAdmit(queue, item) {
|
|
148
|
+
const controller = this.controllers.get(queue);
|
|
149
|
+
if (!controller) return {
|
|
150
|
+
ok: true
|
|
151
|
+
};
|
|
152
|
+
return controller.tryAdmit(item);
|
|
153
|
+
}
|
|
154
|
+
async release(queue, _slotId) {
|
|
155
|
+
this.controllers.get(queue)?.release();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
4
159
|
// src/duration.ts
|
|
5
160
|
var UNIT_MS = {
|
|
6
161
|
ms: 1,
|
|
@@ -517,133 +672,6 @@ function eventMatches(payload, match) {
|
|
|
517
672
|
}
|
|
518
673
|
__name(eventMatches, "eventMatches");
|
|
519
674
|
|
|
520
|
-
// src/queue.ts
|
|
521
|
-
var QueueController = class {
|
|
522
|
-
static {
|
|
523
|
-
__name(this, "QueueController");
|
|
524
|
-
}
|
|
525
|
-
config;
|
|
526
|
-
clock;
|
|
527
|
-
inFlight = 0;
|
|
528
|
-
windowStart = 0;
|
|
529
|
-
windowCount = 0;
|
|
530
|
-
/** Registered waiters by id (only used when priority/fairness ordering is in play). */
|
|
531
|
-
waiters = /* @__PURE__ */ new Map();
|
|
532
|
-
/** Monotonic counter stamping each newly-seen waiter for a stable FIFO tiebreak. */
|
|
533
|
-
waiterSeq = 0;
|
|
534
|
-
/** Per-key "last served" order for round-robin fairness — higher = more recently served. */
|
|
535
|
-
keyServed = /* @__PURE__ */ new Map();
|
|
536
|
-
servedTick = 0;
|
|
537
|
-
constructor(config, clock) {
|
|
538
|
-
this.config = config;
|
|
539
|
-
this.clock = clock;
|
|
540
|
-
}
|
|
541
|
-
/** Whether this controller does any priority/fairness ordering (else it's a plain FIFO gate). */
|
|
542
|
-
get ordered() {
|
|
543
|
-
return this.config.fairness === "key";
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* Try to admit one unit of work. On `ok`, the caller has taken a concurrency slot and must call
|
|
547
|
-
* {@link release} when the step settles. On block, `retryAt` is when admission may next succeed.
|
|
548
|
-
*
|
|
549
|
-
* `item` carries optional priority/fairness hints; omitting it is the legacy FIFO path. When
|
|
550
|
-
* ordering is configured (or a priority is given), a blocked call REGISTERS as a waiter and is only
|
|
551
|
-
* admitted on a later retry once it is the rightful next under (priority desc, fairness, FIFO).
|
|
552
|
-
*/
|
|
553
|
-
tryAdmit(item) {
|
|
554
|
-
const now = this.clock();
|
|
555
|
-
const rl = this.config.rateLimit;
|
|
556
|
-
if (rl) {
|
|
557
|
-
if (now - this.windowStart >= rl.periodMs) {
|
|
558
|
-
this.windowStart = now;
|
|
559
|
-
this.windowCount = 0;
|
|
560
|
-
}
|
|
561
|
-
if (this.windowCount >= rl.limit) return {
|
|
562
|
-
ok: false,
|
|
563
|
-
retryAt: this.windowStart + rl.periodMs
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
const usingOrder = this.ordered || item?.priority != null || item?.waiterId != null;
|
|
567
|
-
const waiterId = item?.waiterId;
|
|
568
|
-
const full = this.config.concurrency != null && this.inFlight >= this.config.concurrency;
|
|
569
|
-
if (full) {
|
|
570
|
-
if (usingOrder && waiterId != null) this.register(waiterId, item);
|
|
571
|
-
return {
|
|
572
|
-
ok: false,
|
|
573
|
-
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
if (usingOrder && this.waiters.size > 0) {
|
|
577
|
-
if (waiterId != null) this.register(waiterId, item);
|
|
578
|
-
if (!this.isNext(waiterId, item)) {
|
|
579
|
-
return {
|
|
580
|
-
ok: false,
|
|
581
|
-
retryAt: now + (this.config.retryMs ?? 1e3)
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (rl) this.windowCount += 1;
|
|
586
|
-
this.inFlight += 1;
|
|
587
|
-
if (waiterId != null) this.waiters.delete(waiterId);
|
|
588
|
-
if (item?.key != null) this.keyServed.set(item.key, ++this.servedTick);
|
|
589
|
-
return {
|
|
590
|
-
ok: true
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
/** Release a concurrency slot taken by a successful {@link tryAdmit}. */
|
|
594
|
-
release() {
|
|
595
|
-
if (this.inFlight > 0) this.inFlight -= 1;
|
|
596
|
-
}
|
|
597
|
-
/** Track (or refresh) a blocked call as a waiter; its FIFO seq is stamped only on first sight. */
|
|
598
|
-
register(waiterId, item) {
|
|
599
|
-
const existing = this.waiters.get(waiterId);
|
|
600
|
-
if (existing) {
|
|
601
|
-
existing.priority = item?.priority ?? 0;
|
|
602
|
-
existing.key = item?.key;
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
this.waiters.set(waiterId, {
|
|
606
|
-
priority: item?.priority ?? 0,
|
|
607
|
-
key: item?.key,
|
|
608
|
-
seq: this.waiterSeq++
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Is the calling item the best candidate among all registered waiters? Ordering, best first:
|
|
613
|
-
* 1. higher `priority`;
|
|
614
|
-
* 2. fairness: with `fairness: 'key'`, the least-recently-served key wins (round-robin);
|
|
615
|
-
* 3. earliest registration (FIFO) — stable tiebreak.
|
|
616
|
-
* A bare item (no waiterId) is compared on its hints directly.
|
|
617
|
-
*/
|
|
618
|
-
isNext(waiterId, item) {
|
|
619
|
-
const self = (waiterId != null ? this.waiters.get(waiterId) : void 0) ?? {
|
|
620
|
-
priority: item?.priority ?? 0,
|
|
621
|
-
key: item?.key,
|
|
622
|
-
seq: Number.POSITIVE_INFINITY
|
|
623
|
-
};
|
|
624
|
-
let best = self;
|
|
625
|
-
for (const w of this.waiters.values()) {
|
|
626
|
-
if (this.better(w, best)) best = w;
|
|
627
|
-
}
|
|
628
|
-
return best === self;
|
|
629
|
-
}
|
|
630
|
-
/** Strict "a should be admitted before b" comparison under the active policy. */
|
|
631
|
-
better(a, b) {
|
|
632
|
-
if (a.priority !== b.priority) return a.priority > b.priority;
|
|
633
|
-
if (this.config.fairness === "key") {
|
|
634
|
-
const sa = this.servedOf(a.key);
|
|
635
|
-
const sb = this.servedOf(b.key);
|
|
636
|
-
if (sa !== sb) return sa < sb;
|
|
637
|
-
}
|
|
638
|
-
return a.seq < b.seq;
|
|
639
|
-
}
|
|
640
|
-
/** Last-served tick for a key (0 = never served, so an unseen key is admitted before a busy one). */
|
|
641
|
-
servedOf(key) {
|
|
642
|
-
if (key == null) return 0;
|
|
643
|
-
return this.keyServed.get(key) ?? 0;
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
|
|
647
675
|
// src/singleton-gate.ts
|
|
648
676
|
var SINGLETON_RETRY_MS = 1e3;
|
|
649
677
|
var SINGLETON_RETRY_JITTER_MS = 250;
|
|
@@ -837,6 +865,12 @@ function workflowName(ref) {
|
|
|
837
865
|
__name(workflowName, "workflowName");
|
|
838
866
|
|
|
839
867
|
// src/workflow-ctx.ts
|
|
868
|
+
function normalizeChildOptions(options) {
|
|
869
|
+
return typeof options === "string" ? {
|
|
870
|
+
childId: options
|
|
871
|
+
} : options ?? {};
|
|
872
|
+
}
|
|
873
|
+
__name(normalizeChildOptions, "normalizeChildOptions");
|
|
840
874
|
var Position = class Position2 {
|
|
841
875
|
static {
|
|
842
876
|
__name(this, "Position");
|
|
@@ -1086,7 +1120,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1086
1120
|
await step(`task:dispatch:${name}`, dispatch, options);
|
|
1087
1121
|
return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
|
|
1088
1122
|
}, "task");
|
|
1089
|
-
const child = /* @__PURE__ */ __name(async (workflow2, input,
|
|
1123
|
+
const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
1124
|
+
const { childId, priority } = normalizeChildOptions(options);
|
|
1090
1125
|
const current = pos.next();
|
|
1091
1126
|
const id = childId ?? `${runId}.child.${current}`;
|
|
1092
1127
|
const existing = await readCheckpoint(current);
|
|
@@ -1098,7 +1133,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1098
1133
|
runId,
|
|
1099
1134
|
seq: current
|
|
1100
1135
|
});
|
|
1101
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id);
|
|
1136
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority);
|
|
1102
1137
|
if (!existing) {
|
|
1103
1138
|
await writeCheckpoint(instantCheckpoint({
|
|
1104
1139
|
runId,
|
|
@@ -1110,12 +1145,13 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1110
1145
|
}
|
|
1111
1146
|
throw new WorkflowSuspended();
|
|
1112
1147
|
}, "child");
|
|
1113
|
-
const startChild = /* @__PURE__ */ __name(async (workflow2, input,
|
|
1148
|
+
const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
1149
|
+
const { childId, priority } = normalizeChildOptions(options);
|
|
1114
1150
|
const current = pos.next();
|
|
1115
1151
|
const id = childId ?? `${runId}.child.${current}`;
|
|
1116
1152
|
const existing = await readCheckpoint(current);
|
|
1117
1153
|
if (existing && existing.status === "completed") return existing.output;
|
|
1118
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id);
|
|
1154
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority);
|
|
1119
1155
|
await writeCheckpoint(instantCheckpoint({
|
|
1120
1156
|
runId,
|
|
1121
1157
|
seq: current,
|
|
@@ -1339,8 +1375,10 @@ var WorkflowEngine = class {
|
|
|
1339
1375
|
updateValidators = /* @__PURE__ */ new Map();
|
|
1340
1376
|
/** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
|
|
1341
1377
|
cancelRequested = /* @__PURE__ */ new Set();
|
|
1342
|
-
/** Flow-control
|
|
1343
|
-
|
|
1378
|
+
/** Flow-control admission backend for remote steps (see {@link registerQueue}). */
|
|
1379
|
+
admission;
|
|
1380
|
+
/** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
|
|
1381
|
+
queueWaiters = /* @__PURE__ */ new Map();
|
|
1344
1382
|
/** Which queue a dispatched step took a slot from, by stepId — so the result can release it. */
|
|
1345
1383
|
stepQueue = /* @__PURE__ */ new Map();
|
|
1346
1384
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
@@ -1356,6 +1394,8 @@ var WorkflowEngine = class {
|
|
|
1356
1394
|
] : []));
|
|
1357
1395
|
this.controlPlane = deps.controlPlane;
|
|
1358
1396
|
this.clock = deps.clock ?? Date.now;
|
|
1397
|
+
this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
|
|
1398
|
+
this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
|
|
1359
1399
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
1360
1400
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
1361
1401
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
@@ -1519,7 +1559,7 @@ var WorkflowEngine = class {
|
|
|
1519
1559
|
* durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
|
|
1520
1560
|
*/
|
|
1521
1561
|
registerQueue(config) {
|
|
1522
|
-
this.
|
|
1562
|
+
this.admission.register(config);
|
|
1523
1563
|
}
|
|
1524
1564
|
/** Subscribe to lifecycle events. Returns an unsubscribe function. */
|
|
1525
1565
|
subscribe(listener) {
|
|
@@ -1640,6 +1680,7 @@ var WorkflowEngine = class {
|
|
|
1640
1680
|
input,
|
|
1641
1681
|
tags,
|
|
1642
1682
|
searchAttributes: opts?.searchAttributes,
|
|
1683
|
+
priority: opts?.priority,
|
|
1643
1684
|
createdAt: now,
|
|
1644
1685
|
updatedAt: now
|
|
1645
1686
|
};
|
|
@@ -2866,8 +2907,10 @@ var WorkflowEngine = class {
|
|
|
2866
2907
|
failStep: /* @__PURE__ */ __name((s) => this.failStep(s), "failStep"),
|
|
2867
2908
|
callRemote: /* @__PURE__ */ __name((runId, seq, step, input, queue, transport, admission) => this.callRemote(runId, seq, step, input, queue, transport, replay, admission), "callRemote"),
|
|
2868
2909
|
// Defer so a fast child can't reentrantly resume a still-running parent.
|
|
2869
|
-
startChild: /* @__PURE__ */ __name((workflow, input, id) => {
|
|
2870
|
-
queueMicrotask(() => void this.start(workflow, input, id
|
|
2910
|
+
startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
|
|
2911
|
+
queueMicrotask(() => void this.start(workflow, input, id, {
|
|
2912
|
+
priority
|
|
2913
|
+
}).catch(() => void 0));
|
|
2871
2914
|
}, "startChild"),
|
|
2872
2915
|
signalEntity: /* @__PURE__ */ __name((name, key, op, arg, reply) => {
|
|
2873
2916
|
queueMicrotask(() => void this.entities.dispatch(name, key, op, arg, reply).catch(() => void 0));
|
|
@@ -2968,14 +3011,19 @@ var WorkflowEngine = class {
|
|
|
2968
3011
|
attempt = existing.attempts + 1;
|
|
2969
3012
|
}
|
|
2970
3013
|
const id = stepId(runId, seq);
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
const decision = controller.tryAdmit({
|
|
3014
|
+
if (queue && this.admission.handles(queue)) {
|
|
3015
|
+
const decision = await this.admission.tryAdmit(queue, {
|
|
2974
3016
|
priority: admission?.priority,
|
|
2975
3017
|
key: admission?.fairnessKey ?? runId,
|
|
2976
3018
|
waiterId: id
|
|
2977
3019
|
});
|
|
2978
|
-
if (!decision.ok)
|
|
3020
|
+
if (!decision.ok) {
|
|
3021
|
+
const waiters = this.queueWaiters.get(queue) ?? /* @__PURE__ */ new Set();
|
|
3022
|
+
waiters.add(runId);
|
|
3023
|
+
this.queueWaiters.set(queue, waiters);
|
|
3024
|
+
throw new WorkflowSuspended(decision.retryAt);
|
|
3025
|
+
}
|
|
3026
|
+
this.queueWaiters.get(queue)?.delete(runId);
|
|
2979
3027
|
this.stepQueue.set(id, queue);
|
|
2980
3028
|
}
|
|
2981
3029
|
const validInput = step.input.parse(input);
|
|
@@ -3003,6 +3051,7 @@ var WorkflowEngine = class {
|
|
|
3003
3051
|
input: validInput,
|
|
3004
3052
|
traceparent: this.traceparent?.(),
|
|
3005
3053
|
context: this.context?.(),
|
|
3054
|
+
priority: admission?.priority,
|
|
3006
3055
|
attempt
|
|
3007
3056
|
}, transport);
|
|
3008
3057
|
this.emit({
|
|
@@ -3022,7 +3071,7 @@ var WorkflowEngine = class {
|
|
|
3022
3071
|
async completeRemoteResult(result) {
|
|
3023
3072
|
const cp = await this.store.getCheckpoint(result.runId, result.seq);
|
|
3024
3073
|
if (!cp || cp.status !== "pending") return;
|
|
3025
|
-
this.releaseQueueSlot(cp.stepId);
|
|
3074
|
+
void this.releaseQueueSlot(cp.stepId).catch(() => void 0);
|
|
3026
3075
|
const run = await this.store.getRun(result.runId);
|
|
3027
3076
|
if (run && (run.status === "cancelled" || run.status === "completed")) return;
|
|
3028
3077
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
@@ -3049,12 +3098,26 @@ var WorkflowEngine = class {
|
|
|
3049
3098
|
});
|
|
3050
3099
|
await this.resume(result.runId);
|
|
3051
3100
|
}
|
|
3101
|
+
/**
|
|
3102
|
+
* A slot freed on `queue` (a fleet-wide signal): resume this instance's runs blocked on it so they
|
|
3103
|
+
* re-contend now instead of at their retry tick. Snapshot-and-clear — a run still blocked after the
|
|
3104
|
+
* retry re-registers itself, and one that's gone (cancelled/admitted) is dropped. Best-effort.
|
|
3105
|
+
*/
|
|
3106
|
+
wakeQueueWaiters(queue) {
|
|
3107
|
+
const waiters = this.queueWaiters.get(queue);
|
|
3108
|
+
if (!waiters || waiters.size === 0) return;
|
|
3109
|
+
const runIds = [
|
|
3110
|
+
...waiters
|
|
3111
|
+
];
|
|
3112
|
+
waiters.clear();
|
|
3113
|
+
for (const runId of runIds) void this.resume(runId).catch(() => void 0);
|
|
3114
|
+
}
|
|
3052
3115
|
/** Release the flow-control slot a dispatched step held (if any), by its stepId. */
|
|
3053
|
-
releaseQueueSlot(id) {
|
|
3116
|
+
async releaseQueueSlot(id) {
|
|
3054
3117
|
const queue = this.stepQueue.get(id);
|
|
3055
3118
|
if (queue === void 0) return;
|
|
3056
3119
|
this.stepQueue.delete(id);
|
|
3057
|
-
this.
|
|
3120
|
+
await this.admission.release(queue, id);
|
|
3058
3121
|
}
|
|
3059
3122
|
/** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
|
|
3060
3123
|
async callRemoteInMemory(runId, seq, step, input, transport) {
|
|
@@ -3215,6 +3278,7 @@ var RemoteWorkflowExecutor = class {
|
|
|
3215
3278
|
input: run.input,
|
|
3216
3279
|
history,
|
|
3217
3280
|
group: this.group,
|
|
3281
|
+
priority: run.priority,
|
|
3218
3282
|
attempt: 1
|
|
3219
3283
|
};
|
|
3220
3284
|
const decision = new Promise((resolve, reject) => {
|
|
@@ -3629,6 +3693,9 @@ __name(attributeOperand, "attributeOperand");
|
|
|
3629
3693
|
var STATE_STORE = Symbol.for("nestjs-durable:STATE_STORE");
|
|
3630
3694
|
var TRANSPORT = Symbol.for("nestjs-durable:TRANSPORT");
|
|
3631
3695
|
var DURABLE_OPTIONS = Symbol.for("nestjs-durable:DURABLE_OPTIONS");
|
|
3696
|
+
var STATE_STORE_CANONICAL = Symbol.for("@dudousxd/nestjs-durable:state-store");
|
|
3697
|
+
var TRANSPORT_CANONICAL = Symbol.for("@dudousxd/nestjs-durable:transport");
|
|
3698
|
+
var DURABLE_OPTIONS_CANONICAL = Symbol.for("@dudousxd/nestjs-durable:options");
|
|
3632
3699
|
|
|
3633
3700
|
// src/testing/in-memory-state-store.ts
|
|
3634
3701
|
var InMemoryStateStore = class {
|
|
@@ -3943,9 +4010,11 @@ export {
|
|
|
3943
4010
|
CodecStateStore,
|
|
3944
4011
|
ContinueAsNew,
|
|
3945
4012
|
DURABLE_OPTIONS,
|
|
4013
|
+
DURABLE_OPTIONS_CANONICAL,
|
|
3946
4014
|
Entities,
|
|
3947
4015
|
EventAccumulators,
|
|
3948
4016
|
FatalError,
|
|
4017
|
+
InMemoryAdmissionBackend,
|
|
3949
4018
|
InMemoryStateStore,
|
|
3950
4019
|
InMemoryTransport,
|
|
3951
4020
|
NonDeterminismError,
|
|
@@ -3954,9 +4023,11 @@ export {
|
|
|
3954
4023
|
RemoteStepTimeout,
|
|
3955
4024
|
RemoteWorkflowExecutor,
|
|
3956
4025
|
STATE_STORE,
|
|
4026
|
+
STATE_STORE_CANONICAL,
|
|
3957
4027
|
SignalTimeoutError,
|
|
3958
4028
|
SingletonQueueFullError,
|
|
3959
4029
|
TRANSPORT,
|
|
4030
|
+
TRANSPORT_CANONICAL,
|
|
3960
4031
|
WORKFLOW_NAME_KEY,
|
|
3961
4032
|
WorkflowEngine,
|
|
3962
4033
|
WorkflowSuspended,
|