@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.
@@ -1,190 +1,53 @@
1
1
  /**
2
- * @module event-sourcing
3
- * @category Internal
4
- *
5
- * Pure event-sourcing primitives: `snap` persists state checkpoints, `load`
6
- * reconstructs state by folding events through reducers, and `action`
7
- * validates an action, runs invariants, emits events, and commits them
8
- * atomically. `tombstone` commits the close-the-books guard with optimistic
9
- * concurrency.
10
- *
11
- * These are the bare implementations — observability is layered on top in
12
- * {@link "tracing"} and wired by the orchestrator at construction time.
13
- * No tracing imports here, no module-level mutable state.
14
- *
15
- * @internal
2
+ * Event-sourcing primitives: `snap`, `load`, `action`, `tombstone`, `scan`.
3
+ * Bare implementations; tracing wraps them in `tracing.ts`.
16
4
  */
17
5
  import { type Patch } from "@rotorsoft/act-patch";
18
6
  import type { Committed, DoOptions, EventSource, LoadTarget, ScanOptions, ScanResult, Schema, Schemas, Snapshot, State, Target } from "../types/index.js";
19
7
  /**
20
- * The reduction pipeline names three distinct things, and each word means
21
- * exactly one of them:
22
- *
23
- * - a **reducer** is a state's `.patch()` handler — it takes one event and
24
- * returns a {@link Patch} *partial*;
25
- * - a **patch step** ({@link bare_patch} / {@link validating_patch}) merges
26
- * that partial into the current state, yielding the next full state;
27
- * - the **fold** is the loop that applies the patch step across a stream's
28
- * events (the reader loop in {@link load} / {@link action}, and the
29
- * projection engine in {@link "projection-fold"}).
30
- *
31
- * `PatchFn` is the per-event patch step. The fold loop calls one of two
32
- * implementations, selected **once** at construction (in
33
- * {@link "tracing".build_es}) — never branched per event:
34
- *
35
- * - {@link bare_patch} — the default. Literally `patch(state, partial)`,
36
- * no wrapper. No validation cost on the hot path.
37
- * - {@link validating_patch} — the opt-in `ActOptions.validateFoldedState`
38
- * path. Merges, then parses the merged full state against the state's
39
- * declared Zod schema.
40
- *
41
- * The `me`/`event` arguments are unused by the bare implementation but
42
- * carried on the shared signature so the fold loop is call-shape-identical
43
- * regardless of which patch step was selected — the validating
44
- * implementation needs them to name the failing reduction.
45
- *
46
- * @internal
8
+ * The patch step: merges a reducer's partial into state. Chosen once at
9
+ * construction (`build_es`), never per event. `me`/`event` only serve
10
+ * {@link validating_patch}, to name the failing reduction.
47
11
  */
48
12
  export type PatchFn = <TState extends Schema, TEvents extends Schemas, TActions extends Schemas>(me: State<TState, TEvents, TActions>, state: TState, partial: Readonly<Patch<TState>>, event: Committed<TEvents, keyof TEvents>) => TState;
49
- /**
50
- * The default patch step: a bare `patch()` merge with no wrapper and no
51
- * branch. The off-path is a plain reduction, so an
52
- * app that leaves `validateFoldedState` off pays nothing — not even a
53
- * comparison.
54
- *
55
- * @internal
56
- */
13
+ /** The default patch step: a bare merge, so the hot path pays nothing. */
57
14
  export declare const bare_patch: PatchFn;
58
15
  /**
59
- * The opt-in patch step: merge the partial into state, then
60
- * parse the merged full state against the owning state's declared Zod
61
- * schema. A reducer that produces schema-violating state (the calculator
62
- * divide-by-zero NaN class) fails here, at the triggering event,
63
- * instead of propagating and surfacing hops later as a confusing
64
- * downstream error.
65
- *
66
- * The `target` string names the state and the triggering event
67
- * (`"<state>.<event>#<id>"`) so the resulting {@link ValidationError}
68
- * points straight at the reduction that produced bad state. A debugging /
69
- * CI aid, not a production guard — selected only when the operator opts
70
- * in.
71
- *
72
- * @internal
16
+ * `validateFoldedState`: merge, then parse against the state schema, so a
17
+ * bad reducer fails at its own event. A debugging aid, not a production guard.
73
18
  */
