@rotorsoft/act 1.32.23 → 1.32.24
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/.tsbuildinfo +1 -1
- package/dist/@types/internal/audit.d.ts +5 -45
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +23 -163
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +19 -129
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +21 -158
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/index.cjs +29 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +29 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,35 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* @category Internal
|
|
4
|
-
*
|
|
5
|
-
* Operator-driven store audit.
|
|
6
|
-
*
|
|
7
|
-
* Walks the connected store and yields per-category {@link AuditFinding}s.
|
|
8
|
-
* Each category answers a different "what should I do with this store?"
|
|
9
|
-
* question and pairs with a remediation:
|
|
10
|
-
*
|
|
11
|
-
* - `schema` → fix the data model (poison events, unknown names)
|
|
12
|
-
* - `close-candidate` → `app.close([...])`
|
|
13
|
-
* - `restart-candidate` → `app.close([{stream, restart:true}, …])`
|
|
14
|
-
* - `deprecated-load` → `app.close([...])` on the heaviest carriers
|
|
15
|
-
* - `reaction-health` → `app.unblock(...)` / `app.reset(...)`
|
|
16
|
-
* - `snapshot-drift` → manual `load({snap:true})` or wait for policy
|
|
17
|
-
* - `routing-health` → restart-with-new-config to re-lane
|
|
18
|
-
* - `correlation-gaps` → fix upstream correlator misconfig
|
|
19
|
-
* - `clock-anomalies` → infra remediation (clock skew)
|
|
20
|
-
*
|
|
21
|
-
* ## Single-scan multiplex
|
|
22
|
-
*
|
|
23
|
-
* Each category is an {@link AuditPass} with optional per-row callbacks
|
|
24
|
-
* (`on_event` / `on_stream` / `on_stat`) and a `finalize` hook. The
|
|
2
|
+
* Operator-driven store audit. Each category is an {@link AuditPass}; the
|
|
25
3
|
* dispatcher runs each needed scan once and broadcasts rows to every pass,
|
|
26
|
-
* so an audit costs at most three scans
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* Reads only the {@link AuditDeps} bag and the public `Store`; never the
|
|
30
|
-
* orchestration internals.
|
|
31
|
-
*
|
|
32
|
-
* @internal
|
|
4
|
+
* so an audit costs at most three scans. Reads only {@link AuditDeps} and
|
|
5
|
+
* the public `Store`, never orchestration internals.
|
|
33
6
|
*/
|
|
34
7
|
import type { AuditCategory, AuditFinding, AuditOptions, State, Store } from "../types/index.js";
|
|
35
8
|
/** The orchestrator state the audit reads, built once at `build()`. */
|
|
@@ -39,22 +12,9 @@ export type AuditDeps = {
|
|
|
39
12
|
readonly event_to_state: ReadonlyMap<string, State<any, any, any>>;
|
|
40
13
|
/** Declared drain lanes (for routing-health unknown-lane). */
|
|
41
14
|
readonly declared_lanes: ReadonlySet<string>;
|
|
42
|
-
/**
|
|
43
|
-
* Event names that the registry has at least one reaction for —
|
|
44
|
-
* used by routing-health to detect "registered but unrouted"
|
|
45
|
-
* events. Normalized down from the internal `event_to_lanes` map
|
|
46
|
-
* (which carries lane-set details audit doesn't need).
|
|
47
|
-
*/
|
|
15
|
+
/** Event names with at least one reaction (routing-health `unrouted`). */
|
|
48
16
|
readonly routed_events: ReadonlySet<string>;
|
|
49
17
|
};
|
|
50
|
-
/**
|
|
51
|
-
* Top-level audit dispatcher. Single-scan multiplex: each requested
|
|
52
|
-
* category contributes a `AuditPass`, the dispatcher determines the
|
|
53
|
-
* union of required data sources (events / streams / stats), runs
|
|
54
|
-
* each once, broadcasts rows, and yields per-category findings in
|
|
55
|
-
* the order the categories were requested.
|
|
56
|
-
* Scans finish before the first yield; bound the work with
|
|
57
|
-
* `options.query`.
|
|
58
|
-
*/
|
|
18
|
+
/** Scans finish before the first yield; bound the work with `options.query`. */
|
|
59
19
|
export declare function audit(deps: AuditDeps, categories?: AuditCategory[], options?: AuditOptions): AsyncIterable<AuditFinding>;
|
|
60
20
|
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/internal/audit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/internal/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EAIZ,KAAK,EACL,KAAK,EAGN,MAAM,mBAAmB,CAAC;AAQ3B,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACnE,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC7C,CAAC;AA0CF,gFAAgF;AAChF,wBAAuB,KAAK,CAC1B,IAAI,EAAE,SAAS,EACf,UAAU,CAAC,EAAE,aAAa,EAAE,EAC5B,OAAO,GAAE,YAAiB,GACzB,aAAa,CAAC,YAAY,CAAC,CAsD7B"}
|
|
@@ -1,33 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Correlation — the discovery half of the correlate→drain pair. Owns the
|
|
6
|
-
* lazy init (subscribe static targets, read cold-start watermark) and the
|
|
7
|
-
* scan that resolves each event to its target streams.
|
|
8
|
-
*
|
|
9
|
-
* The scan is also the **producer of the work mark**: every target
|
|
10
|
-
* an event resolves to is subscribed with `correlated_at` = that event's
|
|
11
|
-
* id, which is how `claim` answers "does this stream have work?" off the
|
|
12
|
-
* subscription row instead of probing the event log.
|
|
13
|
-
*
|
|
14
|
-
* The Act orchestrator passes registry + classification (which static
|
|
15
|
-
* targets to subscribe) at build time; everything past that lives here.
|
|
16
|
-
*
|
|
17
|
-
* @internal
|
|
2
|
+
* Correlation, the discovery half of correlate→drain: resolves each event to
|
|
3
|
+
* its target streams and raises each target's work mark (`correlated_at`),
|
|
4
|
+
* which is what `claim` reads. See docs/architecture/correlation-and-drain.
|
|
18
5
|
*/
|
|
19
6
|
import type { Query, Registry, SchemaRegister, Schemas } from "../types/index.js";
|
|
20
7
|
import type { DrainOps } from "./drain.js";
|
|
21
|
-
/**
|
|
22
|
-
* Static resolver target collected at build time. Subscribed once during
|
|
23
|
-
* init, then marked by every scan whose events resolve to it.
|
|
24
|
-
*
|
|
25
|
-
* @property priority - Scheduling priority for the resolved target stream.
|
|
26
|
-
* Combined with peers via `max()` at build time when multiple reactions
|
|
27
|
-
* target the same stream — see `build-classify.ts`.
|
|
28
|
-
*
|
|
29
|
-
* @internal
|
|
30
|
-
*/
|
|
8
|
+
/** Static resolver target collected at build time and subscribed at init. */
|
|
31
9
|
export type StaticTarget = {
|
|
32
10
|
readonly stream: string;
|
|
33
11
|
readonly source?: string;
|
|
@@ -40,186 +18,68 @@ export type CorrelateCycleDeps<TSchemaReg extends SchemaRegister<TActions>, TEve
|
|
|
40
18
|
static_targets: ReadonlyArray<StaticTarget>;
|
|
41
19
|
cd: DrainOps<TEvents>;
|
|
42
20
|
max_subscribed_streams: number;
|
|
43
|
-
/**
|
|
44
|
-
* Every lane a controller exists for — `"default"` plus each
|
|
45
|
-
* `.withLane({name})`. Injected rather than derived: `internal/` receives
|
|
46
|
-
* what it needs. A resolution naming anything outside this set has no
|
|
47
|
-
* claimant, so correlate reroutes it here.
|
|
48
|
-
*/
|
|
21
|
+
/** Every lane a controller exists for; others are rerouted to default. */
|
|
49
22
|
declared_lanes: ReadonlySet<string>;
|
|
50
|
-
/**
|
|
51
|
-
* The lanes this worker drains, set only when `onlyLanes` leaves some
|
|
52
|
-
* declared lane out. Workers on different shards are not interchangeable,
|
|
53
|
-
* so each shard gets its own correlation lease.
|
|
54
|
-
*/
|
|
23
|
+
/** Set when `onlyLanes` leaves a lane out; each shard gets its own lease. */
|
|
55
24
|
shard?: ReadonlyArray<string>;
|
|
56
25
|
on_init?: () => void;
|
|
57
26
|
on_init_async?: () => Promise<void>;
|
|
58
27
|
};
|
|
59
|
-
/**
|
|
60
|
-
* Drives correlation for one Act instance. Owns the checkpoint, the
|
|
61
|
-
* correlation lease and the subscription records.
|
|
62
|
-
*
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
28
|
+
/** Drives correlation for one Act: the checkpoint, the lease, the subscription records. */
|
|
65
29
|
export declare class CorrelateCycle<TSchemaReg extends SchemaRegister<TActions>, TEvents extends Schemas, TActions extends Schemas> {
|
|
66
30
|
private _checkpoint;
|
|
67
31
|
private _initialized;
|
|
68
|
-
/**
|
|
69
|
-
* This worker's identity for the correlation lease. A per-instance
|
|
70
|
-
* UUID, matching the drain's convention, so a renewal is recognised as the
|
|
71
|
-
* same holder and a restarted process never inherits a stale claim.
|
|
72
|
-
*/
|
|
32
|
+
/** Lease holder id; per instance, so a restart never inherits a stale claim. */
|
|
73
33
|
private readonly _by;
|
|
74
|
-
/**
|
|
75
|
-
* Which correlator this is. Computed once from the registry so identical
|
|
76
|
-
* workers share a lease and unrelated applications never do.
|
|
77
|
-
*/
|
|
78
34
|
private readonly _key;
|
|
79
|
-
/**
|
|
80
|
-
* When this worker's correlation lease runs out, as a local clock reading,
|
|
81
|
-
* or 0 when it holds none.
|
|
82
|
-
*
|
|
83
|
-
* Cached so the holder doesn't ask the store on every pass. A stale belief
|
|
84
|
-
* can only cause a duplicate scan, and marks are idempotent, so it costs
|
|
85
|
-
* work, never correctness.
|
|
86
|
-
*/
|
|
35
|
+
/** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
|
|
87
36
|
private _lease_until;
|
|
88
|
-
/**
|
|
89
|
-
* Whether a scan might find anything, like the drain's flag: a commit or
|
|
90
|
-
* notification raises it, a short page lowers it, and a disarmed scan
|
|
91
|
-
* returns without touching the store.
|
|
92
|
-
*
|
|
93
|
-
* Starts armed: the log may already hold events this process has never
|
|
94
|
-
* correlated, and only a scan can find out.
|
|
95
|
-
*/
|
|
37
|
+
/** Raised by a commit or notification, lowered by a short page. Starts armed. */
|
|
96
38
|
private _armed;
|
|
97
|
-
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
98
39
|
private _init_promise;
|
|
99
40
|
private readonly _dynamic_subscriptions;
|
|
100
|
-
/**
|
|
101
|
-
* What each static target was subscribed at by `init`. A plain map, never
|
|
102
|
-
* evicted: the collection is the build-time `_static_targets` list, so it
|
|
103
|
-
* is already bounded by the registry and costs nothing the registry does
|
|
104
|
-
* not already hold.
|
|
105
|
-
*
|
|
106
|
-
* Kept out of the LRU so eviction can never drop a static target's
|
|
107
|
-
* `+Infinity` floor and let a dynamic resolution re-lane it.
|
|
108
|
-
*/
|
|
109
41
|
private readonly _static_subscriptions;
|
|
110
|
-
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
111
42
|
private readonly _patterns;
|
|
112
43
|
private readonly _registry;
|
|
113
44
|
private readonly _static_targets;
|
|
114
45
|
private readonly _cd;
|
|
115
46
|
private readonly _on_init;
|
|
116
|
-
/**
|
|
117
|
-
* Async cold-start hook, run once after `on_init` inside `init()`. The
|
|
118
|
-
* orchestrator re-seeds its defer timers from the persisted `deferred_at`
|
|
119
|
-
* here, so an idle deferred stream re-arms across a restart.
|
|
120
|
-
*/
|
|
47
|
+
/** Runs once after `on_init`; re-seeds defer timers across a restart. */
|
|
121
48
|
private readonly _on_init_async;
|
|
122
|
-
/** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
|
|
123
49
|
private readonly _declared_lanes;
|
|
124
|
-
/**
|
|
125
|
-
* Offending declarations already reported, so a resolver firing for every
|
|
126
|
-
* matching event reports once. Owned by the instance rather than the
|
|
127
|
-
* module: `internal/` holds no module-level state.
|
|
128
|
-
*/
|
|
50
|
+
/** Report-once keys; per instance because `internal/` holds no module state. */
|
|
129
51
|
private readonly _reported;
|
|
130
52
|
constructor({ registry, static_targets, cd, max_subscribed_streams, declared_lanes, shard, on_init, on_init_async, }: CorrelateCycleDeps<TSchemaReg, TEvents, TActions>);
|
|
131
|
-
/** Last correlated event id. */
|
|
132
53
|
get checkpoint(): number;
|
|
133
|
-
/**
|
|
134
|
-
* Signal that a commit (local or remote) may have produced events this
|
|
135
|
-
* process has not correlated. Cheap and idempotent — the orchestrator calls
|
|
136
|
-
* it on every commit and every notification.
|
|
137
|
-
*/
|
|
54
|
+
/** A commit (local or remote) may have produced uncorrelated events. */
|
|
138
55
|
arm(): void;
|
|
139
|
-
/**
|
|
140
|
-
* Initialize correlation state on first call.
|
|
141
|
-
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
142
|
-
* flooring a first boot at `watermark - back_scan` so an event
|
|
143
|
-
* committed-but-not-correlated before a crash is re-scanned on
|
|
144
|
-
* restart instead of skipped
|
|
145
|
-
* - Subscribes static resolver targets (idempotent upsert)
|
|
146
|
-
* - Populates the subscribed-streams LRU
|
|
147
|
-
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
148
|
-
*/
|
|
149
56
|
init(): Promise<void>;
|
|
150
57
|
private _run_init;
|
|
151
|
-
/**
|
|
152
|
-
* Start correlation over after the event log was replaced wholesale
|
|
153
|
-
* (`restore`). The restored log is renumbered from the start and its
|
|
154
|
-
* subscription rows are gone, so the in-memory scan position and both
|
|
155
|
-
* subscription records describe a log that no longer exists. Dropping them
|
|
156
|
-
* and the init latch makes the next pass run cold-start again: static
|
|
157
|
-
* targets are re-subscribed, the checkpoint is re-read from the store
|
|
158
|
-
* (which `restore` reset), and dynamic targets are rediscovered by the
|
|
159
|
-
* scan.
|
|
160
|
-
*/
|
|
58
|
+
/** After `restore` replaced the log: forget everything and cold-start again. */
|
|
161
59
|
restart(): void;
|
|
162
|
-
/**
|
|
163
|
-
* Forget dynamic targets whose subscription rows no longer exist (a full
|
|
164
|
-
* close deletes the row), so a later scan re-subscribes them. Static
|
|
165
|
-
* targets keep their records.
|
|
166
|
-
*/
|
|
60
|
+
/** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
|
|
167
61
|
forget_subscribed(streams: Iterable<string>): void;
|
|
168
|
-
/**
|
|
169
|
-
* Would an event from `stream` be fetched for a target subscribed with
|
|
170
|
-
* `source`? The subscription's source is the filter `fetch` queries with
|
|
171
|
-
* — literal names by equality, patterns compiled as a `RegExp` — so an
|
|
172
|
-
* event outside it is not work for that target and must not mark it.
|
|
173
|
-
* No source means the target consumes every stream.
|
|
174
|
-
*/
|
|
62
|
+
/** Would the target's own `fetch` (filtered by `source`) return this event? */
|
|
175
63
|
private _in_fetch_window;
|
|
176
|
-
/**
|
|
177
|
-
* Scan the events past the checkpoint, resolve each to its target
|
|
178
|
-
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
179
|
-
* targets get registered, and every target an event resolved to gets its
|
|
180
|
-
* **work mark** raised to that event's id.
|
|
181
|
-
*
|
|
182
|
-
* Both resolver kinds are walked. A static target is already subscribed
|
|
183
|
-
* at init, but marking it is what makes it claimable without probing the
|
|
184
|
-
* event log, so the scan runs for every app.
|
|
185
|
-
*/
|
|
64
|
+
/** Scan past the checkpoint, subscribe new targets and raise their work marks. */
|
|
186
65
|
correlate(query?: Query,
|
|
187
66
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* True only on the automatic paths — the settle loop and the poller —
|
|
191
|
-
* where the question is "should *someone* scan?" and one worker doing it
|
|
192
|
-
* serves all of them.
|
|
193
|
-
*
|
|
194
|
-
* An explicit `app.correlate()` means "scan now" and ignores the lease:
|
|
195
|
-
* `close` catches up by looping until the checkpoint moves, and a
|
|
196
|
-
* lease-blocked scan would cap its prune at a stale position.
|
|
67
|
+
* Honour the correlation lease. Only the settle loop and poller do; an
|
|
68
|
+
* explicit `app.correlate()` scans now, since `close` loops on it.
|
|
197
69
|
*/
|
|
198
70
|
lease?: boolean): Promise<{
|
|
199
71
|
subscribed: number;
|
|
200
72
|
last_id: number;
|
|
201
73
|
marked: number;
|
|
202
|
-
/** False when the pass was disarmed and returned without a store read. */
|
|
203
74
|
scanned: boolean;
|
|
204
75
|
}>;
|
|
205
76
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* has one, says what lane and priority it was subscribed at; a resolution
|
|
210
|
-
* that doesn't beat that priority carries the row's values, exactly as if
|
|
211
|
-
* the record had never been evicted. One read per target with no record,
|
|
212
|
-
* and only when more than one lane is declared: with one lane there is no
|
|
213
|
-
* lane to lose, and the store already keeps the higher priority.
|
|
77
|
+
* Read the row of each target this scan had no record of (new or evicted),
|
|
78
|
+
* so a resolution that doesn't beat its priority keeps the row's lane.
|
|
79
|
+
* Only called with more than one lane declared.
|
|
214
80
|
*/
|
|
215
81
|
private _recall;
|
|
216
|
-
/**
|
|
217
|
-
* Hand the correlation lease back early.
|
|
218
|
-
*
|
|
219
|
-
* The port has no release verb; re-acquiring as the same holder with a
|
|
220
|
-
* zero duration releases it. Best-effort: a failure costs the lease's
|
|
221
|
-
* remaining lifetime, as a crash would.
|
|
222
|
-
*/
|
|
82
|
+
/** Release the lease early by re-acquiring it for zero ms. Best-effort. */
|
|
223
83
|
release_correlation(): Promise<void>;
|
|
224
84
|
}
|
|
225
85
|
//# sourceMappingURL=correlate-cycle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlate-cycle.d.ts","sourceRoot":"","sources":["../../../src/internal/correlate-cycle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"correlate-cycle.d.ts","sourceRoot":"","sources":["../../../src/internal/correlate-cycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,82 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* @
|
|
4
|
-
*
|
|
5
|
-
* Two layers of the drain pipeline:
|
|
6
|
-
*
|
|
7
|
-
* - {@link run_drain_cycle} — pure function for one round-trip of
|
|
8
|
-
* claim → fetch → group → dispatch → ack/block. No orchestrator state.
|
|
9
|
-
* Reusable for property tests and standalone benchmarks.
|
|
10
|
-
*
|
|
11
|
-
* - {@link DrainController} — stateful driver that owns the armed flag,
|
|
12
|
-
* the concurrency lock, and the adaptive lag/lead ratio.
|
|
13
|
-
*
|
|
14
|
-
* @internal
|
|
2
|
+
* The drain pipeline: {@link run_drain_cycle} is one stateless
|
|
3
|
+
* claim → fetch → dispatch → ack/block round-trip; {@link DrainController}
|
|
4
|
+
* owns the armed flag, the lock, the lag/lead ratio and the defer wake.
|
|
15
5
|
*/
|
|
16
6
|
import type { BatchHandler, BlockedLease, CloseTarget, Drain, DrainOptions, Lease, Logger, ReactionPayload, Registry, SchemaRegister, Schemas } from "../types/index.js";
|
|
17
7
|
import type { CircuitBreaker } from "./circuit-breaker.js";
|
|
18
8
|
import type { DrainOps } from "./drain.js";
|
|
19
|
-
/**
|
|
20
|
-
* Outcome of processing a single leased stream — produced by Act's `handle`
|
|
21
|
-
* / `handle_batch` dispatchers, consumed by `run_drain_cycle` to drive ack/block.
|
|
22
|
-
*
|
|
23
|
-
* @internal
|
|
24
|
-
*/
|
|
9
|
+
/** Outcome of handling one leased stream; drives its ack/block. */
|
|
25
10
|
export type HandleResult = Readonly<{
|
|
26
11
|
lease: Lease;
|
|
27
12
|
handled: number;
|
|
28
|
-
/**
|
|
29
|
-
* Event id at which the ack would land: the last *successful* event id,
|
|
30
|
-
* or `lease.at` when the batch had no work.
|
|
31
|
-
*/
|
|
13
|
+
/** Last successful event id, or `lease.at` when there was no work. */
|
|
32
14
|
acked_at: number;
|
|
33
15
|
error?: string;
|
|
34
16
|
block?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Wall-clock timestamp (ms since epoch) at which the next attempt on
|
|
37
|
-
* this stream may run, from the reaction's backoff (or the default).
|
|
38
|
-
* Set on every retry; absent on success, block and defer.
|
|
39
|
-
*/
|
|
17
|
+
/** Epoch ms of the next retry, from the backoff. Only set on a retry. */
|
|
40
18
|
next_attempt_at?: number;
|
|
41
|
-
/**
|
|
42
|
-
* Wall-clock timestamp (ms since epoch) at which to re-visit a stream
|
|
43
|
-
* whose handler *deferred*: the triggering events stay pending, `retry`
|
|
44
|
-
* is not bumped, and the drain redelivers once `defer` elapses. Unlike
|
|
45
|
-
* {@link next_attempt_at}, a defer carries no error and never blocks.
|
|
46
|
-
*/
|
|
19
|
+
/** Epoch ms to redeliver a deferred stream; no error, no retry bump. */
|
|
47
20
|
defer?: number;
|
|
48
|
-
/**
|
|
49
|
-
* Close request from a handler that threw `CloseSignal`. The triggering
|
|
50
|
-
* event is acked (so the close guard doesn't see the reaction as in
|
|
51
|
-
* flight), then the drain hands this {@link CloseTarget} to `on_close`.
|
|
52
|
-
*/
|
|
21
|
+
/** From a `CloseSignal`; acked first so the close guard sees it caught up. */
|
|
53
22
|
close?: CloseTarget;
|
|
54
|
-
/**
|
|
55
|
-
* Event id that threw, when a handler error occurred. Distinct from
|
|
56
|
-
* {@link acked_at}: `failed_at = acked_at + 1` in dense streams, but
|
|
57
|
-
* adapters with sparse ids give the trace the exact position. Always
|
|
58
|
-
* set on the per-event error path; absent in batch mode (where no
|
|
59
|
-
* single event id can be attributed to the failure).
|
|
60
|
-
*/
|
|
23
|
+
/** Event id that threw (sparse ids make it differ from `acked_at + 1`); never in batch mode. */
|
|
61
24
|
failed_at?: number;
|
|
62
25
|
}>;
|
|
63
|
-
/**
|
|
64
|
-
* Per-event reaction dispatcher signature (matches `Act.handle`).
|
|
65
|
-
* @internal
|
|
66
|
-
*/
|
|
26
|
+
/** `Act.handle`. */
|
|
67
27
|
export type Handle<TEvents extends Schemas> = (lease: Lease, payloads: ReactionPayload<TEvents>[]) => Promise<HandleResult>;
|
|
68
|
-
/**
|
|
69
|
-
* Bulk reaction dispatcher signature (matches `Act.handle_batch`).
|
|
70
|
-
* @internal
|
|
71
|
-
*/
|
|
28
|
+
/** `Act.handle_batch`. */
|
|
72
29
|
export type HandleBatch<TEvents extends Schemas> = (lease: Lease, payloads: ReactionPayload<TEvents>[], batchHandler: BatchHandler<TEvents>) => Promise<HandleResult>;
|
|
73
|
-
/**
|
|
74
|
-
* Dependencies the {@link DrainController} needs from the orchestrator.
|
|
75
|
-
* The lifecycle event sinks (`on_acked` / `on_blocked`) are callbacks so
|
|
76
|
-
* this module doesn't reach back into Act's emitter.
|
|
77
|
-
*
|
|
78
|
-
* @internal
|
|
79
|
-
*/
|
|
80
30
|
export type DrainControllerDeps<TEvents extends Schemas, TActions extends Schemas, TSchemaReg extends SchemaRegister<TActions>> = {
|
|
81
31
|
readonly logger: Logger;
|
|
82
32
|
readonly ops: DrainOps<TEvents>;
|
|
@@ -86,20 +36,10 @@ export type DrainControllerDeps<TEvents extends Schemas, TActions extends Schema
|
|
|
86
36
|
readonly handle_batch: HandleBatch<TEvents>;
|
|
87
37
|
readonly on_acked: (acked: Lease[]) => void;
|
|
88
38
|
readonly on_blocked: (blocked: BlockedLease[]) => void;
|
|
89
|
-
/**
|
|
90
|
-
* Runs the cycle's reaction-requested closes after acks/blocks land.
|
|
91
|
-
* Awaited so a slow close doesn't overlap the next claim.
|
|
92
|
-
*/
|
|
39
|
+
/** Awaited, so a slow close doesn't overlap the next claim. */
|
|
93
40
|
readonly on_close: (targets: CloseTarget[]) => Promise<void>;
|
|
94
|
-
/**
|
|
95
|
-
* Shared circuit breaker. Opens after repeated store failures so the
|
|
96
|
-
* drain stops hammering a down backend, and surfaces each failure.
|
|
97
|
-
*/
|
|
98
41
|
readonly breaker: CircuitBreaker;
|
|
99
|
-
/**
|
|
100
|
-
* Runs a body in the Act's ports frame. The per-lane worker ticks outside
|
|
101
|
-
* any caller frame, so its drain must be re-scoped.
|
|
102
|
-
*/
|
|
42
|
+
/** The worker ticks outside any caller frame, so its drain is re-scoped. */
|
|
103
43
|
readonly run_scoped: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
104
44
|
/** Lane this controller drains. Undefined spans all lanes. */
|
|
105
45
|
readonly lane?: string;
|
|
@@ -109,85 +49,35 @@ export type DrainControllerDeps<TEvents extends Schemas, TActions extends Schema
|
|
|
109
49
|
readonly leaseMillis?: number;
|
|
110
50
|
};
|
|
111
51
|
};
|
|
112
|
-
/**
|
|
113
|
-
* Stateful driver around {@link run_drain_cycle}. Owns:
|
|
114
|
-
*
|
|
115
|
-
* - `_armed` — has any commit / reset / cold-start signaled work to do?
|
|
116
|
-
* - `_locked` — concurrent-call guard (overlapping `drain()` calls return
|
|
117
|
-
* an empty result instead of running twice)
|
|
118
|
-
* - `_ratio` — adaptive lag-to-lead frontier split, updated per cycle
|
|
119
|
-
*
|
|
120
|
-
* The orchestrator owns commits, lifecycle emission, and `arm()` triggers
|
|
121
|
-
* — the controller owns everything between those edges.
|
|
122
|
-
*
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
52
|
export declare class DrainController<TEvents extends Schemas, TActions extends Schemas, TSchemaReg extends SchemaRegister<TActions>> {
|
|
126
53
|
private _armed;
|
|
127
54
|
private _locked;
|
|
128
55
|
private _ratio;
|
|
129
56
|
/**
|
|
130
|
-
* Local wake
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* the drain alone finds it, and on an idle app nothing else would.
|
|
134
|
-
* Every drain re-aims it when it ends, and a due entry stays until a pass
|
|
135
|
-
* handles it or a pass that started after it came due claims nothing, so
|
|
136
|
-
* the wake keeps draining until every due stream is reached.
|
|
137
|
-
* `stop()` cancels the timer and a stopped drain never re-schedules it,
|
|
138
|
-
* so a wake never runs after a stop.
|
|
57
|
+
* Local wake at the earliest parked (backoff or defer) visit; the schedule
|
|
58
|
+
* itself is persisted. On an idle app nothing else would drain it. A due
|
|
59
|
+
* entry stays until handled or until a later pass claims nothing.
|
|
139
60
|
*/
|
|
140
61
|
private readonly _defer;
|
|
141
|
-
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
142
62
|
private _worker;
|
|
143
|
-
/**
|
|
144
|
-
* Misroutings this controller has reported. A resolver returning a
|
|
145
|
-
* projection's target does so for every matching event; one line per event
|
|
146
|
-
* would bury the signal it exists to raise.
|
|
147
|
-
*/
|
|
148
63
|
private readonly _misrouted;
|
|
149
64
|
private _stopped;
|
|
150
|
-
/**
|
|
151
|
-
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
152
|
-
* wait for it. Never rejects.
|
|
153
|
-
*/
|
|
65
|
+
/** Never rejects; graceful shutdown waits on it. */
|
|
154
66
|
private _inflight;
|
|
155
67
|
private _inflight_done;
|
|
156
68
|
private readonly _deps;
|
|
157
69
|
constructor(deps: DrainControllerDeps<TEvents, TActions, TSchemaReg>);
|
|
158
|
-
/**
|
|
159
|
-
* Signal that a commit (or reset / cold-start) may have produced work.
|
|
160
|
-
* Subsequent `drain()` calls will run the pipeline; once the pipeline
|
|
161
|
-
* settles to no-progress, the controller disarms itself.
|
|
162
|
-
*/
|
|
163
70
|
arm(): void;
|
|
164
|
-
/**
|
|
165
|
-
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
166
|
-
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
167
|
-
* Seeds collapse into one timer; the earliest wins.
|
|
168
|
-
*/
|
|
71
|
+
/** Cold start: re-seed a persisted `deferred_at` into the local wake. */
|
|
169
72
|
seed_defer(stream: string, at: number): void;
|
|
170
|
-
/** Read-only flag — true while a commit / reset is unprocessed. */
|
|
171
73
|
get armed(): boolean;
|
|
172
|
-
/** The cycle in flight, or `undefined` when idle. */
|
|
173
74
|
get inflight(): Promise<void> | undefined;
|
|
174
75
|
/** This lane's configured lease, or `undefined`; the shutdown grace basis. */
|
|
175
76
|
get lease_millis(): number | undefined;
|
|
176
|
-
/** Lane this controller drains (undefined spans all lanes). */
|
|
177
77
|
get lane(): string | undefined;
|
|
178
|
-
/**
|
|
179
|
-
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
180
|
-
* cadence. When armed, the worker calls `drain()` on every
|
|
181
|
-
* tick and re-schedules; when not armed, it still re-schedules at
|
|
182
|
-
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
183
|
-
*
|
|
184
|
-
* The setTimeout chain uses `unref()` so it doesn't keep the process
|
|
185
|
-
* alive on its own.
|
|
186
|
-
*/
|
|
78
|
+
/** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
|
|
187
79
|
start(cycleMs: number): void;
|
|
188
|
-
/** Stop the per-lane worker. Idempotent. */
|
|
189
80
|
stop(): void;
|
|
190
|
-
/** Run one drain pass. Short-circuits when not armed or already running. */
|
|
191
81
|
drain(options?: DrainOptions): Promise<Drain<TEvents>>;
|
|
192
82
|
}
|
|
193
83
|
//# sourceMappingURL=drain-cycle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain-cycle.d.ts","sourceRoot":"","sources":["../../../src/internal/drain-cycle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"drain-cycle.d.ts","sourceRoot":"","sources":["../../../src/internal/drain-cycle.ts"],"names":[],"mappings":"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"}
|