@dudousxd/nestjs-durable-core 0.65.0 → 0.67.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 +307 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +330 -7
- package/dist/index.d.ts +330 -7
- package/dist/index.js +299 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1032,6 +1032,16 @@ interface StateStore {
|
|
|
1032
1032
|
*/
|
|
1033
1033
|
transaction?<T>(work: (tx: StoreTransaction) => Promise<T>): Promise<T>;
|
|
1034
1034
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
1035
|
+
/**
|
|
1036
|
+
* Count the runs matching `query`, grouped by `(status, origin)` — the totals behind a console's
|
|
1037
|
+
* status and origin chips, in ONE aggregate instead of a full listing the caller counts in memory.
|
|
1038
|
+
* This is what lets a run list be paginated at all: `listRuns` can return 100 rows while the chips
|
|
1039
|
+
* still say how many of the 9,000 are failed.
|
|
1040
|
+
*
|
|
1041
|
+
* Cells with a zero count are simply absent. Optional: a store that omits it still works — callers
|
|
1042
|
+
* fall back to counting a {@link listRuns} result, which is correct but unbounded.
|
|
1043
|
+
*/
|
|
1044
|
+
runFacets?(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
1035
1045
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
1036
1046
|
/**
|
|
1037
1047
|
* The LATEST checkpoint for `runId` whose `name` equals `name` exactly (highest `seq` wins), or
|
|
@@ -1143,14 +1153,34 @@ interface RunQuery {
|
|
|
1143
1153
|
* Restrict to runs whose workflow was declared by this package (exact match against
|
|
1144
1154
|
* {@link WorkflowRun.origin}), ANDed with the other predicates.
|
|
1145
1155
|
*
|
|
1146
|
-
* A run with NO origin (unknown — see the field) matches no `origin`
|
|
1156
|
+
* A run with NO origin (unknown — see the field) matches no `origin` VALUE at all, so a UI facet
|
|
1147
1157
|
* built on this must keep an "all origins" option: filtering by any single package silently hides
|
|
1148
|
-
* every unattributed run, which reads to an operator as "those runs do not exist".
|
|
1158
|
+
* every unattributed run, which reads to an operator as "those runs do not exist". `null` selects
|
|
1159
|
+
* exactly that absent bucket (`origin IS NULL`), so a paginated console can offer an "unknown"
|
|
1160
|
+
* facet without having to hold every run in the browser to find them.
|
|
1149
1161
|
*/
|
|
1150
|
-
origin?: string | undefined;
|
|
1162
|
+
origin?: string | null | undefined;
|
|
1151
1163
|
limit?: number | undefined;
|
|
1152
1164
|
offset?: number | undefined;
|
|
1153
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* The predicates a {@link RunFacetRow} count is taken over — a {@link RunQuery} minus the axes the
|
|
1168
|
+
* facets THEMSELVES report (`status`/`origin`) and minus paging. Keeping them out of the type is
|
|
1169
|
+
* what makes the counts answerable in one query: a console narrows by tag/tenant/attribute, and the
|
|
1170
|
+
* one call back tells it how the matching runs split across every status and every origin at once.
|
|
1171
|
+
*/
|
|
1172
|
+
type RunFacetQuery = Omit<RunQuery, 'status' | 'statuses' | 'origin' | 'limit' | 'offset'>;
|
|
1173
|
+
/**
|
|
1174
|
+
* One `(status, origin)` cell of {@link StateStore.runFacets} — a `GROUP BY` row, not a page of runs.
|
|
1175
|
+
* A console pivots these into its status chips and its origin chips, so both stay exact while the
|
|
1176
|
+
* list itself is paginated. `origin` is `null` for runs carrying none (see {@link WorkflowRun.origin});
|
|
1177
|
+
* that bucket is a real, countable answer here rather than the absence a value-match cannot express.
|
|
1178
|
+
*/
|
|
1179
|
+
interface RunFacetRow {
|
|
1180
|
+
status: RunStatus;
|
|
1181
|
+
origin: string | null;
|
|
1182
|
+
count: number;
|
|
1183
|
+
}
|
|
1154
1184
|
/** The transaction handle `StateStore.transaction` hands to its work callback. */
|
|
1155
1185
|
interface StoreTransaction {
|
|
1156
1186
|
/** The store-native transaction handle (TypeORM `EntityManager`, Prisma tx client, MikroORM `EntityManager`,
|
|
@@ -1214,6 +1244,9 @@ type RunRequestKind = {
|
|
|
1214
1244
|
} | {
|
|
1215
1245
|
kind: 'listRuns';
|
|
1216
1246
|
query: RunQuery;
|
|
1247
|
+
} | {
|
|
1248
|
+
kind: 'runFacets';
|
|
1249
|
+
query: RunFacetQuery;
|
|
1217
1250
|
} | {
|
|
1218
1251
|
kind: 'workerHealth';
|
|
1219
1252
|
} | {
|
|
@@ -1552,6 +1585,21 @@ interface Transport {
|
|
|
1552
1585
|
* caller could not have enumerated.
|
|
1553
1586
|
*/
|
|
1554
1587
|
readAllWorkerDescriptors?(): Promise<WorkerDescriptor[]>;
|
|
1588
|
+
/**
|
|
1589
|
+
* Every live worker heartbeat in the deployment, across all groups — the LIVENESS floor under
|
|
1590
|
+
* {@link WorkflowEngine.workflowDirectory}. Optional, same as {@link readAllWorkerDescriptors}.
|
|
1591
|
+
*
|
|
1592
|
+
* It exists because a descriptor is the richer advertisement but not the universal one: an SDK
|
|
1593
|
+
* that predates the handshake beats a liveness key and publishes no descriptor at all, and reading
|
|
1594
|
+
* only descriptors reports such a fleet as EMPTY while it is serving work. Convention routing
|
|
1595
|
+
* already resolves those workers off exactly these keys, so a directory built without them
|
|
1596
|
+
* contradicts the dispatcher standing next to it.
|
|
1597
|
+
*
|
|
1598
|
+
* Distinct from {@link listWorkerGroups}, which answers with tokens alone: this carries the
|
|
1599
|
+
* instance behind each one, so the directory can say how many workers back a name and a partition
|
|
1600
|
+
* sighting can name who is beating there.
|
|
1601
|
+
*/
|
|
1602
|
+
readAllWorkerHeartbeats?(): Promise<WorkerHeartbeat[]>;
|
|
1555
1603
|
/**
|
|
1556
1604
|
* DB-less tenant worker → control plane: publish a {@link StartRunMessage} requesting a new run.
|
|
1557
1605
|
* Optional — only transports that carry the hosted-control-plane protocol (P4) implement this.
|
|
@@ -2442,6 +2490,25 @@ declare class EventAccumulators {
|
|
|
2442
2490
|
* picker offering steps would be offering an operation that does not exist. Step HANDLER names are
|
|
2443
2491
|
* still advertised (`WorkerDescriptor.steps`) — that is a routing/capability fact used to decide
|
|
2444
2492
|
* whether a dispatched step can be run, not an invocable catalog.
|
|
2493
|
+
*
|
|
2494
|
+
* ## The floor: a live worker that announces nothing
|
|
2495
|
+
*
|
|
2496
|
+
* An SDK old enough to predate the descriptor advertisement publishes only its TTL'd liveness
|
|
2497
|
+
* heartbeat — one key per routing token it consumes. Read strictly, such a fleet announces nothing
|
|
2498
|
+
* and this registry would be empty while work is being served. That answer is not merely unhelpful,
|
|
2499
|
+
* it CONTRADICTS the engine beside it: `resolveRemoteByConvention` routes a call to `X` the moment a
|
|
2500
|
+
* token named `X` heartbeats, so the deployment already treats liveness as sufficient to CALL a
|
|
2501
|
+
* workflow while claiming to know nothing when asked to LIST one. Both answers come out of the same
|
|
2502
|
+
* Redis.
|
|
2503
|
+
*
|
|
2504
|
+
* So the heartbeat is folded in as a second, weaker tier of evidence ({@link AnnouncedWorkflow.evidence}
|
|
2505
|
+
* `'observed'`): the entry exists because a live token of that name exists, which is exactly — no more
|
|
2506
|
+
* and no less — the condition under which convention routing would resolve it. Listing it therefore
|
|
2507
|
+
* cannot introduce a failure the dispatcher did not already have. What it CANNOT do is state a
|
|
2508
|
+
* version, an origin, a runtime, or even that the token serves a workflow rather than a step handler
|
|
2509
|
+
* of the same name: none of that is in a heartbeat, and every one of those stays un-stated rather than
|
|
2510
|
+
* being guessed. A descriptor always wins — a token an announcement already covers is never re-added
|
|
2511
|
+
* as an observation.
|
|
2445
2512
|
*/
|
|
2446
2513
|
|
|
2447
2514
|
/** One live worker's claim about one workflow: the registration plus who announced it. */
|
|
@@ -2457,25 +2524,46 @@ interface Disagreement {
|
|
|
2457
2524
|
axis: 'group' | 'origin' | 'requires';
|
|
2458
2525
|
values: string[];
|
|
2459
2526
|
}
|
|
2527
|
+
/**
|
|
2528
|
+
* How strong the fleet's claim about a workflow is. The two tiers are NOT interchangeable and a
|
|
2529
|
+
* caller must not read them as the same fact:
|
|
2530
|
+
*
|
|
2531
|
+
* - `'declared'` — at least one live worker published a descriptor naming this workflow. Everything
|
|
2532
|
+
* populated on the entry was STATED by that worker; everything absent it declined to state.
|
|
2533
|
+
* - `'observed'` — nobody described it. What exists is a live routing token of this name, which is
|
|
2534
|
+
* precisely the condition `resolveRemoteByConvention` uses to route a call, so the name IS
|
|
2535
|
+
* reachable. Nothing else is known: no version, no origin, no runtime, and no assurance that the
|
|
2536
|
+
* worker consuming that token serves a WORKFLOW there rather than a step handler of the same name.
|
|
2537
|
+
* An entry like this is a live queue, not a promise.
|
|
2538
|
+
*/
|
|
2539
|
+
type AnnouncementEvidence = 'declared' | 'observed';
|
|
2460
2540
|
/** One workflow the live fleet announces, folded across every worker that announced it. */
|
|
2461
2541
|
interface AnnouncedWorkflow {
|
|
2542
|
+
/** What kind of claim this entry rests on — see {@link AnnouncementEvidence}. A caller that shows
|
|
2543
|
+
* a version, an origin or a runtime must check this first: an `'observed'` entry has none of
|
|
2544
|
+
* them, and the emptiness is a fact about the fleet, not a rendering gap. */
|
|
2545
|
+
evidence: AnnouncementEvidence;
|
|
2462
2546
|
/** `name@version`, or the bare `name` when no announcer stated a version. Stable sort key. */
|
|
2463
2547
|
key: string;
|
|
2464
2548
|
name: string;
|
|
2465
2549
|
/** Absent when no announcer stated one — never inferred from another announcer's version. */
|
|
2466
2550
|
version?: string;
|
|
2467
|
-
/** Every distinct routing group announced (sorted). Empty = nobody stated one.
|
|
2551
|
+
/** Every distinct routing group announced (sorted). Empty = nobody stated one. On an `'observed'`
|
|
2552
|
+
* entry this is the token the heartbeat was published UNDER — not a claim by anybody, but the
|
|
2553
|
+
* queue a call would actually land on, which is the one thing an observation does establish. */
|
|
2468
2554
|
groups: string[];
|
|
2469
2555
|
/** Every distinct declaring package announced (sorted). Empty = nobody stated one. */
|
|
2470
2556
|
origins: string[];
|
|
2471
2557
|
/** Every distinct capability demand announced, each de-duplicated + sorted. Empty = nobody stated
|
|
2472
2558
|
* one. Two entries here means two workers demand different capabilities for the same version. */
|
|
2473
2559
|
requires: string[][];
|
|
2474
|
-
/** Runtimes of the live announcers (sorted, de-duplicated).
|
|
2560
|
+
/** Runtimes of the live announcers (sorted, de-duplicated). EMPTY on an `'observed'` entry: a
|
|
2561
|
+
* heartbeat does not say what runtime wrote it, and the instance id's shape is a convention, not
|
|
2562
|
+
* a statement, so it is not read as one. */
|
|
2475
2563
|
runtimes: ('node' | 'python')[];
|
|
2476
2564
|
/** Instance ids of the live workers announcing it (sorted, de-duplicated). Its length is how many
|
|
2477
2565
|
* workers can currently run this workflow — `1` is a single point of failure, and it is never 0
|
|
2478
|
-
* (an entry exists only because somebody announced it). */
|
|
2566
|
+
* (an entry exists only because somebody announced it, or because somebody is beating on it). */
|
|
2479
2567
|
instances: string[];
|
|
2480
2568
|
/** The axes the announcers differ on, empty when they speak with one voice. A caller that must
|
|
2481
2569
|
* pick a single target has to resolve these itself — the registry refuses to guess. */
|
|
@@ -2502,6 +2590,151 @@ declare function announcementsOf(raw: RawWorkerDescriptor | WorkerDescriptor): W
|
|
|
2502
2590
|
* `instances` is a set, so it counts workers, not keys.
|
|
2503
2591
|
*/
|
|
2504
2592
|
declare function aggregateAnnouncements(descriptors: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): AnnouncedWorkflow[];
|
|
2593
|
+
/**
|
|
2594
|
+
* The part of one live worker heartbeat this registry can read: the routing token the beat was
|
|
2595
|
+
* published under, and which process published it. That is genuinely all a heartbeat holds — it is
|
|
2596
|
+
* the cheap tier of the two-tier advertisement, and its job is liveness, not description.
|
|
2597
|
+
*
|
|
2598
|
+
* Structurally satisfied by `WorkerHeartbeat` (the transport-facing shape, which also carries a beat
|
|
2599
|
+
* time and an execution snapshot). Declared here rather than imported so this module keeps its "pure
|
|
2600
|
+
* data, no transport" footing and `interfaces.ts` can keep importing FROM it.
|
|
2601
|
+
*/
|
|
2602
|
+
interface HeartbeatSighting {
|
|
2603
|
+
/** The routing token the beat was published under (already partition-suffixed when partitioned). */
|
|
2604
|
+
group: string;
|
|
2605
|
+
/** The beating process's instance id. */
|
|
2606
|
+
instanceId: string;
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* The weaker tier: one entry per live routing token that NO descriptor accounts for.
|
|
2610
|
+
*
|
|
2611
|
+
* `covered` holds every token the declared announcements already explain — both the sanitized form of
|
|
2612
|
+
* each announced NAME and every group any announcer stated. A token in that set is dropped, so a
|
|
2613
|
+
* fleet that describes itself properly gets exactly the same registry it got before this tier
|
|
2614
|
+
* existed, and a worker is never counted twice under two kinds of evidence.
|
|
2615
|
+
*
|
|
2616
|
+
* Everything an observation cannot know stays empty rather than being filled with a plausible guess:
|
|
2617
|
+
* no version (so nothing can be pinned against it), no origin, no requires, no runtime. The `name` is
|
|
2618
|
+
* the token itself, which equals the workflow's name for every name a queue token can round-trip —
|
|
2619
|
+
* `sanitizeQueueToken` rewrites `:` to `-` and that rewrite is not invertible, so a workflow named
|
|
2620
|
+
* `orders:fulfill` is observed as `orders-fulfill`. Stated here because a picker showing that string
|
|
2621
|
+
* should show what the queue is really called, not a reconstruction that might be wrong.
|
|
2622
|
+
*/
|
|
2623
|
+
declare function observedAnnouncements(heartbeats: readonly HeartbeatSighting[], covered: ReadonlySet<string>): AnnouncedWorkflow[];
|
|
2624
|
+
/**
|
|
2625
|
+
* Every routing token a live descriptor already ACCOUNTS FOR, and which therefore needs no
|
|
2626
|
+
* observation — for either of the two opposite reasons.
|
|
2627
|
+
*
|
|
2628
|
+
* - A token some worker announced as a WORKFLOW: already in the registry, richer than an
|
|
2629
|
+
* observation could ever be, and re-adding it would count one worker twice.
|
|
2630
|
+
* - A token some worker declared as a STEP HANDLER (`WorkerDescriptor.steps`): it must be left OUT,
|
|
2631
|
+
* because a step is not addressable from outside a run and offering one as callable would be
|
|
2632
|
+
* offering an operation the engine does not have. Route-by-handler gives every step its own queue,
|
|
2633
|
+
* so without this the liveness floor turns a fleet's entire step surface into fake workflows.
|
|
2634
|
+
*
|
|
2635
|
+
* That second exclusion is a DECLARED negative — the worker said "this name is a step" — which is
|
|
2636
|
+
* exactly the sort of fact an observation cannot supply for itself, and the clearest illustration of
|
|
2637
|
+
* what the two tiers are each worth. A worker on an SDK too old to describe itself declares neither
|
|
2638
|
+
* its workflows nor its steps, so every one of its tokens stays observed and indistinguishable: its
|
|
2639
|
+
* step queues are listed alongside its workflow queues because nothing published says which is
|
|
2640
|
+
* which. That is a fair report of the fleet, not a defect in the read.
|
|
2641
|
+
*
|
|
2642
|
+
* `sanitize` is injected so this module keeps no dependency on the queue layer; a declared name is
|
|
2643
|
+
* covered in its bare token form and, for a partitioned worker, in the suffixed form its queues
|
|
2644
|
+
* actually carry.
|
|
2645
|
+
*/
|
|
2646
|
+
declare function coveredTokens(announced: readonly AnnouncedWorkflow[], descriptors: readonly (RawWorkerDescriptor | WorkerDescriptor)[], sanitize: (name: string) => string): Set<string>;
|
|
2647
|
+
|
|
2648
|
+
/**
|
|
2649
|
+
* The workflow directory — {@link ./announced}'s registry plus the thing an empty registry could
|
|
2650
|
+
* never say: WHY it is empty.
|
|
2651
|
+
*
|
|
2652
|
+
* ## The three states an empty list used to collapse
|
|
2653
|
+
*
|
|
2654
|
+
* "What workflows can I call?" has three honest answers, and returning `[]` for all of them is the
|
|
2655
|
+
* bug this module exists to fix. They are not shades of the same answer; they call for three
|
|
2656
|
+
* different actions from whoever asked.
|
|
2657
|
+
*
|
|
2658
|
+
* 1. **This process cannot ask.** No transport here can introspect the advertisement or liveness
|
|
2659
|
+
* keyspace — a pure in-process pool advertises nothing and scans nothing. Workers may well be
|
|
2660
|
+
* running; nobody looked. ({@link WorkflowDirectory.supported} is `false`.) The action is to
|
|
2661
|
+
* configure a transport, or to stop treating this pod's answer as the deployment's answer.
|
|
2662
|
+
* 2. **Nothing is live.** The fleet WAS asked and the fleet is empty. ({@link WorkflowDirectory.supported}
|
|
2663
|
+
* is `true`, `workflows` is `[]`.) The action is to start a worker.
|
|
2664
|
+
* 3. **Here it is.** ({@link WorkflowDirectory.supported} is `true`, `workflows` is populated.)
|
|
2665
|
+
*
|
|
2666
|
+
* There is a fourth that hides inside the second and is the most expensive to debug, because it
|
|
2667
|
+
* looks exactly like "nobody is running" from the caller's side: **a worker is live on a partition
|
|
2668
|
+
* this engine does not serve.** A worker started with a partition consumes `<token>@<partition>`,
|
|
2669
|
+
* convention resolution computes the token for THIS engine's namespace, misses, and reports absence.
|
|
2670
|
+
* The heartbeat keyspace is shared, so the evidence is right there — and
|
|
2671
|
+
* {@link WorkflowDirectory.otherPartitions} reports it instead of letting the caller conclude that
|
|
2672
|
+
* nothing exists.
|
|
2673
|
+
*
|
|
2674
|
+
* ## Why a sentence rides along
|
|
2675
|
+
*
|
|
2676
|
+
* Every field here is structured and a caller can branch on all of it. {@link WorkflowDirectory.detail}
|
|
2677
|
+
* exists anyway because the three states are only useful if the human reading the picker learns which
|
|
2678
|
+
* one they are in, and a UI that has to re-derive that prose from booleans will re-derive it
|
|
2679
|
+
* differently in each place it is shown. The sentence is written once, next to the logic that decides
|
|
2680
|
+
* which state it is.
|
|
2681
|
+
*/
|
|
2682
|
+
|
|
2683
|
+
/** The namespace a descriptor or a bare routing token belongs to when it states none. Mirrors
|
|
2684
|
+
* `tenantGroup`'s default-is-bare rule, so a single-tenant deployment reads as `'default'`
|
|
2685
|
+
* everywhere rather than as a special case. */
|
|
2686
|
+
declare const DEFAULT_PARTITION = "default";
|
|
2687
|
+
/** Live workers seen on a partition the asking engine does not serve — the case that otherwise reads
|
|
2688
|
+
* identically to "nothing is running". */
|
|
2689
|
+
interface PartitionSighting {
|
|
2690
|
+
/** The partition suffix carried by the tokens (never this engine's own). */
|
|
2691
|
+
partition: string;
|
|
2692
|
+
/** The routing tokens live on it, as published (sorted). */
|
|
2693
|
+
groups: string[];
|
|
2694
|
+
/** The instance ids beating there (sorted). */
|
|
2695
|
+
instances: string[];
|
|
2696
|
+
}
|
|
2697
|
+
/** What the live fleet can be asked, and what it answered. See the module docblock for the states. */
|
|
2698
|
+
interface WorkflowDirectory {
|
|
2699
|
+
/**
|
|
2700
|
+
* Could this process ask the fleet at all?
|
|
2701
|
+
*
|
|
2702
|
+
* `false` means no transport here can read the advertisement or liveness keyspace, so `workflows`
|
|
2703
|
+
* is empty because nothing was asked — NOT because nothing is running. `true` means the read
|
|
2704
|
+
* happened and `workflows` is the answer, including when it is empty.
|
|
2705
|
+
*/
|
|
2706
|
+
supported: boolean;
|
|
2707
|
+
/** Everything callable in this engine's partition, declared and observed alike. */
|
|
2708
|
+
workflows: AnnouncedWorkflow[];
|
|
2709
|
+
/** Live workers on OTHER partitions (empty for an operator engine, which serves them all). */
|
|
2710
|
+
otherPartitions: PartitionSighting[];
|
|
2711
|
+
/** One sentence saying which state this is and what it implies. Written for a human. */
|
|
2712
|
+
detail: string;
|
|
2713
|
+
}
|
|
2714
|
+
/** The partition a routing token belongs to: the suffix after the LAST `@`, or {@link DEFAULT_PARTITION}
|
|
2715
|
+
* for a bare token. Matches how `tenantGroup` builds the token and how `workerHealth` takes it apart. */
|
|
2716
|
+
declare function partitionOfToken(token: string): string;
|
|
2717
|
+
/**
|
|
2718
|
+
* Fold live descriptors and live heartbeats into the directory for one engine.
|
|
2719
|
+
*
|
|
2720
|
+
* `namespace` is the asking engine's, and `undefined` means an operator that serves every partition
|
|
2721
|
+
* ("ver tudo = ausência de namespace"): it filters nothing and reports no foreign partitions, because
|
|
2722
|
+
* none of them are foreign to it. A namespaced engine sees only its own partition's workers, and
|
|
2723
|
+
* everything else it can see is reported as a sighting rather than silently dropped — dropping is
|
|
2724
|
+
* what made a misconfigured partition indistinguishable from an empty fleet.
|
|
2725
|
+
*
|
|
2726
|
+
* Pure and holds nothing: the caller does the two live reads it already knows how to do, folds them
|
|
2727
|
+
* here, and drops the result.
|
|
2728
|
+
*/
|
|
2729
|
+
declare function buildWorkflowDirectory(input: {
|
|
2730
|
+
descriptors: readonly (RawWorkerDescriptor | WorkerDescriptor)[];
|
|
2731
|
+
heartbeats: readonly HeartbeatSighting[];
|
|
2732
|
+
namespace: string | undefined;
|
|
2733
|
+
/** Whether ANY transport in the pool could perform the reads above. */
|
|
2734
|
+
supported: boolean;
|
|
2735
|
+
/** The queue-token form of a workflow name — injected so this module needs no queue dependency. */
|
|
2736
|
+
sanitize: (name: string) => string;
|
|
2737
|
+
}): WorkflowDirectory;
|
|
2505
2738
|
|
|
2506
2739
|
/**
|
|
2507
2740
|
* Bilateral compatibility negotiation between two handshake descriptors (design §7.3/§7.4). Pure
|
|
@@ -2702,6 +2935,30 @@ interface SingletonConfig {
|
|
|
2702
2935
|
*/
|
|
2703
2936
|
maxQueueDepth?: number;
|
|
2704
2937
|
}
|
|
2938
|
+
/**
|
|
2939
|
+
* Stamped on a run whose workflow version NOBODY declared: a remote resolved by convention, served
|
|
2940
|
+
* by a worker that announces no version for it (an SDK too old to publish a descriptor, or a body
|
|
2941
|
+
* whose author simply never stated one).
|
|
2942
|
+
*
|
|
2943
|
+
* ## Why a run has to carry this
|
|
2944
|
+
*
|
|
2945
|
+
* Such a run's `workflowVersion` is `'1'`, the engine's convention default. That value is a routing
|
|
2946
|
+
* placeholder, not an observation — and read back later it is indistinguishable from a worker that
|
|
2947
|
+
* genuinely declared version `'1'`. Any check comparing an authored pin against it therefore passes
|
|
2948
|
+
* whenever the pin is `'1'` and fails whenever it is not, regardless of what the callee actually is.
|
|
2949
|
+
* A check that cannot fail is worse than no check, because it reads as a guarantee.
|
|
2950
|
+
*
|
|
2951
|
+
* The tag is the difference, carried ON the run so a retrospective check needs no second read: a
|
|
2952
|
+
* caller that pinned a version can see that the version it is comparing against was never stated,
|
|
2953
|
+
* and say so, instead of reporting a match it did not really make. It is deliberately a tag rather
|
|
2954
|
+
* than a new column — every store already persists and queries tags, so this needs no migration and
|
|
2955
|
+
* shows up in run listings and dashboards for free. Follows the `singleton:<key>` precedent for an
|
|
2956
|
+
* engine-stamped reserved tag.
|
|
2957
|
+
*
|
|
2958
|
+
* A run whose callee DOES declare a version never carries it; that run's version is the declared one
|
|
2959
|
+
* and a pin against it is genuinely checked.
|
|
2960
|
+
*/
|
|
2961
|
+
declare const VERSION_UNDECLARED_TAG = "version:undeclared";
|
|
2705
2962
|
interface WorkflowEngineDeps {
|
|
2706
2963
|
store: StateStore;
|
|
2707
2964
|
/** A single task transport. Shorthand for a one-entry `transports` pool (id `default`). */
|
|
@@ -3143,6 +3400,21 @@ declare class WorkflowEngine {
|
|
|
3143
3400
|
* liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
|
|
3144
3401
|
*/
|
|
3145
3402
|
private resolveRemoteByConvention;
|
|
3403
|
+
/**
|
|
3404
|
+
* The version the LIVE fleet declares for `workflow` on `group`, or `undefined` when it declares
|
|
3405
|
+
* none — which is a fact about the fleet, not a failure to read it.
|
|
3406
|
+
*
|
|
3407
|
+
* The read is the descriptor advertisement, scoped to the one group being resolved: the workers on
|
|
3408
|
+
* that queue, their `registrations`, the entry whose `name` matches. That is where a version can
|
|
3409
|
+
* come from at all — a heartbeat has no room for one, so a worker on an SDK too old to publish a
|
|
3410
|
+
* descriptor declares nothing and the caller is told so rather than handed a default.
|
|
3411
|
+
*
|
|
3412
|
+
* `undefined` when the announcers DISAGREE, and deliberately: two live workers claiming different
|
|
3413
|
+
* versions of one name is a deployment in the middle of something, and picking either would stamp
|
|
3414
|
+
* a run with a version half the fleet does not serve. Undeclared is the honest reading of a
|
|
3415
|
+
* contradiction, and it is the reading that refuses to let a pin pass on a coin flip.
|
|
3416
|
+
*/
|
|
3417
|
+
private declaredVersion;
|
|
3146
3418
|
/**
|
|
3147
3419
|
* Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
|
|
3148
3420
|
* registration (or `undefined` if there is none). The parent of an awaited child is found via its
|
|
@@ -3511,6 +3783,21 @@ declare class WorkflowEngine {
|
|
|
3511
3783
|
* Workflows only, and deliberately: see the "steps are out of scope" note in `handshake/announced`.
|
|
3512
3784
|
*/
|
|
3513
3785
|
announcedWorkflows(): Promise<AnnouncedWorkflow[]>;
|
|
3786
|
+
/**
|
|
3787
|
+
* {@link announcedWorkflows}, plus the thing a list of workflows can never say: why it is the
|
|
3788
|
+
* length it is. See {@link WorkflowDirectory} for the three states an empty array used to collapse
|
|
3789
|
+
* into one — "nobody asked", "nothing is live", and the partition mismatch that reads exactly like
|
|
3790
|
+
* the second from the caller's side.
|
|
3791
|
+
*
|
|
3792
|
+
* Two live reads, both on demand and neither retained: the descriptor advertisement (what workers
|
|
3793
|
+
* SAY they serve) and the heartbeat keyspace (what is demonstrably consuming a queue right now).
|
|
3794
|
+
* The second is why a worker on an SDK too old to publish a descriptor appears here at all — it
|
|
3795
|
+
* beats and never describes itself, and reading only descriptors reported such a fleet as empty
|
|
3796
|
+
* while `resolveRemoteByConvention`, a few lines away in this same class, was routing calls to it
|
|
3797
|
+
* off those very keys. Two answers out of one Redis, disagreeing. Now they agree, and the entry
|
|
3798
|
+
* says which of the two kinds of evidence it rests on rather than flattening them together.
|
|
3799
|
+
*/
|
|
3800
|
+
workflowDirectory(): Promise<WorkflowDirectory>;
|
|
3514
3801
|
/**
|
|
3515
3802
|
* The routing groups of every in-flight run's still-`pending` remote step — the queues where work is
|
|
3516
3803
|
* dispatched and awaiting a worker. Folded into {@link workerHealth} so a step waiting on a group
|
|
@@ -4090,6 +4377,25 @@ declare class RemoteWorkflowExecutor implements WorkflowExecutor {
|
|
|
4090
4377
|
dispatch(run: WorkflowRun, history: HistoryEvent[], taskId: string, pendingSignals?: WorkflowTask['pendingSignals']): Promise<void>;
|
|
4091
4378
|
}
|
|
4092
4379
|
|
|
4380
|
+
/**
|
|
4381
|
+
* The origin a facet cell is filed under. An origin that is absent, null, or blank is all the same
|
|
4382
|
+
* thing to an operator — a run no package claims — so they collapse to `null` here. Without this the
|
|
4383
|
+
* "unknown" chip would count only one of those spellings while `origin IS NULL` paging returned
|
|
4384
|
+
* another, and the chip's number would not match the page it opens.
|
|
4385
|
+
*/
|
|
4386
|
+
declare function facetOrigin(origin: string | null | undefined): string | null;
|
|
4387
|
+
/**
|
|
4388
|
+
* Fold `(status, origin)` counts into one cell per pair, normalising origins via {@link facetOrigin}.
|
|
4389
|
+
* Store adapters group in SQL and pipe their rows through this, so every adapter — and the in-memory
|
|
4390
|
+
* store the tests run against — agrees on what lands in the unattributed bucket instead of each
|
|
4391
|
+
* collapsing blanks its own way.
|
|
4392
|
+
*/
|
|
4393
|
+
declare function mergeRunFacetRows(rows: readonly {
|
|
4394
|
+
status: RunStatus;
|
|
4395
|
+
origin: string | null | undefined;
|
|
4396
|
+
count: number;
|
|
4397
|
+
}[]): RunFacetRow[];
|
|
4398
|
+
|
|
4093
4399
|
/** A run as returned by the gateway's list — the durable run plus an optional {@link RunWaiting}
|
|
4094
4400
|
* descriptor the control plane resolves (what a suspended run is parked on), so the dashboard can
|
|
4095
4401
|
* name the wait in a list row without fetching each run's timeline. Additive: `waiting` is absent
|
|
@@ -4147,6 +4453,12 @@ declare abstract class RunGateway {
|
|
|
4147
4453
|
* parked on). The store-backed gateway resolves `waiting`; a proxy relays whatever the control
|
|
4148
4454
|
* plane sent. */
|
|
4149
4455
|
abstract listRuns(query: RunQuery): Promise<RunListItem[]>;
|
|
4456
|
+
/**
|
|
4457
|
+
* Counts of the runs matching `query`, grouped by `(status, origin)` — what a console's status and
|
|
4458
|
+
* origin chips display. Separate from {@link listRuns} precisely so the list can be PAGINATED: the
|
|
4459
|
+
* page bounds what is rendered, these counts stay whole-set exact. Zero-count cells are absent.
|
|
4460
|
+
*/
|
|
4461
|
+
abstract runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
4150
4462
|
/**
|
|
4151
4463
|
* Bulk-resolve {@link RunWaiting} for an arbitrary set of run ids — for a consumer with its OWN
|
|
4152
4464
|
* filtered/paginated run listing (so it isn't just `listRuns` under another name) that needs to know
|
|
@@ -4225,6 +4537,13 @@ interface PayloadCodec {
|
|
|
4225
4537
|
declare class CodecStateStore implements StateStore {
|
|
4226
4538
|
private readonly inner;
|
|
4227
4539
|
private readonly codec;
|
|
4540
|
+
/**
|
|
4541
|
+
* Forwarded verbatim — a facet cell counts `status`/`origin`, neither of which the codec touches
|
|
4542
|
+
* (it encodes payloads: input/output/error). Bound as a PROPERTY, and only when the inner store
|
|
4543
|
+
* actually aggregates, so `store.runFacets?.()` still reports absence through the wrapper instead
|
|
4544
|
+
* of resolving to a silently wrong empty result.
|
|
4545
|
+
*/
|
|
4546
|
+
readonly runFacets?: (query: RunFacetQuery) => Promise<RunFacetRow[]>;
|
|
4228
4547
|
constructor(inner: StateStore, codec: PayloadCodec);
|
|
4229
4548
|
private enc;
|
|
4230
4549
|
private dec;
|
|
@@ -4678,7 +4997,11 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
4678
4997
|
* scan `strValue` rows. This is the in-memory analog of the SQL EXISTS on `durable_run_attributes`.
|
|
4679
4998
|
*/
|
|
4680
4999
|
private candidatesForFilter;
|
|
5000
|
+
/** Every run satisfying `query`'s predicates, unsorted and unpaged — the shared body of
|
|
5001
|
+
* {@link listRuns} (which then sorts + pages it) and {@link runFacets} (which groups it). */
|
|
5002
|
+
private matching;
|
|
4681
5003
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
5004
|
+
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
4682
5005
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
4683
5006
|
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
4684
5007
|
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
@@ -4710,4 +5033,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
|
|
|
4710
5033
|
onControl(handler: (msg: ControlMessage) => void): void;
|
|
4711
5034
|
}
|
|
4712
5035
|
|
|
4713
|
-
export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AnnouncedWorkflow, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, announcementsOf, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, describeWorker, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };
|
|
5036
|
+
export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AnnouncedWorkflow, type AnnouncementEvidence, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DEFAULT_PARTITION, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatSighting, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PartitionSighting, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunFacetQuery, type RunFacetRow, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, VERSION_UNDECLARED_TAG, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, type WorkflowDirectory, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, announcementsOf, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, buildWorkflowDirectory, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, coveredTokens, createStepLogger, currentStep, currentWorkflowCtx, debug, describeWorker, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, facetOrigin, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, mergeRunFacetRows, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, observedAnnouncements, parseDuration, partitionOfToken, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };
|