74
19
  export declare const validating_patch: PatchFn;
75
- /**
76
- * Internal action signature seen by the orchestrator — the {@link Correlator}
77
- * is bound at `build_es` time, so callers don't pass it through.
78
- *
79
- * @internal
80
- */
20
+ /** `action` with the correlator and patch step bound by `build_es`. */
81
21
  type BoundAction = <TState extends Schema, TEvents extends Schemas, TActions extends Schemas, TKey extends keyof TActions>(me: State<TState, TEvents, TActions>, action: TKey, target: Target, payload: Readonly<TActions[TKey]>, options?: DoOptions<TEvents>) => Promise<Snapshot<TState, TEvents>[]>;
82
- /** @internal */
83
22
  export interface EsOps {
84
23
  snap: typeof snap;
85
24
  load: typeof load;
86
25
  action: BoundAction;
87
26
  tombstone: typeof tombstone;
88
27
  }
89
- /**
90
- * Event sourcing utilities for snapshotting, loading, and committing actions/events.
91
- * Used internally by Act and state machines.
92
- */
93
- /**
94
- * Saves a snapshot of the state to the store.
95
- *
96
- * Snapshots are used to optimize state reconstruction for aggregates with long event streams.
97
- *
98
- * @template TState The type of state
99
- * @template TEvents The type of events
100
- * @param snapshot The snapshot to save
101
- * @returns Promise that resolves when the snapshot is saved
102
- *
103
- * @example
104
- * await snap(snapshot);
105
- */
28
+ /** Commits a snapshot event. Never throws: a failed snapshot must not fail the action. */
106
29
  export declare function snap<TState extends Schema, TEvents extends Schemas>(snapshot: Snapshot<TState, TEvents>): Promise<Committed<TEvents, keyof TEvents> | undefined>;
107
30
  /**
108
- * Commits a tombstone event with optimistic concurrency, returning the
109
- * committed record on success or `undefined` if the stream moved past
110
- * `expectedVersion` (concurrent write detected). Other store errors
111
- * propagate.
112
- *
113
- * Used by `close()` to guard a stream while archive/truncate runs:
114
- * subsequent `action()` calls see the tombstone at head and reject with
115
- * {@link StreamClosedError} until the close completes.
116
- *
117
- * @internal
31
+ * Commits the close guard at `expectedVersion`; `undefined` when the stream
32
+ * moved. Actions then see the tombstone at head and throw {@link StreamClosedError}.
118
33
  */
119
34
  export declare function tombstone(stream: string, expectedVersion: number, correlation: string): Promise<Committed<Schemas, keyof Schemas> | undefined>;
120
35
  /**
121
- * Scan a restore source event by event. Owns pagination, validation,
122
- * the `drop_snapshots` filter, the `on_progress` callback, and the
123
- * causation remap; adapters supply only the per-event insert
124
- * `callback` via the driver pattern (see {@link Store.restore}).
125
- *
126
- * Pages the source with `limit`/`after`; a source that ignores the
127
- * filter (`CsvFile`) returns everything at once and the loop ends. Throws
128
- * on the first invalid event. Returns the result without `duration_ms`,
129
- * which {@link Act.restore} adds.
130
- *
131
- * @internal
36
+ * Pages a restore source: validation, drop filters, migrations, progress and
37
+ * causation remap. Adapters supply only the per-event insert `callback`
38
+ * (see {@link Store.restore}). A source that ignores `limit` (`CsvFile`)
39
+ * returns everything in one page.
132
40
  */
133
41
  export declare function scan(source: EventSource, opts?: ScanOptions, callback?: (event: Committed<Schemas, keyof Schemas>) => Promise<number>): Promise<Omit<ScanResult, "duration_ms">>;
