@dudousxd/nestjs-durable-core 0.53.0 → 0.55.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 +204 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -14
- package/dist/index.d.ts +146 -14
- package/dist/index.js +201 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,8 @@ __export(index_exports, {
|
|
|
30
30
|
DURABLE_STEP_CONFIG: () => DURABLE_STEP_CONFIG,
|
|
31
31
|
DURABLE_STEP_NAME: () => DURABLE_STEP_NAME,
|
|
32
32
|
DurableWorkflow: () => DurableWorkflow,
|
|
33
|
+
EVENT_BUFFER_SCAN_LIMIT: () => EVENT_BUFFER_SCAN_LIMIT,
|
|
34
|
+
EVENT_TOKEN_PREFIX: () => EVENT_TOKEN_PREFIX,
|
|
33
35
|
Entities: () => Entities,
|
|
34
36
|
EventAccumulators: () => EventAccumulators,
|
|
35
37
|
FatalError: () => FatalError,
|
|
@@ -68,6 +70,7 @@ __export(index_exports, {
|
|
|
68
70
|
eventPrefix: () => eventPrefix,
|
|
69
71
|
eventToken: () => eventToken,
|
|
70
72
|
indexWaitersByRun: () => indexWaitersByRun,
|
|
73
|
+
isEventToken: () => isEventToken,
|
|
71
74
|
isWorkflowControlFlowSignal: () => isWorkflowControlFlowSignal,
|
|
72
75
|
matchesAttributes: () => matchesAttributes,
|
|
73
76
|
normalizeAttributeRows: () => normalizeAttributeRows,
|
|
@@ -871,10 +874,16 @@ var EventAccumulators = class {
|
|
|
871
874
|
// src/events.ts
|
|
872
875
|
var enc = /* @__PURE__ */ __name((s) => Buffer.from(s, "utf8").toString("base64"), "enc");
|
|
873
876
|
var dec = /* @__PURE__ */ __name((s) => Buffer.from(s, "base64").toString("utf8"), "dec");
|
|
877
|
+
var EVENT_BUFFER_SCAN_LIMIT = 50;
|
|
878
|
+
var EVENT_TOKEN_PREFIX = "event:";
|
|
874
879
|
function eventPrefix(name) {
|
|
875
|
-
return
|
|
880
|
+
return `${EVENT_TOKEN_PREFIX}${enc(name)}:`;
|
|
876
881
|
}
|
|
877
882
|
__name(eventPrefix, "eventPrefix");
|
|
883
|
+
function isEventToken(token) {
|
|
884
|
+
return token.startsWith(EVENT_TOKEN_PREFIX);
|
|
885
|
+
}
|
|
886
|
+
__name(isEventToken, "isEventToken");
|
|
878
887
|
function eventToken(name, match, runId, seq) {
|
|
879
888
|
return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
|
|
880
889
|
}
|
|
@@ -957,7 +966,7 @@ var RemoteWorkflowExecutor = class {
|
|
|
957
966
|
var WEBHOOK_PREFIX = "wh:";
|
|
958
967
|
var CHILD_PREFIX = "child:";
|
|
959
968
|
var BREAKPOINT_PREFIX = "bp:";
|
|
960
|
-
var EVENT_PREFIX =
|
|
969
|
+
var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
|
|
961
970
|
function classifyWaiterToken(token) {
|
|
962
971
|
if (token.startsWith(WEBHOOK_PREFIX)) return {
|
|
963
972
|
on: "webhook",
|
|
@@ -1181,6 +1190,21 @@ var TransportPool = class {
|
|
|
1181
1190
|
];
|
|
1182
1191
|
}
|
|
1183
1192
|
/**
|
|
1193
|
+
* The first transport (in pool order) reporting a LIVE heartbeat for `group` — the one a
|
|
1194
|
+
* convention-routed remote workflow must dispatch on. In a mixed pool (e.g. a tenant-scoped
|
|
1195
|
+
* control plane pairing its namespaced transport with a bare-prefix one for operator-convention
|
|
1196
|
+
* tenant workers), each transport sees a DIFFERENT keyspace: dispatching on `primary` while the
|
|
1197
|
+
* live worker heartbeats on another transport parks the task on a queue nobody consumes. Returns
|
|
1198
|
+
* `undefined` when no transport reports the group (mirrors the merged `listWorkerGroups` miss).
|
|
1199
|
+
*/
|
|
1200
|
+
async transportWithLiveGroup(group) {
|
|
1201
|
+
for (const { transport } of this.transports) {
|
|
1202
|
+
if (!transport.listWorkerGroups) continue;
|
|
1203
|
+
if ((await transport.listWorkerGroups()).includes(group)) return transport;
|
|
1204
|
+
}
|
|
1205
|
+
return void 0;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1184
1208
|
* Propagate the engine's `namespace` to every transport that partitions by it — so the same
|
|
1185
1209
|
* namespace that scopes the store also scopes each transport's queues/keys. A transport that
|
|
1186
1210
|
* doesn't partition (no `useNamespace`) is skipped. Idempotent; a transport given an explicit
|
|
@@ -1423,6 +1447,29 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
1423
1447
|
value: buffered.payload
|
|
1424
1448
|
};
|
|
1425
1449
|
}, "consumeBuffered");
|
|
1450
|
+
const consumeBufferedEvent = /* @__PURE__ */ __name(async (name, match, token, seq) => {
|
|
1451
|
+
const candidates = await store.listBufferedEvents(name, EVENT_BUFFER_SCAN_LIMIT);
|
|
1452
|
+
for (const candidate of candidates) {
|
|
1453
|
+
if (!eventMatches(candidate.payload, match ?? {})) continue;
|
|
1454
|
+
if (!await store.removeBufferedEvent(candidate.id)) continue;
|
|
1455
|
+
await store.removeSignalWaiter({
|
|
1456
|
+
token,
|
|
1457
|
+
runId,
|
|
1458
|
+
seq
|
|
1459
|
+
});
|
|
1460
|
+
await writeCheckpoint(instantCheckpoint({
|
|
1461
|
+
runId,
|
|
1462
|
+
seq,
|
|
1463
|
+
name: `signal:${token}`,
|
|
1464
|
+
kind: "signal",
|
|
1465
|
+
output: candidate.payload
|
|
1466
|
+
}));
|
|
1467
|
+
return {
|
|
1468
|
+
value: candidate.payload
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
return null;
|
|
1472
|
+
}, "consumeBufferedEvent");
|
|
1426
1473
|
const stampDeadline = /* @__PURE__ */ __name(async (deadlineSeq, label, timeoutMs) => {
|
|
1427
1474
|
const recorded = await readCheckpoint(deadlineSeq);
|
|
1428
1475
|
const deadline = recorded?.wakeAt ?? host.clock() + timeoutMs;
|
|
@@ -1503,6 +1550,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
1503
1550
|
runId,
|
|
1504
1551
|
seq: current
|
|
1505
1552
|
});
|
|
1553
|
+
const bufferedHit2 = await consumeBufferedEvent(name, opts?.match, token2, current);
|
|
1554
|
+
if (bufferedHit2) return bufferedHit2.value;
|
|
1506
1555
|
throw new WorkflowSuspended();
|
|
1507
1556
|
}
|
|
1508
1557
|
const timeoutMs = opts.timeoutMs;
|
|
@@ -1525,6 +1574,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
1525
1574
|
runId,
|
|
1526
1575
|
seq: waitSeq
|
|
1527
1576
|
});
|
|
1577
|
+
const bufferedHit = await consumeBufferedEvent(name, opts.match, token, waitSeq);
|
|
1578
|
+
if (bufferedHit) return bufferedHit.value;
|
|
1528
1579
|
throw new WorkflowSuspended(deadline);
|
|
1529
1580
|
}, "waitForEvent");
|
|
1530
1581
|
const task = /* @__PURE__ */ __name(async (name, dispatch, options) => {
|
|
@@ -1918,6 +1969,7 @@ var WorkflowEngine = class {
|
|
|
1918
1969
|
maxRecoveryAttempts;
|
|
1919
1970
|
remoteAdvanceSilenceMs;
|
|
1920
1971
|
reconcileMs;
|
|
1972
|
+
eventBufferTtlMs;
|
|
1921
1973
|
remoteRedispatchMs;
|
|
1922
1974
|
remoteRedispatchMax;
|
|
1923
1975
|
webhookUrl;
|
|
@@ -1991,6 +2043,7 @@ var WorkflowEngine = class {
|
|
|
1991
2043
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1992
2044
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
1993
2045
|
this.reconcileMs = deps.reconcileMs === void 0 ? 3e5 : deps.reconcileMs || void 0;
|
|
2046
|
+
this.eventBufferTtlMs = deps.eventBufferTtlMs || void 0;
|
|
1994
2047
|
this.remoteRedispatchMs = deps.remoteRedispatchMs || void 0;
|
|
1995
2048
|
this.remoteRedispatchMax = Math.max(1, deps.remoteRedispatchMax ?? 10);
|
|
1996
2049
|
this.webhookUrl = deps.webhookUrl;
|
|
@@ -2467,10 +2520,10 @@ var WorkflowEngine = class {
|
|
|
2467
2520
|
* liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
|
|
2468
2521
|
*/
|
|
2469
2522
|
async resolveRemoteByConvention(run) {
|
|
2470
|
-
const liveGroups = await this.pool.listWorkerGroups();
|
|
2471
2523
|
const group = tenantGroup(sanitizeQueueToken(run.workflow), run.namespace);
|
|
2472
|
-
|
|
2473
|
-
|
|
2524
|
+
const liveTransport = await this.pool.transportWithLiveGroup(group);
|
|
2525
|
+
if (!liveTransport) return void 0;
|
|
2526
|
+
const executor = new RemoteWorkflowExecutor(liveTransport, run.workflow, run.namespace);
|
|
2474
2527
|
return {
|
|
2475
2528
|
name: run.workflow,
|
|
2476
2529
|
version: run.workflowVersion,
|
|
@@ -2664,14 +2717,16 @@ var WorkflowEngine = class {
|
|
|
2664
2717
|
* Resume every suspended run whose durable timer is due. Call periodically (a poller) and on
|
|
2665
2718
|
* boot. A run still not due re-suspends cheaply without running new work.
|
|
2666
2719
|
*
|
|
2667
|
-
* Piggy-backs the signal-waiter reconcile safety net (see {@link reconcileSignalWaiter}): a
|
|
2668
|
-
* between the waiter/buffer pairing ops on EITHER side (this run's own `putSignalWaiter` →
|
|
2669
|
-
* `takeBufferedSignal`, or
|
|
2670
|
-
* `signal`'s interleaving proof
|
|
2671
|
-
*
|
|
2672
|
-
*
|
|
2673
|
-
*
|
|
2674
|
-
*
|
|
2720
|
+
* Piggy-backs the signal/event-waiter reconcile safety net (see {@link reconcileSignalWaiter}): a
|
|
2721
|
+
* crash between the waiter/buffer pairing ops on EITHER side (this run's own `putSignalWaiter` →
|
|
2722
|
+
* `takeBufferedSignal`/`listBufferedEvents`, or the other side's `takeSignalWaiter`/fan-out →
|
|
2723
|
+
* `bufferSignal`/`bufferEvent` → re-check — see `signal`'s interleaving proof, which `publishEvent`
|
|
2724
|
+
* mirrors for events) can leave both rows stranded with nothing left to pair them: the waiter's turn
|
|
2725
|
+
* already suspended, the other side that buffered it already returned. Event-wait suspends already
|
|
2726
|
+
* carry a `reconcileMs` fallback `wakeAt` (see {@link reconcileWakeAt}) precisely so they show up
|
|
2727
|
+
* HERE — that's what makes this due-timer pass the natural place for the sweep (and, for events, also
|
|
2728
|
+
* where an `eventBufferTtlMs` prune piggy-backs — see {@link reconcileBufferedEvent}). One bulk waiter
|
|
2729
|
+
* scan, filtered to just this tick's batch, keeps it cheap regardless of store size.
|
|
2675
2730
|
*/
|
|
2676
2731
|
async resumeDueTimers(nowMs = this.clock()) {
|
|
2677
2732
|
const due = await this.store.listDueTimers(nowMs, this.namespace);
|
|
@@ -2681,11 +2736,15 @@ var WorkflowEngine = class {
|
|
|
2681
2736
|
}
|
|
2682
2737
|
/**
|
|
2683
2738
|
* The reconcile safety net's per-run check (see {@link resumeDueTimers}): if this suspended run has
|
|
2684
|
-
* a registered signal waiter
|
|
2685
|
-
* directly instead of falling through to a full replay.
|
|
2686
|
-
* never buffered (
|
|
2687
|
-
*
|
|
2688
|
-
*
|
|
2739
|
+
* a registered signal OR event waiter and a matching payload is already buffered, pair and deliver
|
|
2740
|
+
* them directly instead of falling through to a full replay. Child/breakpoint waiter tokens are
|
|
2741
|
+
* never buffered (a child/breakpoint notifies via `signal` only once its counterpart is already
|
|
2742
|
+
* registered) — `takeBufferedSignal` on those is a guaranteed miss, so this only ever does real work
|
|
2743
|
+
* for a plain signal or an event waiter (classified via {@link isEventToken}).
|
|
2744
|
+
*
|
|
2745
|
+
* Also opportunistically prunes expired buffered EVENTS for this waiter's name when
|
|
2746
|
+
* {@link eventBufferTtlMs} is set (see its doc — scoped to names a registered waiter references in
|
|
2747
|
+
* this tick's batch, piggy-backing the SAME scan rather than a separate pass), even on a miss.
|
|
2689
2748
|
*
|
|
2690
2749
|
* Returns `undefined` (not settled — `resumeLeased` proceeds with its normal `resume`) when there's
|
|
2691
2750
|
* no waiter or no buffered hit. Deliberately never returns `null`: a delivered signal always resumes
|
|
@@ -2695,6 +2754,7 @@ var WorkflowEngine = class {
|
|
|
2695
2754
|
*/
|
|
2696
2755
|
async reconcileSignalWaiter(run, waiter) {
|
|
2697
2756
|
if (!waiter) return void 0;
|
|
2757
|
+
if (isEventToken(waiter.token)) return this.reconcileBufferedEvent(run, waiter);
|
|
2698
2758
|
const buffered = await this.store.takeBufferedSignal(waiter.token);
|
|
2699
2759
|
if (!buffered) return void 0;
|
|
2700
2760
|
await this.store.removeSignalWaiter(waiter);
|
|
@@ -2705,6 +2765,34 @@ var WorkflowEngine = class {
|
|
|
2705
2765
|
};
|
|
2706
2766
|
}
|
|
2707
2767
|
/**
|
|
2768
|
+
* The EVENT half of {@link reconcileSignalWaiter}: scan this waiter's name for a matching buffered
|
|
2769
|
+
* event (same list+evaluate+claim shape as `waitForEvent`'s own consumption — the match predicate
|
|
2770
|
+
* belongs to the waiter, never the store), pruning any expired candidates it encounters along the
|
|
2771
|
+
* way when {@link eventBufferTtlMs} is configured. A pruned-but-non-matching candidate does not stop
|
|
2772
|
+
* the scan; only a MATCHING hit (or exhausting the scan window) does.
|
|
2773
|
+
*/
|
|
2774
|
+
async reconcileBufferedEvent(run, waiter) {
|
|
2775
|
+
const name = eventNameOf(waiter.token);
|
|
2776
|
+
const match = eventMatchOf(waiter.token);
|
|
2777
|
+
const candidates = await this.store.listBufferedEvents(name, EVENT_BUFFER_SCAN_LIMIT);
|
|
2778
|
+
const nowMs = this.clock();
|
|
2779
|
+
for (const candidate of candidates) {
|
|
2780
|
+
if (this.eventBufferTtlMs != null && nowMs - candidate.publishedAt > this.eventBufferTtlMs) {
|
|
2781
|
+
await this.store.removeBufferedEvent(candidate.id);
|
|
2782
|
+
continue;
|
|
2783
|
+
}
|
|
2784
|
+
if (!eventMatches(candidate.payload, match)) continue;
|
|
2785
|
+
if (!await this.store.removeBufferedEvent(candidate.id)) continue;
|
|
2786
|
+
await this.store.removeSignalWaiter(waiter);
|
|
2787
|
+
const settled = await this.deliverSignal(waiter, candidate.payload);
|
|
2788
|
+
return settled ?? {
|
|
2789
|
+
runId: run.id,
|
|
2790
|
+
status: run.status
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
return void 0;
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2708
2796
|
* Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
|
|
2709
2797
|
* exactly one instance runs it), then resumes the body. Returns the result, or null if another
|
|
2710
2798
|
* instance holds the lease or the engine is draining. The default in-process dispatcher calls this;
|
|
@@ -2892,6 +2980,21 @@ var WorkflowEngine = class {
|
|
|
2892
2980
|
* 2. **Starts** a fresh run of every workflow registered with `onEvent: [name]`, passing the
|
|
2893
2981
|
* payload as input. Idempotent by `evt:<id>:<workflow>` — pass `opts.id` to dedupe redeliveries
|
|
2894
2982
|
* of the same logical event (default: a fresh uuid, so each publish triggers once).
|
|
2983
|
+
*
|
|
2984
|
+
* Semantics (deliberate — mirrors `signalWithStart`'s reliability contract, so document it exactly):
|
|
2985
|
+
* a publish that resumes ≥1 live waiter, OR routes into an `eventBatch` accumulator / starts ≥1
|
|
2986
|
+
* subscriber, behaves exactly as above and is NOT buffered — fan-out stays live-only, same as
|
|
2987
|
+
* today. A publish that touches NEITHER — no live waiter matched, no subscriber exists/started —
|
|
2988
|
+
* buffers ONE copy (via {@link StateStore.bufferEvent}) unless `opts.buffer === false`, so it isn't
|
|
2989
|
+
* silently dropped just because nobody happened to be listening yet. That buffered copy is consumed
|
|
2990
|
+
* by the FIRST future `ctx.waitForEvent(name, { match })` whose match accepts its payload —
|
|
2991
|
+
* point-to-point on redelivery, by design (only one waiter ever gets it, even though the LIVE path
|
|
2992
|
+
* above is fan-out) — never by a second, later-registered onEvent subscriber (subscriber starts stay
|
|
2993
|
+
* live-only; `opts.id` dedupe applies to those only, unchanged). Right after buffering, this re-checks
|
|
2994
|
+
* `listSignalWaiters` ONCE — sandwich parity with `signal`'s own take → buffer → re-check — so a
|
|
2995
|
+
* waiter that registers in the sliver between the initial miss and the buffer write is still paired
|
|
2996
|
+
* instead of leaving both rows stranded (see `signal`'s interleaving proof; `waitForEvent`'s own
|
|
2997
|
+
* post-registration buffer scan is the mirror-image half of this same race).
|
|
2895
2998
|
*/
|
|
2896
2999
|
async publishEvent(name, payload, opts) {
|
|
2897
3000
|
let touched = 0;
|
|
@@ -2918,6 +3021,23 @@ var WorkflowEngine = class {
|
|
|
2918
3021
|
}
|
|
2919
3022
|
}
|
|
2920
3023
|
}
|
|
3024
|
+
if (touched > 0 || opts?.buffer === false) return touched;
|
|
3025
|
+
const bufferedId = globalThis.crypto.randomUUID();
|
|
3026
|
+
await this.store.bufferEvent({
|
|
3027
|
+
name,
|
|
3028
|
+
payload,
|
|
3029
|
+
id: bufferedId,
|
|
3030
|
+
publishedAt: this.clock()
|
|
3031
|
+
});
|
|
3032
|
+
const lateWaiters = await this.store.listSignalWaiters(eventPrefix(name));
|
|
3033
|
+
const lateWaiter = lateWaiters.find((w) => eventMatches(payload, eventMatchOf(w.token)));
|
|
3034
|
+
if (!lateWaiter) return touched;
|
|
3035
|
+
if (!await this.store.removeBufferedEvent(bufferedId)) return touched;
|
|
3036
|
+
const waiter = await this.store.takeSignalWaiter(lateWaiter.token);
|
|
3037
|
+
if (waiter) {
|
|
3038
|
+
const settled = await this.deliverSignal(waiter, payload);
|
|
3039
|
+
if (settled) touched += 1;
|
|
3040
|
+
}
|
|
2921
3041
|
return touched;
|
|
2922
3042
|
}
|
|
2923
3043
|
/**
|
|
@@ -3022,6 +3142,27 @@ var WorkflowEngine = class {
|
|
|
3022
3142
|
void this.signal(`child:${runId}`, completion).catch(() => void 0);
|
|
3023
3143
|
}
|
|
3024
3144
|
/**
|
|
3145
|
+
* Deferred child start shared by the in-process ctx host and the remote `startChild` command.
|
|
3146
|
+
* Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
|
|
3147
|
+
* that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
|
|
3148
|
+
* failure) must NOT be swallowed: the parent is already suspended on `child:<childId>` (the waiter
|
|
3149
|
+
* is put BEFORE start on every path), so a silent drop parks it in suspended-forever, invisibly
|
|
3150
|
+
* re-attempting on every recovery wake — a misconfigured remote child looks exactly like a healthy
|
|
3151
|
+
* long wait. Instead the failure is delivered to that waiter like a failed child (notifyParent):
|
|
3152
|
+
* the parent resumes and fails loudly with the cause. For a fire-and-forget `ctx.startChild`
|
|
3153
|
+
* (spawn — no waiter) the completion is buffered; a later join by the same id consumes it and
|
|
3154
|
+
* correctly observes the failed start.
|
|
3155
|
+
*/
|
|
3156
|
+
startChildDeferred(workflow, input, childId, opts) {
|
|
3157
|
+
queueMicrotask(() => void this.start(workflow, input, childId, opts).catch((err) => {
|
|
3158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3159
|
+
this.notifyParent(childId, {
|
|
3160
|
+
ok: false,
|
|
3161
|
+
error: `child workflow "${workflow}" failed to start: ${message}`
|
|
3162
|
+
});
|
|
3163
|
+
}));
|
|
3164
|
+
}
|
|
3165
|
+
/**
|
|
3025
3166
|
* Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
|
|
3026
3167
|
* `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
|
|
3027
3168
|
* steps' compensations run in reverse (visible as `compensate:<step>` events), then it's marked
|
|
@@ -3845,9 +3986,9 @@ var WorkflowEngine = class {
|
|
|
3845
3986
|
parallelGroup: cmd.parallelGroup
|
|
3846
3987
|
});
|
|
3847
3988
|
if (!await this.store.getRun(childId)) {
|
|
3848
|
-
|
|
3989
|
+
this.startChildDeferred(cmd.workflow, cmd.input, childId, {
|
|
3849
3990
|
namespace: run.namespace
|
|
3850
|
-
})
|
|
3991
|
+
});
|
|
3851
3992
|
}
|
|
3852
3993
|
} else {
|
|
3853
3994
|
throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
|
|
@@ -4226,10 +4367,10 @@ var WorkflowEngine = class {
|
|
|
4226
4367
|
// `davi-local` pipeline would stamp its `processing` child `default` and it would leak off the
|
|
4227
4368
|
// tenant's worker pool onto the shared/dev workers.
|
|
4228
4369
|
startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
|
|
4229
|
-
|
|
4370
|
+
this.startChildDeferred(workflow, input, id, {
|
|
4230
4371
|
priority,
|
|
4231
4372
|
namespace: parentNamespace
|
|
4232
|
-
})
|
|
4373
|
+
});
|
|
4233
4374
|
}, "startChild"),
|
|
4234
4375
|
// Deferred for the same reentrancy reason as `startChild` above. `cancel()` is already
|
|
4235
4376
|
// idempotent on a terminal/cancelled run (returns its existing status without side effects), so
|
|
@@ -4809,6 +4950,22 @@ var CodecStateStore = class {
|
|
|
4809
4950
|
payload: this.dec(buffered.payload)
|
|
4810
4951
|
};
|
|
4811
4952
|
}
|
|
4953
|
+
bufferEvent(input) {
|
|
4954
|
+
return this.inner.bufferEvent({
|
|
4955
|
+
...input,
|
|
4956
|
+
payload: this.enc(input.payload)
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
async listBufferedEvents(name, limit) {
|
|
4960
|
+
const events = await this.inner.listBufferedEvents(name, limit);
|
|
4961
|
+
return events.map((e) => ({
|
|
4962
|
+
...e,
|
|
4963
|
+
payload: this.dec(e.payload)
|
|
4964
|
+
}));
|
|
4965
|
+
}
|
|
4966
|
+
removeBufferedEvent(id) {
|
|
4967
|
+
return this.inner.removeBufferedEvent(id);
|
|
4968
|
+
}
|
|
4812
4969
|
async listRuns(query) {
|
|
4813
4970
|
return (await this.inner.listRuns(query)).map((r) => this.decRun(r));
|
|
4814
4971
|
}
|
|
@@ -5281,6 +5438,28 @@ var InMemoryStateStore = class {
|
|
|
5281
5438
|
payload
|
|
5282
5439
|
};
|
|
5283
5440
|
}
|
|
5441
|
+
/** `id -> row`, so {@link removeBufferedEvent} can delete an exact row regardless of iteration
|
|
5442
|
+
* order — mirrors the SQL stores' PK-by-id delete. */
|
|
5443
|
+
bufferedEvents = /* @__PURE__ */ new Map();
|
|
5444
|
+
async bufferEvent(input) {
|
|
5445
|
+
this.bufferedEvents.set(input.id, {
|
|
5446
|
+
name: input.name,
|
|
5447
|
+
payload: input.payload,
|
|
5448
|
+
publishedAt: input.publishedAt
|
|
5449
|
+
});
|
|
5450
|
+
}
|
|
5451
|
+
async listBufferedEvents(name, limit) {
|
|
5452
|
+
return [
|
|
5453
|
+
...this.bufferedEvents.entries()
|
|
5454
|
+
].filter(([, e]) => e.name === name).sort((a, b) => a[1].publishedAt - b[1].publishedAt).slice(0, limit).map(([id, e]) => ({
|
|
5455
|
+
id,
|
|
5456
|
+
payload: e.payload,
|
|
5457
|
+
publishedAt: e.publishedAt
|
|
5458
|
+
}));
|
|
5459
|
+
}
|
|
5460
|
+
async removeBufferedEvent(id) {
|
|
5461
|
+
return this.bufferedEvents.delete(id);
|
|
5462
|
+
}
|
|
5284
5463
|
/**
|
|
5285
5464
|
* Candidate run ids satisfying ONE attribute predicate, served from {@link attributeIndex} — the
|
|
5286
5465
|
* pushdown step. Numeric predicates scan only `numValue` rows under the key; string/boolean ones
|
|
@@ -5440,6 +5619,8 @@ var InMemoryTransport = class {
|
|
|
5440
5619
|
DURABLE_STEP_CONFIG,
|
|
5441
5620
|
DURABLE_STEP_NAME,
|
|
5442
5621
|
DurableWorkflow,
|
|
5622
|
+
EVENT_BUFFER_SCAN_LIMIT,
|
|
5623
|
+
EVENT_TOKEN_PREFIX,
|
|
5443
5624
|
Entities,
|
|
5444
5625
|
EventAccumulators,
|
|
5445
5626
|
FatalError,
|
|
@@ -5478,6 +5659,7 @@ var InMemoryTransport = class {
|
|
|
5478
5659
|
eventPrefix,
|
|
5479
5660
|
eventToken,
|
|
5480
5661
|
indexWaitersByRun,
|
|
5662
|
+
isEventToken,
|
|
5481
5663
|
isWorkflowControlFlowSignal,
|
|
5482
5664
|
matchesAttributes,
|
|
5483
5665
|
normalizeAttributeRows,
|