@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,190 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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
|
|
21
|
-
*
|
|
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
|
-
*
|
|
60
|
-
*
|
|
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
|
|
109
|
-
*
|
|
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
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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
|
-
*
|
|
136
|
-
*
|
|
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
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
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":"AAAA
|
|
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
|
-
//
|
|
3163
|
-
//
|
|
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
|
-
//
|
|
3402
|
-
//
|
|
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
|
-
*
|
|
3448
|
-
*
|
|
3449
|
-
*
|
|
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
|
|
3974
|
-
// latest snapshot
|
|
3975
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
4471
|
-
*
|
|
4472
|
-
*
|
|
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;
|