134
42
  /**
135
- * Loads a snapshot of the state from the store by folding events through the
136
- * state's patch reducers.
137
- *
138
- * First checks the cache for a checkpoint, then queries the store for events
139
- * committed after the cached position. On cache miss, replays from the store
140
- * (using snapshots if available to avoid full replay).
141
- *
142
- * @template TState The type of state
143
- * @template TEvents The type of events
144
- * @template TActions The type of actions
145
- * @param me The state machine definition.
146
- * @param stream The stream (instance) to load
147
- * @param callback (Optional) Callback to receive the loaded snapshot as it is built
148
- * @param asOf (Optional) Time-travel cursor; bypasses the cache.
149
- * @param actor (Optional) Passed through to the state's `view` delegate
150
- * when constructing the snapshot.event — semantics are the state's to
151
- * define (see {@link state}).
152
- * @returns The snapshot of the loaded state
153
- *
154
- * @example
155
- * const snapshot = await load(Counter, "counter1");
43
+ * Folds a stream into state, from the cached checkpoint or the latest
44
+ * snapshot. `asOf` time-travels and bypasses the cache.
156
45
  */
157
46
  export declare function load<TState extends Schema, TEvents extends Schemas, TActions extends Schemas>(me: State<TState, TEvents, TActions>, target: LoadTarget, callback?: (snapshot: Snapshot<TState, TEvents>) => void, patch_fn?: PatchFn): Promise<Snapshot<TState, TEvents>>;
158
47
  /**
159
- * Executes an action and emits an event to be committed by the store.
160
- *
161
- * Validates the action, applies business invariants, emits events, and
162
- * commits them to the event store. When the action's
163
- * {@link ActionOptions} declare a retry budget, the orchestrator owns
164
- * the loop on {@link ConcurrencyError}: cache is invalidated, optional
165
- * `backoff` delay is applied, and the action re-runs from `load`. Any
166
- * other error rethrows immediately and does not consume the budget.
167
- *
168
- * Reactions skip optimistic concurrency (commit below passes
169
- * `undefined` as `expectedVersion` when `reactingTo` is set), so
170
- * `ConcurrencyError` cannot fire on the reaction-driven path — the
171
- * loop is naturally a no-op there.
172
- *
173
- * @template TState The type of state
174
- * @template TEvents The type of events
175
- * @template TActions The type of action_schemas
176
- * @template TKey The type of action to execute
177
- * @param me The state machine definition
178
- * @param action The action to execute
179
- * @param target The target (stream, actor, etc.)
180
- * @param payload The payload of the action
181
- * @param options Per-call dispatch options ({@link DoOptions}) —
182
- * `reactingTo` to thread correlation, `correlator` to override the
183
- * framework or orchestrator-level correlator for this call only.
184
- * @returns The snapshot of the committed event
185
- *
186
- * @example
187
- * const snapshot = await action(Counter, "increment", { stream: "counter1", actor }, { by: 1 });
48
+ * Validates, checks invariants, emits and commits. On {@link ConcurrencyError}
49
+ * it re-runs from `load` within the action's `maxRetries`; any other error
50
+ * rethrows. Reactions commit without the inferred version guard.
188
51
  */
189
52
  export declare function action<TState extends Schema, TEvents extends Schemas, TActions extends Schemas, TKey extends keyof TActions>(me: State<TState, TEvents, TActions>, action: TKey, target: Target, payload: Readonly<TActions[TKey]>, options?: DoOptions<TEvents>, patch_fn?: PatchFn): Promise<Snapshot<TState, TEvents>[]>;
190
53
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"event-sourcing.d.ts","sourceRoot":"","sources":["../../../src/internal/event-sourcing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"event-sourcing.d.ts","sourceRoot":"","sources":["../../../src/internal/event-sourcing.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -3128,71 +3128,27 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
3128
3128
  var CorrelateCycle = class {
3129
3129
  _checkpoint = -1;
3130
3130
  _initialized = false;
3131
- /**
3132
- * This worker's identity for the correlation lease. A per-instance
3133
- * UUID, matching the drain's convention, so a renewal is recognised as the
3134
- * same holder and a restarted process never inherits a stale claim.
3135
- */
3131
+ /** Lease holder id; per instance, so a restart never inherits a stale claim. */
3136
3132
  _by = (0, import_node_crypto.randomUUID)();
3137
- /**
3138
- * Which correlator this is. Computed once from the registry so identical
3139
- * workers share a lease and unrelated applications never do.
3140
- */
3141
3133
  _key;
3142
- /**
3143
- * When this worker's correlation lease runs out, as a local clock reading,
3144
- * or 0 when it holds none.
3145
- *
3146
- * Cached so the holder doesn't ask the store on every pass. A stale belief
3147
- * can only cause a duplicate scan, and marks are idempotent, so it costs
3148
- * work, never correctness.
3149
- */
3134
+ /** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
3150
3135
  _lease_until = 0;
3151
- /**
3152
- * Whether a scan might find anything, like the drain's flag: a commit or
3153
- * notification raises it, a short page lowers it, and a disarmed scan
3154
- * returns without touching the store.
3155
- *
3156
- * Starts armed: the log may already hold events this process has never
3157
- * correlated, and only a scan can find out.
3158
- */
3136
+ /** Raised by a commit or notification, lowered by a short page. Starts armed. */
3159
3137
  _armed = true;
3160
- /** In-flight init, memoized for single-flight and cleared on failure. */
3161
3138
  _init_promise;
3162
- // Dynamically discovered targets → what each was last subscribed at,
3163
- // bounded by `maxSubscribedStreams`. It decides *what* a scan sends with a
3164
- // target: priority/lane only when the resolution beats the recorded floor,
3165
- // otherwise the row's own values. See {@link Subscription}.
3139
+ // Bounded by `maxSubscribedStreams`; static records live apart so eviction
3140
+ // never drops their +Infinity floor.
3166
3141
  _dynamic_subscriptions;
3167
- /**
3168
- * What each static target was subscribed at by `init`. A plain map, never
3169
- * evicted: the collection is the build-time `_static_targets` list, so it
3170
- * is already bounded by the registry and costs nothing the registry does
3171
- * not already hold.
3172
- *
3173
- * Kept out of the LRU so eviction can never drop a static target's
3174
- * `+Infinity` floor and let a dynamic resolution re-lane it.
3175
- */
3176
3142
  _static_subscriptions = /* @__PURE__ */ new Map();
3177
- /** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
3178
3143
  _patterns = new LruMap(PATTERN_CACHE_SIZE);
3179
3144
  _registry;
3180
3145
  _static_targets;
3181
3146
  _cd;
3182
3147
  _on_init;
3183
- /**
3184
- * Async cold-start hook, run once after `on_init` inside `init()`. The
3185
- * orchestrator re-seeds its defer timers from the persisted `deferred_at`
3186
- * here, so an idle deferred stream re-arms across a restart.
3187
- */
3148
+ /** Runs once after `on_init`; re-seeds defer timers across a restart. */
3188
3149
  _on_init_async;
3189
- /** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
3190
3150
  _declared_lanes;
3191
- /**
3192
- * Offending declarations already reported, so a resolver firing for every
3193
- * matching event reports once. Owned by the instance rather than the
3194
- * module: `internal/` holds no module-level state.
3195
- */
3151
+ /** Report-once keys; per instance because `internal/` holds no module state. */
3196
3152
  _reported = /* @__PURE__ */ new Set();
3197
3153
  constructor({
3198
3154
  registry,
@@ -3213,28 +3169,13 @@ var CorrelateCycle = class {
3213
3169
  this._on_init = on_init;
3214
3170
  this._on_init_async = on_init_async;
3215
3171
  }
3216
- /** Last correlated event id. */
3217
3172
  get checkpoint() {
3218
3173
  return this._checkpoint;
3219
3174
  }
3220
- /**
3221
- * Signal that a commit (local or remote) may have produced events this
3222
- * process has not correlated. Cheap and idempotent — the orchestrator calls
3223
- * it on every commit and every notification.
3224
- */
3175
+ /** A commit (local or remote) may have produced uncorrelated events. */
3225
3176
  arm() {
3226
3177
  this._armed = true;
3227
3178
  }
3228
- /**
3229
- * Initialize correlation state on first call.
3230
- * - Reads the durable correlate checkpoint (and max(at)) from the store,
3231
- * flooring a first boot at `watermark - back_scan` so an event
3232
- * committed-but-not-correlated before a crash is re-scanned on
3233
- * restart instead of skipped
3234
- * - Subscribes static resolver targets (idempotent upsert)
3235
- * - Populates the subscribed-streams LRU
3236
- * - Fires `on_init` once (Act uses this to flag a cold-start drain)
3237
- */
3238
3179
  async init() {
3239
3180
  if (this._initialized) return;
3240
3181
  if (!this._init_promise) {
@@ -3261,16 +3202,7 @@ var CorrelateCycle = class {
3261
3202
  }
3262
3203
  await this._on_init_async?.();
3263
3204
  }
3264
- /**
3265
- * Start correlation over after the event log was replaced wholesale
3266
- * (`restore`). The restored log is renumbered from the start and its
3267
- * subscription rows are gone, so the in-memory scan position and both
3268
- * subscription records describe a log that no longer exists. Dropping them
3269
- * and the init latch makes the next pass run cold-start again: static
3270
- * targets are re-subscribed, the checkpoint is re-read from the store
3271
- * (which `restore` reset), and dynamic targets are rediscovered by the
3272
- * scan.
3273
- */
3205
+ /** After `restore` replaced the log: forget everything and cold-start again. */
3274
3206
  restart() {
3275
3207
  this._initialized = false;
3276
3208
  this._init_promise = void 0;
@@ -3279,21 +3211,11 @@ var CorrelateCycle = class {
3279
3211
  this._static_subscriptions.clear();
3280
3212
  this._armed = true;
3281
3213
  }
3282
- /**
3283
- * Forget dynamic targets whose subscription rows no longer exist (a full
3284
- * close deletes the row), so a later scan re-subscribes them. Static
3285
- * targets keep their records.
3286
- */
3214
+ /** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
3287
3215
  forget_subscribed(streams) {
3288
3216
  for (const stream of streams) this._dynamic_subscriptions.delete(stream);
3289
3217
  }
3290
- /**
3291
- * Would an event from `stream` be fetched for a target subscribed with
3292
- * `source`? The subscription's source is the filter `fetch` queries with
3293
- * — literal names by equality, patterns compiled as a `RegExp` — so an
3294
- * event outside it is not work for that target and must not mark it.
3295
- * No source means the target consumes every stream.
3296
- */
3218
+ /** Would the target's own `fetch` (filtered by `source`) return this event? */
3297
3219
  _in_fetch_window(source, stream) {
3298
3220
  if (source === void 0 || source === stream) return true;
3299
3221
  if (is_literal_source(source)) return false;
@@ -3304,16 +3226,7 @@ var CorrelateCycle = class {
3304
3226
  }
3305
3227
  return pattern.test(stream);
3306
3228
  }
3307
- /**
3308
- * Scan the events past the checkpoint, resolve each to its target
3309
- * streams, and record what it found through `cd.subscribe` — new dynamic
3310
- * targets get registered, and every target an event resolved to gets its
3311
- * **work mark** raised to that event's id.
3312
- *
3313
- * Both resolver kinds are walked. A static target is already subscribed
3314
- * at init, but marking it is what makes it claimable without probing the
3315
- * event log, so the scan runs for every app.
3316
- */
3229
+ /** Scan past the checkpoint, subscribe new targets and raise their work marks. */
3317
3230
  async correlate(query = { after: -1, limit: 10 }, lease = false) {
3318
3231
  await this.init();
3319
3232
  if (!this._armed)
@@ -3398,10 +3311,8 @@ var CorrelateCycle = class {
3398
3311
  }
3399
3312
  }
3400
3313
  },
3401
- // Decline the sensitive payload: the scan only needs name/stream/id,
3402
- // and one undecryptable row would otherwise stop every stream's
3403
- // reactions. Resolvers therefore see `pii: null`; correlate runs
3404
- // actor-less, so plaintext here would be un-gated disclosure.
3314
+ // Resolvers see `pii: null`: correlate runs actor-less, and one
3315
+ // undecryptable row would otherwise stop every stream's reactions.
3405
3316
  { ...query, after, with_pii: false }
3406
3317
  );
3407
3318
  if (this._declared_lanes.size > 1) await this._recall(correlated);
@@ -3444,14 +3355,9 @@ var CorrelateCycle = class {
3444
3355
  return { subscribed: 0, last_id, marked: 0, scanned: true };
3445
3356
  }
3446
3357
  /**
3447
- * Give each target this scan had no record of the record its row holds.
3448
- *
3449
- * A target with no record is either new or forgotten. Its row, when it
3450
- * has one, says what lane and priority it was subscribed at; a resolution
3451
- * that doesn't beat that priority carries the row's values, exactly as if
3452
- * the record had never been evicted. One read per target with no record,
3453
- * and only when more than one lane is declared: with one lane there is no
3454
- * lane to lose, and the store already keeps the higher priority.
3358
+ * Read the row of each target this scan had no record of (new or evicted),
3359
+ * so a resolution that doesn't beat its priority keeps the row's lane.
3360
+ * Only called with more than one lane declared.
3455
3361
  */
3456
3362
  async _recall(correlated) {
3457
3363
  const missed = [...correlated].filter(([, entry]) => entry.missed);
@@ -3487,13 +3393,7 @@ var CorrelateCycle = class {
3487
3393
  })
3488
3394
  );
3489
3395
  }
3490
- /**
3491
- * Hand the correlation lease back early.
3492
- *
3493
- * The port has no release verb; re-acquiring as the same holder with a
3494
- * zero duration releases it. Best-effort: a failure costs the lease's
3495
- * remaining lifetime, as a crash would.
3496
- */
3396
+ /** Release the lease early by re-acquiring it for zero ms. Best-effort. */
3497
3397
  async release_correlation() {
3498
3398
  try {
3499
3399
  this._lease_until = 0;
@@ -3970,11 +3870,9 @@ async function load(me, target, callback, patch_fn = bare_patch) {
3970
3870
  {
3971
3871
  stream,
3972
3872
  stream_exact: true,
3973
- // `with_snaps` on every non-time-travel load. Cold, it resumes from the
3974
- // latest snapshot (this is the only place that decides it; stores just
3975
- // apply it). Warm, it keeps a snapshot above a stale checkpoint in the
3976
- // result, so a windowed close that pruned the events in between can't
3977
- // make the fold apply the tail onto stale state.
3873
+ // `with_snaps` on every non-time-travel load: cold, it resumes from the
3874
+ // latest snapshot; warm, it keeps a snapshot above a stale checkpoint,
3875
+ // so a windowed close's prune can't fold the tail onto stale state.
3978
3876
  ...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
3979
3877
  }
3980
3878
  );
@@ -3998,10 +3896,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
3998
3896
  event,
3999
3897
  state: state2,
4000
3898
  version: event?.version ?? cached?.version ?? -1,
4001
- // Head event id, captured atomically with `state`: the last replayed
4002
- // event's id on a cache miss, or the cached checkpoint's event_id on a
4003
- // warm hit with no new events. Consumers read this instead of a
4004
- // separate cache lookup that could race a concurrent commit.
3899
+ // Captured with `state`, so consumers never race a separate cache lookup.
4005
3900
  id: event?.id ?? cached?.event_id ?? -1,
4006
3901
  patches,
4007
3902
  snaps,
@@ -4063,8 +3958,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
4063
3958
  action: {
4064
3959
  name: action2,
4065
3960
  ...target
4066
- // payload intentionally omitted from causation metadata —
4067
- // callers correlate via the correlation id when they need it.
3961
+ // payload intentionally omitted
4068
3962
  },
4069
3963
  event: reactingTo ? {
4070
3964
  id: reactingTo.id,
@@ -4467,61 +4361,35 @@ var DrainController = class {
4467
4361
  _locked = false;
4468
4362
  _ratio = 0.5;
4469
4363
  /**
4470
- * Local wake for streams this worker parked (backoff or defer). The
4471
- * schedule itself is persisted in the store; this runs the drain at the
4472
- * earliest pending visit. A parked stream already carries its mark, so
4473
- * the drain alone finds it, and on an idle app nothing else would.
4474
- * Every drain re-aims it when it ends, and a due entry stays until a pass
4475
- * handles it or a pass that started after it came due claims nothing, so
4476
- * the wake keeps draining until every due stream is reached.
4477
- * `stop()` cancels the timer and a stopped drain never re-schedules it,
4478
- * so a wake never runs after a stop.
4364
+ * Local wake at the earliest parked (backoff or defer) visit; the schedule
4365
+ * itself is persisted. On an idle app nothing else would drain it. A due
4366
+ * entry stays until handled or until a later pass claims nothing.
4479
4367
  */
4480
4368
  _defer = new DeferTimer(() => {
4481
4369
  this._armed = true;
4482
4370
  void this._deps.run_scoped(() => this.drain());
4483
4371
  });
4484
- /** Worker timer. Set when `start()` is active, undefined otherwise. */
4485
4372
  _worker;
4486
- /**
4487
- * Misroutings this controller has reported. A resolver returning a
4488
- * projection's target does so for every matching event; one line per event
4489
- * would bury the signal it exists to raise.
4490
- */
4491
4373
  _misrouted = /* @__PURE__ */ new Set();
4492
4374
  _stopped = false;
4493
- /**
4494
- * Resolves when the cycle in flight finishes, so a graceful shutdown can
4495
- * wait for it. Never rejects.
4496
- */
4375
+ /** Never rejects; graceful shutdown waits on it. */
4497
4376
  _inflight;
4498
4377
  _inflight_done;
4499
4378
  _deps;
4500
4379
  constructor(deps) {
4501
4380
  this._deps = deps;
4502
4381
  }
4503
- /**
4504
- * Signal that a commit (or reset / cold-start) may have produced work.
4505
- * Subsequent `drain()` calls will run the pipeline; once the pipeline
4506
- * settles to no-progress, the controller disarms itself.
4507
- */
4508
4382
  arm() {
4509
4383
  this._armed = true;
4510
4384
  }
4511
- /**
4512
- * Re-seed a persisted `deferred_at` into the local wake at cold start, so
4513
- * an idle deferred stream re-arms at its due-time with no new commit.
4514
- * Seeds collapse into one timer; the earliest wins.
4515
- */
4385
+ /** Cold start: re-seed a persisted `deferred_at` into the local wake. */
4516
4386
  seed_defer(stream, at) {
4517
4387
  this._defer.set(stream, at);
4518
4388
  this._defer.schedule();
4519
4389
  }
4520
- /** Read-only flag — true while a commit / reset is unprocessed. */
4521
4390
  get armed() {
4522
4391
  return this._armed;
4523
4392
  }
4524
- /** The cycle in flight, or `undefined` when idle. */
4525
4393
  get inflight() {
4526
4394
  return this._inflight;
4527
4395
  }
@@ -4529,19 +4397,10 @@ var DrainController = class {
4529
4397
  get lease_millis() {
4530
4398
  return this._deps.defaults?.leaseMillis;
4531
4399
  }
4532
- /** Lane this controller drains (undefined spans all lanes). */
4533
4400
  get lane() {
4534
4401
  return this._deps.lane;
4535
4402
  }
4536
- /**
4537
- * Start a per-lane worker that drains at the lane's `cycleMs`
4538
- * cadence. When armed, the worker calls `drain()` on every
4539
- * tick and re-schedules; when not armed, it still re-schedules at
4540
- * `cycleMs` so a future `arm()` is picked up on the next tick.
4541
- *
4542
- * The setTimeout chain uses `unref()` so it doesn't keep the process
4543
- * alive on its own.
4544
- */
4403
+ /** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
4545
4404
  start(cycleMs) {
4546
4405
  if (this._worker || this._stopped) return;
4547
4406
  const run = this._deps.run_scoped;
@@ -4554,7 +4413,6 @@ var DrainController = class {
4554
4413
  this._worker = setTimeout(tick, cycleMs);
4555
4414
  this._worker.unref();
4556
4415
  }
4557
- /** Stop the per-lane worker. Idempotent. */
4558
4416
  stop() {
4559
4417
  this._stopped = true;
4560
4418
  if (this._worker) {
@@ -4563,7 +4421,6 @@ var DrainController = class {
4563
4421
  }
4564
4422
  this._defer.stop();
4565
4423
  }
4566
- /** Run one drain pass. Short-circuits when not armed or already running. */
4567
4424
  async drain(options = {}) {
4568
4425
  if (!this._armed) return EMPTY_DRAIN;
4569
4426
  if (this._locked) return EMPTY_DRAIN;