@rotorsoft/act 1.3.0 → 1.5.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/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +28 -3
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +6 -6
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +1 -1
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +11 -21
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/lru-map.d.ts +2 -2
- package/dist/@types/internal/lru-map.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +3 -5
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +21 -5
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +4 -30
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/{chunk-XKCTGUW2.js → chunk-I4L224TZ.js} +15 -12
- package/dist/chunk-I4L224TZ.js.map +1 -0
- package/dist/{chunk-TZWDSNSN.js → chunk-PMAZTOSO.js} +31 -5
- package/dist/chunk-PMAZTOSO.js.map +1 -0
- package/dist/index.cjs +281 -246
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +240 -234
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +34 -16
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +7 -6
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs +30 -4
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-TZWDSNSN.js.map +0 -1
- package/dist/chunk-XKCTGUW2.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -204,10 +204,11 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
204
204
|
|
|
205
205
|
// src/internal/lru-map.ts
|
|
206
206
|
var LruMap = class {
|
|
207
|
-
constructor(_maxSize) {
|
|
208
|
-
this._maxSize = _maxSize;
|
|
209
|
-
}
|
|
210
207
|
_entries = /* @__PURE__ */ new Map();
|
|
208
|
+
_max_size;
|
|
209
|
+
constructor(maxSize) {
|
|
210
|
+
this._max_size = maxSize;
|
|
211
|
+
}
|
|
211
212
|
get(key) {
|
|
212
213
|
const v = this._entries.get(key);
|
|
213
214
|
if (v === void 0) return void 0;
|
|
@@ -220,7 +221,7 @@ var LruMap = class {
|
|
|
220
221
|
}
|
|
221
222
|
set(key, value) {
|
|
222
223
|
this._entries.delete(key);
|
|
223
|
-
if (this._entries.size >= this.
|
|
224
|
+
if (this._entries.size >= this._max_size) {
|
|
224
225
|
const oldest = this._entries.keys().next().value;
|
|
225
226
|
this._entries.delete(oldest);
|
|
226
227
|
}
|
|
@@ -302,44 +303,70 @@ var Errors = {
|
|
|
302
303
|
NonRetryableError: "ERR_NON_RETRYABLE"
|
|
303
304
|
};
|
|
304
305
|
var ValidationError = class extends Error {
|
|
306
|
+
/** The type of target being validated (e.g., "action", "event") */
|
|
307
|
+
target;
|
|
308
|
+
/** The invalid payload that failed validation */
|
|
309
|
+
payload;
|
|
310
|
+
/** Zod validation error details */
|
|
311
|
+
details;
|
|
305
312
|
constructor(target, payload, details) {
|
|
306
313
|
super(`Invalid ${target} payload`);
|
|
314
|
+
this.name = Errors.ValidationError;
|
|
307
315
|
this.target = target;
|
|
308
316
|
this.payload = payload;
|
|
309
317
|
this.details = details;
|
|
310
|
-
this.name = Errors.ValidationError;
|
|
311
318
|
}
|
|
312
319
|
};
|
|
313
320
|
var InvariantError = class extends Error {
|
|
321
|
+
/** The action that was attempted */
|
|
322
|
+
action;
|
|
323
|
+
/** The action payload that was provided */
|
|
324
|
+
payload;
|
|
325
|
+
/** The target stream and actor context */
|
|
326
|
+
target;
|
|
327
|
+
/** The current state snapshot when invariant was checked */
|
|
328
|
+
snapshot;
|
|
329
|
+
/** Human-readable description of why the invariant failed */
|
|
330
|
+
description;
|
|
314
331
|
constructor(action2, payload, target, snapshot, description) {
|
|
315
332
|
super(`${action2} failed invariant: ${description}`);
|
|
333
|
+
this.name = Errors.InvariantError;
|
|
316
334
|
this.action = action2;
|
|
317
335
|
this.payload = payload;
|
|
318
336
|
this.target = target;
|
|
319
337
|
this.snapshot = snapshot;
|
|
320
338
|
this.description = description;
|
|
321
|
-
this.name = Errors.InvariantError;
|
|
322
339
|
}
|
|
323
340
|
};
|
|
324
341
|
var ConcurrencyError = class extends Error {
|
|
342
|
+
/** The stream that had the concurrent modification */
|
|
343
|
+
stream;
|
|
344
|
+
/** The actual current version in the store */
|
|
345
|
+
lastVersion;
|
|
346
|
+
/** The events that were being committed */
|
|
347
|
+
events;
|
|
348
|
+
/** The version number that was expected */
|
|
349
|
+
expectedVersion;
|
|
325
350
|
constructor(stream, lastVersion, events, expectedVersion) {
|
|
326
351
|
super(
|
|
327
352
|
`Concurrency error committing "${events.map((e) => `${stream}.${e.name}`).join(
|
|
328
353
|
", "
|
|
329
354
|
)}". Expected version ${expectedVersion} but found version ${lastVersion}.`
|
|
330
355
|
);
|
|
356
|
+
this.name = Errors.ConcurrencyError;
|
|
331
357
|
this.stream = stream;
|
|
332
358
|
this.lastVersion = lastVersion;
|
|
333
359
|
this.events = events;
|
|
334
360
|
this.expectedVersion = expectedVersion;
|
|
335
|
-
this.name = Errors.ConcurrencyError;
|
|
336
361
|
}
|
|
337
362
|
};
|
|
338
363
|
var StreamClosedError = class extends Error {
|
|
364
|
+
/** The stream that is closed */
|
|
365
|
+
stream;
|
|
339
366
|
constructor(stream) {
|
|
340
367
|
super(`Stream "${stream}" is closed (tombstoned)`);
|
|
341
|
-
this.stream = stream;
|
|
342
368
|
this.name = Errors.StreamClosedError;
|
|
369
|
+
this.stream = stream;
|
|
343
370
|
}
|
|
344
371
|
};
|
|
345
372
|
var NonRetryableError = class extends Error {
|
|
@@ -495,12 +522,8 @@ async function sleep(ms) {
|
|
|
495
522
|
|
|
496
523
|
// src/adapters/in-memory-store.ts
|
|
497
524
|
var InMemoryStream = class {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
this.source = source;
|
|
501
|
-
this._priority = priority;
|
|
502
|
-
this._lane = lane;
|
|
503
|
-
}
|
|
525
|
+
stream;
|
|
526
|
+
source;
|
|
504
527
|
_at = -1;
|
|
505
528
|
_retry = -1;
|
|
506
529
|
_blocked = false;
|
|
@@ -509,6 +532,12 @@ var InMemoryStream = class {
|
|
|
509
532
|
_leased_until = void 0;
|
|
510
533
|
_priority = 0;
|
|
511
534
|
_lane = DEFAULT_LANE;
|
|
535
|
+
constructor(stream, source, priority = 0, lane = DEFAULT_LANE) {
|
|
536
|
+
this.stream = stream;
|
|
537
|
+
this.source = source;
|
|
538
|
+
this._priority = priority;
|
|
539
|
+
this._lane = lane;
|
|
540
|
+
}
|
|
512
541
|
get priority() {
|
|
513
542
|
return this._priority;
|
|
514
543
|
}
|
|
@@ -1902,18 +1931,23 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
|
|
|
1902
1931
|
|
|
1903
1932
|
// src/internal/correlate-cycle.ts
|
|
1904
1933
|
var CorrelateCycle = class {
|
|
1905
|
-
constructor(registry, staticTargets, hasDynamicResolvers, cd, maxSubscribedStreams, onInit) {
|
|
1906
|
-
this.registry = registry;
|
|
1907
|
-
this.staticTargets = staticTargets;
|
|
1908
|
-
this.hasDynamicResolvers = hasDynamicResolvers;
|
|
1909
|
-
this.cd = cd;
|
|
1910
|
-
this.onInit = onInit;
|
|
1911
|
-
this._subscribed = new LruSet(maxSubscribedStreams);
|
|
1912
|
-
}
|
|
1913
1934
|
_checkpoint = -1;
|
|
1914
1935
|
_initialized = false;
|
|
1915
1936
|
_timer = void 0;
|
|
1916
1937
|
_subscribed;
|
|
1938
|
+
_registry;
|
|
1939
|
+
_static_targets;
|
|
1940
|
+
_has_dynamic_resolvers;
|
|
1941
|
+
_cd;
|
|
1942
|
+
_on_init;
|
|
1943
|
+
constructor(registry, staticTargets, hasDynamicResolvers, cd, maxSubscribedStreams, onInit) {
|
|
1944
|
+
this._subscribed = new LruSet(maxSubscribedStreams);
|
|
1945
|
+
this._registry = registry;
|
|
1946
|
+
this._static_targets = staticTargets;
|
|
1947
|
+
this._has_dynamic_resolvers = hasDynamicResolvers;
|
|
1948
|
+
this._cd = cd;
|
|
1949
|
+
this._on_init = onInit;
|
|
1950
|
+
}
|
|
1917
1951
|
/** Last correlated event id. */
|
|
1918
1952
|
get checkpoint() {
|
|
1919
1953
|
return this._checkpoint;
|
|
@@ -1928,10 +1962,10 @@ var CorrelateCycle = class {
|
|
|
1928
1962
|
async init() {
|
|
1929
1963
|
if (this._initialized) return;
|
|
1930
1964
|
this._initialized = true;
|
|
1931
|
-
const { watermark } = await store2().subscribe([...this.
|
|
1965
|
+
const { watermark } = await store2().subscribe([...this._static_targets]);
|
|
1932
1966
|
this._checkpoint = watermark;
|
|
1933
|
-
this.
|
|
1934
|
-
for (const { stream } of this.
|
|
1967
|
+
this._on_init?.();
|
|
1968
|
+
for (const { stream } of this._static_targets) {
|
|
1935
1969
|
this._subscribed.add(stream);
|
|
1936
1970
|
}
|
|
1937
1971
|
}
|
|
@@ -1942,7 +1976,7 @@ var CorrelateCycle = class {
|
|
|
1942
1976
|
*/
|
|
1943
1977
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
1944
1978
|
await this.init();
|
|
1945
|
-
if (!this.
|
|
1979
|
+
if (!this._has_dynamic_resolvers)
|
|
1946
1980
|
return { subscribed: 0, last_id: this._checkpoint };
|
|
1947
1981
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
1948
1982
|
const correlated = /* @__PURE__ */ new Map();
|
|
@@ -1950,7 +1984,7 @@ var CorrelateCycle = class {
|
|
|
1950
1984
|
await store2().query(
|
|
1951
1985
|
(event) => {
|
|
1952
1986
|
last_id = event.id;
|
|
1953
|
-
const register = this.
|
|
1987
|
+
const register = this._registry.events[event.name];
|
|
1954
1988
|
if (register) {
|
|
1955
1989
|
for (const reaction of register.reactions.values()) {
|
|
1956
1990
|
if (typeof reaction.resolver !== "function") continue;
|
|
@@ -1986,7 +2020,7 @@ var CorrelateCycle = class {
|
|
|
1986
2020
|
lane
|
|
1987
2021
|
})
|
|
1988
2022
|
);
|
|
1989
|
-
const { subscribed } = await this.
|
|
2023
|
+
const { subscribed } = await this._cd.subscribe(streams);
|
|
1990
2024
|
this._checkpoint = last_id;
|
|
1991
2025
|
if (subscribed) {
|
|
1992
2026
|
for (const { stream } of streams) {
|
|
@@ -2089,51 +2123,7 @@ var subscribe = (streams) => store2().subscribe(streams);
|
|
|
2089
2123
|
|
|
2090
2124
|
// src/internal/event-sourcing.ts
|
|
2091
2125
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
2092
|
-
|
|
2093
|
-
const state2 = {
|
|
2094
|
-
slot: null,
|
|
2095
|
-
onProduce: null,
|
|
2096
|
-
onConsume: null,
|
|
2097
|
-
done: false,
|
|
2098
|
-
error: void 0
|
|
2099
|
-
};
|
|
2100
|
-
const wakeProduce = () => {
|
|
2101
|
-
const fn = state2.onProduce;
|
|
2102
|
-
state2.onProduce = null;
|
|
2103
|
-
if (fn) fn();
|
|
2104
|
-
};
|
|
2105
|
-
void source.query((event) => {
|
|
2106
|
-
state2.slot = event;
|
|
2107
|
-
wakeProduce();
|
|
2108
|
-
return new Promise((resolve) => {
|
|
2109
|
-
state2.onConsume = () => resolve();
|
|
2110
|
-
});
|
|
2111
|
-
}, filter).then(
|
|
2112
|
-
() => {
|
|
2113
|
-
state2.done = true;
|
|
2114
|
-
wakeProduce();
|
|
2115
|
-
},
|
|
2116
|
-
(err) => {
|
|
2117
|
-
state2.error = err;
|
|
2118
|
-
state2.done = true;
|
|
2119
|
-
wakeProduce();
|
|
2120
|
-
}
|
|
2121
|
-
);
|
|
2122
|
-
while (true) {
|
|
2123
|
-
if (state2.slot === null && !state2.done)
|
|
2124
|
-
await new Promise((resolve) => {
|
|
2125
|
-
state2.onProduce = resolve;
|
|
2126
|
-
});
|
|
2127
|
-
if (state2.error) throw state2.error;
|
|
2128
|
-
if (state2.slot === null) return;
|
|
2129
|
-
const event = state2.slot;
|
|
2130
|
-
state2.slot = null;
|
|
2131
|
-
const fn = state2.onConsume;
|
|
2132
|
-
state2.onConsume = null;
|
|
2133
|
-
fn();
|
|
2134
|
-
yield event;
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2126
|
+
var DEFAULT_BATCH = 500;
|
|
2137
2127
|
async function snap(snapshot) {
|
|
2138
2128
|
try {
|
|
2139
2129
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -2165,7 +2155,7 @@ async function tombstone(stream, expectedVersion, correlation) {
|
|
|
2165
2155
|
throw error;
|
|
2166
2156
|
}
|
|
2167
2157
|
}
|
|
2168
|
-
function
|
|
2158
|
+
function is_valid(event) {
|
|
2169
2159
|
if (event.version < 0) return false;
|
|
2170
2160
|
if (!(event.created instanceof Date) || Number.isNaN(event.created.getTime()))
|
|
2171
2161
|
return false;
|
|
@@ -2173,48 +2163,70 @@ function isValid(event) {
|
|
|
2173
2163
|
}
|
|
2174
2164
|
async function scan(source, opts = {}, callback) {
|
|
2175
2165
|
const { drop_snapshots = false, on_progress } = opts;
|
|
2176
|
-
const
|
|
2166
|
+
const limit = opts.batch_size ?? DEFAULT_BATCH;
|
|
2167
|
+
const id_map = /* @__PURE__ */ new Map();
|
|
2177
2168
|
let kept = 0;
|
|
2178
|
-
let
|
|
2169
|
+
let dropped_snaps = 0;
|
|
2179
2170
|
let processed = 0;
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2171
|
+
let at;
|
|
2172
|
+
let max_id;
|
|
2173
|
+
const probed = await source.query(
|
|
2174
|
+
(e) => {
|
|
2175
|
+
max_id = e.id;
|
|
2176
|
+
},
|
|
2177
|
+
{ backward: true, limit: 1 }
|
|
2178
|
+
);
|
|
2179
|
+
if (probed !== 1) max_id = void 0;
|
|
2180
|
+
while (true) {
|
|
2181
|
+
let got = 0;
|
|
2182
|
+
let id;
|
|
2183
|
+
await source.query(
|
|
2184
|
+
async (event) => {
|
|
2185
|
+
got++;
|
|
2186
|
+
id = event.id;
|
|
2187
|
+
processed++;
|
|
2188
|
+
if (!is_valid(event))
|
|
2189
|
+
throw new Error(`Invalid event at index ${processed}`);
|
|
2190
|
+
if (on_progress) on_progress({ processed, id: event.id, max_id });
|
|
2191
|
+
if (drop_snapshots && event.name === SNAP_EVENT) {
|
|
2192
|
+
dropped_snaps++;
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (!callback) {
|
|
2196
|
+
kept++;
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
let remapped = event;
|
|
2200
|
+
const caused_by = event.meta.causation.event?.id;
|
|
2201
|
+
if (caused_by !== void 0) {
|
|
2202
|
+
const new_caused_by = id_map.get(caused_by);
|
|
2203
|
+
if (new_caused_by !== void 0 && new_caused_by !== caused_by) {
|
|
2204
|
+
remapped = {
|
|
2205
|
+
...event,
|
|
2206
|
+
meta: {
|
|
2207
|
+
...event.meta,
|
|
2208
|
+
causation: {
|
|
2209
|
+
...event.meta.causation,
|
|
2210
|
+
event: { ...event.meta.causation.event, id: new_caused_by }
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2205
2214
|
}
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2215
|
+
}
|
|
2216
|
+
const new_id = await callback(remapped);
|
|
2217
|
+
id_map.set(event.id, new_id);
|
|
2218
|
+
kept++;
|
|
2219
|
+
},
|
|
2220
|
+
{ after: at, limit }
|
|
2221
|
+
);
|
|
2222
|
+
if (got !== limit) break;
|
|
2223
|
+
at = id;
|
|
2212
2224
|
}
|
|
2213
2225
|
return {
|
|
2214
2226
|
kept,
|
|
2215
2227
|
dropped: {
|
|
2216
2228
|
closed_streams: 0,
|
|
2217
|
-
snapshots:
|
|
2229
|
+
snapshots: dropped_snaps,
|
|
2218
2230
|
empty_streams: 0
|
|
2219
2231
|
}
|
|
2220
2232
|
};
|
|
@@ -2632,9 +2644,6 @@ var EMPTY_DRAIN = {
|
|
|
2632
2644
|
blocked: []
|
|
2633
2645
|
};
|
|
2634
2646
|
var DrainController = class {
|
|
2635
|
-
constructor(deps) {
|
|
2636
|
-
this.deps = deps;
|
|
2637
|
-
}
|
|
2638
2647
|
_armed = false;
|
|
2639
2648
|
_locked = false;
|
|
2640
2649
|
_ratio = 0.5;
|
|
@@ -2650,6 +2659,10 @@ var DrainController = class {
|
|
|
2650
2659
|
/** Worker timer (ACT-1103). Set when `start()` is active, undefined otherwise. */
|
|
2651
2660
|
_worker;
|
|
2652
2661
|
_stopped = false;
|
|
2662
|
+
_deps;
|
|
2663
|
+
constructor(deps) {
|
|
2664
|
+
this._deps = deps;
|
|
2665
|
+
}
|
|
2653
2666
|
/**
|
|
2654
2667
|
* Signal that a commit (or reset / cold-start) may have produced work.
|
|
2655
2668
|
* Subsequent `drain()` calls will run the pipeline; once the pipeline
|
|
@@ -2690,7 +2703,7 @@ var DrainController = class {
|
|
|
2690
2703
|
}
|
|
2691
2704
|
/** Lane this controller drains (undefined = legacy single-lane span). */
|
|
2692
2705
|
get lane() {
|
|
2693
|
-
return this.
|
|
2706
|
+
return this._deps.lane;
|
|
2694
2707
|
}
|
|
2695
2708
|
/**
|
|
2696
2709
|
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
@@ -2724,7 +2737,7 @@ var DrainController = class {
|
|
|
2724
2737
|
async drain(options = {}) {
|
|
2725
2738
|
if (!this._armed) return EMPTY_DRAIN;
|
|
2726
2739
|
if (this._locked) return EMPTY_DRAIN;
|
|
2727
|
-
const d = this.
|
|
2740
|
+
const d = this._deps.defaults ?? {};
|
|
2728
2741
|
const streamLimit = d.streamLimit ?? options.streamLimit ?? 10;
|
|
2729
2742
|
const eventLimit = d.eventLimit ?? options.eventLimit ?? 10;
|
|
2730
2743
|
const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? 1e4;
|
|
@@ -2733,24 +2746,24 @@ var DrainController = class {
|
|
|
2733
2746
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
2734
2747
|
const leading = streamLimit - lagging;
|
|
2735
2748
|
const cycle = await runDrainCycle(
|
|
2736
|
-
this.
|
|
2737
|
-
this.
|
|
2738
|
-
this.
|
|
2739
|
-
this.
|
|
2740
|
-
this.
|
|
2749
|
+
this._deps.ops,
|
|
2750
|
+
this._deps.registry,
|
|
2751
|
+
this._deps.batchHandlers,
|
|
2752
|
+
this._deps.handle,
|
|
2753
|
+
this._deps.handleBatch,
|
|
2741
2754
|
lagging,
|
|
2742
2755
|
leading,
|
|
2743
2756
|
eventLimit,
|
|
2744
2757
|
leaseMillis,
|
|
2745
2758
|
this._backoff.size > 0 ? this.isDeferred : void 0,
|
|
2746
|
-
this.
|
|
2759
|
+
this._deps.lane
|
|
2747
2760
|
);
|
|
2748
2761
|
if (!cycle) {
|
|
2749
2762
|
this._armed = false;
|
|
2750
2763
|
return EMPTY_DRAIN;
|
|
2751
2764
|
}
|
|
2752
2765
|
const { leased, fetched, handled, acked, blocked } = cycle;
|
|
2753
|
-
traceCycle(this.
|
|
2766
|
+
traceCycle(this._deps.logger, leased, fetched, handled, acked, blocked);
|
|
2754
2767
|
this._ratio = computeLagLeadRatio(handled, lagging, leading);
|
|
2755
2768
|
for (const lease of acked) this._backoff.delete(lease.stream);
|
|
2756
2769
|
for (const lease of blocked) this._backoff.delete(lease.stream);
|
|
@@ -2760,13 +2773,13 @@ var DrainController = class {
|
|
|
2760
2773
|
}
|
|
2761
2774
|
}
|
|
2762
2775
|
if (this._backoff.size > 0) this.scheduleBackoffWake();
|
|
2763
|
-
if (acked.length) this.
|
|
2764
|
-
if (blocked.length) this.
|
|
2776
|
+
if (acked.length) this._deps.onAcked(acked);
|
|
2777
|
+
if (blocked.length) this._deps.onBlocked(blocked);
|
|
2765
2778
|
const hasErrors = handled.some(({ error }) => error);
|
|
2766
2779
|
if (!acked.length && !blocked.length && !hasErrors) this._armed = false;
|
|
2767
2780
|
return { fetched, leased, acked, blocked };
|
|
2768
2781
|
} catch (error) {
|
|
2769
|
-
this.
|
|
2782
|
+
this._deps.logger.error(error);
|
|
2770
2783
|
return EMPTY_DRAIN;
|
|
2771
2784
|
} finally {
|
|
2772
2785
|
this._locked = false;
|
|
@@ -3027,12 +3040,15 @@ function buildHandleBatch(logger) {
|
|
|
3027
3040
|
|
|
3028
3041
|
// src/internal/settle.ts
|
|
3029
3042
|
var SettleLoop = class {
|
|
3030
|
-
constructor(deps, defaultDebounceMs) {
|
|
3031
|
-
this.deps = deps;
|
|
3032
|
-
this.defaultDebounceMs = defaultDebounceMs;
|
|
3033
|
-
}
|
|
3034
3043
|
_timer = void 0;
|
|
3035
3044
|
_running = false;
|
|
3045
|
+
_deps;
|
|
3046
|
+
/** Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`. */
|
|
3047
|
+
_default_debounce_ms;
|
|
3048
|
+
constructor(deps, defaultDebounceMs) {
|
|
3049
|
+
this._deps = deps;
|
|
3050
|
+
this._default_debounce_ms = defaultDebounceMs;
|
|
3051
|
+
}
|
|
3036
3052
|
/**
|
|
3037
3053
|
* Schedule a settle pass. Multiple calls inside the debounce window
|
|
3038
3054
|
* coalesce into one cycle. The cycle runs correlate→drain in a loop
|
|
@@ -3042,7 +3058,7 @@ var SettleLoop = class {
|
|
|
3042
3058
|
*/
|
|
3043
3059
|
schedule(options = {}) {
|
|
3044
3060
|
const {
|
|
3045
|
-
debounceMs = this.
|
|
3061
|
+
debounceMs = this._default_debounce_ms,
|
|
3046
3062
|
correlate: correlateQuery = { after: -1, limit: 100 },
|
|
3047
3063
|
maxPasses = Infinity,
|
|
3048
3064
|
...drainOptions
|
|
@@ -3053,19 +3069,19 @@ var SettleLoop = class {
|
|
|
3053
3069
|
if (this._running) return;
|
|
3054
3070
|
this._running = true;
|
|
3055
3071
|
(async () => {
|
|
3056
|
-
await this.
|
|
3072
|
+
await this._deps.init();
|
|
3057
3073
|
let lastDrain;
|
|
3058
3074
|
for (let i = 0; i < maxPasses; i++) {
|
|
3059
|
-
const { subscribed } = await this.
|
|
3075
|
+
const { subscribed } = await this._deps.correlate({
|
|
3060
3076
|
...correlateQuery,
|
|
3061
|
-
after: this.
|
|
3077
|
+
after: this._deps.checkpoint()
|
|
3062
3078
|
});
|
|
3063
|
-
lastDrain = await this.
|
|
3079
|
+
lastDrain = await this._deps.drain(drainOptions);
|
|
3064
3080
|
const made_progress = subscribed > 0 || lastDrain.acked.length > 0 || lastDrain.blocked.length > 0;
|
|
3065
3081
|
if (!made_progress) break;
|
|
3066
3082
|
}
|
|
3067
|
-
if (lastDrain) this.
|
|
3068
|
-
})().catch((err) => this.
|
|
3083
|
+
if (lastDrain) this._deps.onSettled(lastDrain);
|
|
3084
|
+
})().catch((err) => this._deps.logger.error(err)).finally(() => {
|
|
3069
3085
|
this._running = false;
|
|
3070
3086
|
});
|
|
3071
3087
|
}, debounceMs);
|
|
@@ -3083,6 +3099,117 @@ var SettleLoop = class {
|
|
|
3083
3099
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
3084
3100
|
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
3085
3101
|
var Act = class {
|
|
3102
|
+
_emitter = new import_node_events.default();
|
|
3103
|
+
/** #803: gate the `Store.notify` subscription side. */
|
|
3104
|
+
_listen;
|
|
3105
|
+
/** #803: gate the local reaction pipeline (drain controllers, settle, correlate). */
|
|
3106
|
+
_drain;
|
|
3107
|
+
/** Event names with at least one registered reaction (computed at build time) */
|
|
3108
|
+
_reactive_events;
|
|
3109
|
+
/** One DrainController per active lane, keyed by lane name. */
|
|
3110
|
+
_drain_controllers;
|
|
3111
|
+
/** Correlation state machine: lazy init, dynamic-resolver scan, periodic worker. */
|
|
3112
|
+
_correlate;
|
|
3113
|
+
/** Debounced correlate→drain catch-up loop. */
|
|
3114
|
+
_settle;
|
|
3115
|
+
/**
|
|
3116
|
+
* Disposer for the cross-process notify subscription, set up eagerly
|
|
3117
|
+
* during construction. Held as a promise because the subscription
|
|
3118
|
+
* itself may be async (the PG adapter checks out a dedicated client
|
|
3119
|
+
* and runs `LISTEN` before resolving). Resolves to `undefined` when
|
|
3120
|
+
* the store doesn't implement `notify` or there are no registered
|
|
3121
|
+
* reactions.
|
|
3122
|
+
*
|
|
3123
|
+
* **Contract:** the configured store must be injected via
|
|
3124
|
+
* {@link store}`(adapter)` *before* calling `act()...build()`. The
|
|
3125
|
+
* orchestrator wires notify against whatever store is current at
|
|
3126
|
+
* construction time — late injection after build is unsupported.
|
|
3127
|
+
*/
|
|
3128
|
+
_notify_disposer;
|
|
3129
|
+
/** Public registry — kept as-is per the no-prefix-on-public convention. */
|
|
3130
|
+
registry;
|
|
3131
|
+
/** Map of state name → state definition; populated by the builder. */
|
|
3132
|
+
_states;
|
|
3133
|
+
/**
|
|
3134
|
+
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
3135
|
+
* via {@link ActLifecycleEvents}.
|
|
3136
|
+
*/
|
|
3137
|
+
emit(event, args) {
|
|
3138
|
+
return this._emitter.emit(event, args);
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* Register a listener for a lifecycle event. The listener receives the
|
|
3142
|
+
* event-specific payload.
|
|
3143
|
+
*/
|
|
3144
|
+
on(event, listener) {
|
|
3145
|
+
this._emitter.on(event, listener);
|
|
3146
|
+
return this;
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Remove a previously registered lifecycle listener.
|
|
3150
|
+
*/
|
|
3151
|
+
off(event, listener) {
|
|
3152
|
+
this._emitter.off(event, listener);
|
|
3153
|
+
return this;
|
|
3154
|
+
}
|
|
3155
|
+
/** Batch handlers for static-target projections (target → handler) */
|
|
3156
|
+
_batch_handlers;
|
|
3157
|
+
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
3158
|
+
_es;
|
|
3159
|
+
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
3160
|
+
_cd;
|
|
3161
|
+
/**
|
|
3162
|
+
* Event-name → owning state, computed at build time. The duplicate-event
|
|
3163
|
+
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
3164
|
+
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
3165
|
+
* set when seeding a `restart` snapshot in multi-state apps.
|
|
3166
|
+
*/
|
|
3167
|
+
_event_to_state;
|
|
3168
|
+
/**
|
|
3169
|
+
* Event-name → lane fan-in for selective arming (ACT-1103). Built by
|
|
3170
|
+
* `classifyRegistry` once per build. `"all"` means at least one of
|
|
3171
|
+
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3172
|
+
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3173
|
+
* reactions target.
|
|
3174
|
+
*/
|
|
3175
|
+
_event_to_lanes;
|
|
3176
|
+
/**
|
|
3177
|
+
* Audit dependency bag (#723). Built once at construction; held as
|
|
3178
|
+
* an immutable snapshot of the registry state the audit module
|
|
3179
|
+
* needs. Lives in `internal/audit.ts` — this orchestrator never
|
|
3180
|
+
* carries audit logic, only the deps + a one-liner that hands them
|
|
3181
|
+
* over.
|
|
3182
|
+
*/
|
|
3183
|
+
_audit_deps;
|
|
3184
|
+
/** Logger resolved at construction time (after user port configuration) */
|
|
3185
|
+
_logger = log();
|
|
3186
|
+
/** Wraps a public-method body so internal `store()`/`cache()` resolve to the
|
|
3187
|
+
* per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
|
|
3188
|
+
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
3189
|
+
* tests that dispose and re-seed mid-suite. */
|
|
3190
|
+
_scoped;
|
|
3191
|
+
/**
|
|
3192
|
+
* Correlation-id generator for originating actions. Bound at
|
|
3193
|
+
* construction from `options.correlator ?? defaultCorrelator`. The
|
|
3194
|
+
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
3195
|
+
* uses it via {@link closeCorrelation}.
|
|
3196
|
+
*/
|
|
3197
|
+
_correlator;
|
|
3198
|
+
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
3199
|
+
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
3200
|
+
_bound_do = this.do.bind(this);
|
|
3201
|
+
_bound_load = this.load.bind(this);
|
|
3202
|
+
_bound_query = this.query.bind(this);
|
|
3203
|
+
_bound_query_array = this.query_array.bind(this);
|
|
3204
|
+
/** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
|
|
3205
|
+
_handle;
|
|
3206
|
+
_handle_batch;
|
|
3207
|
+
/** Declared drain lanes (ACT-1103). */
|
|
3208
|
+
_lanes;
|
|
3209
|
+
/** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
|
|
3210
|
+
get lanes() {
|
|
3211
|
+
return this._lanes;
|
|
3212
|
+
}
|
|
3086
3213
|
/**
|
|
3087
3214
|
* Create a new Act orchestrator. Prefer the {@link act} builder over
|
|
3088
3215
|
* direct construction — `act()...build()` wires the registry, merges
|
|
@@ -3097,9 +3224,9 @@ var Act = class {
|
|
|
3097
3224
|
* these from `.withLane(...)` calls. Slice 1 records them on the
|
|
3098
3225
|
* instance; later slices fan out one `DrainController` per lane.
|
|
3099
3226
|
*/
|
|
3100
|
-
constructor(registry,
|
|
3227
|
+
constructor(registry, states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
|
|
3101
3228
|
this.registry = registry;
|
|
3102
|
-
this._states =
|
|
3229
|
+
this._states = states;
|
|
3103
3230
|
this._batch_handlers = batchHandlers;
|
|
3104
3231
|
this._lanes = lanes;
|
|
3105
3232
|
if (options.onlyLanes && options.onlyLanes.length > 0) {
|
|
@@ -3133,6 +3260,8 @@ var Act = class {
|
|
|
3133
3260
|
eventToLanes
|
|
3134
3261
|
} = classifyRegistry(this.registry, this._states);
|
|
3135
3262
|
this._reactive_events = reactiveEvents;
|
|
3263
|
+
this._listen = options.listen !== false;
|
|
3264
|
+
this._drain = options.drain !== false;
|
|
3136
3265
|
this._event_to_state = eventToState;
|
|
3137
3266
|
this._event_to_lanes = eventToLanes;
|
|
3138
3267
|
const allLanes = ["default", ...lanes.map((l) => l.name)];
|
|
@@ -3160,7 +3289,8 @@ var Act = class {
|
|
|
3160
3289
|
leaseMillis: cfg.leaseMillis
|
|
3161
3290
|
}
|
|
3162
3291
|
});
|
|
3163
|
-
if (cfg?.cycleMs !== void 0
|
|
3292
|
+
if (cfg?.cycleMs !== void 0 && options.drain !== false)
|
|
3293
|
+
controller.start(cfg.cycleMs);
|
|
3164
3294
|
this._drain_controllers.set(name, controller);
|
|
3165
3295
|
}
|
|
3166
3296
|
this._audit_deps = {
|
|
@@ -3178,9 +3308,10 @@ var Act = class {
|
|
|
3178
3308
|
hasDynamicResolvers,
|
|
3179
3309
|
this._cd,
|
|
3180
3310
|
options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3181
|
-
// Cold start: assume drain is needed (historical events may need processing)
|
|
3311
|
+
// Cold start: assume drain is needed (historical events may need processing).
|
|
3312
|
+
// #803: writer-only instances skip the cold-start arm.
|
|
3182
3313
|
() => {
|
|
3183
|
-
if (this._reactive_events.size > 0) this._armAll();
|
|
3314
|
+
if (this._drain && this._reactive_events.size > 0) this._armAll();
|
|
3184
3315
|
}
|
|
3185
3316
|
);
|
|
3186
3317
|
this._settle = new SettleLoop(
|
|
@@ -3197,109 +3328,6 @@ var Act = class {
|
|
|
3197
3328
|
this._notify_disposer = this._wireNotify(options.scoped?.store ?? store2());
|
|
3198
3329
|
dispose(() => this.shutdown());
|
|
3199
3330
|
}
|
|
3200
|
-
_emitter = new import_node_events.default();
|
|
3201
|
-
/** Event names with at least one registered reaction (computed at build time) */
|
|
3202
|
-
_reactive_events;
|
|
3203
|
-
/** One DrainController per active lane, keyed by lane name. */
|
|
3204
|
-
_drain_controllers;
|
|
3205
|
-
/** Correlation state machine: lazy init, dynamic-resolver scan, periodic worker. */
|
|
3206
|
-
_correlate;
|
|
3207
|
-
/** Debounced correlate→drain catch-up loop. */
|
|
3208
|
-
_settle;
|
|
3209
|
-
/**
|
|
3210
|
-
* Disposer for the cross-process notify subscription, set up eagerly
|
|
3211
|
-
* during construction. Held as a promise because the subscription
|
|
3212
|
-
* itself may be async (the PG adapter checks out a dedicated client
|
|
3213
|
-
* and runs `LISTEN` before resolving). Resolves to `undefined` when
|
|
3214
|
-
* the store doesn't implement `notify` or there are no registered
|
|
3215
|
-
* reactions.
|
|
3216
|
-
*
|
|
3217
|
-
* **Contract:** the configured store must be injected via
|
|
3218
|
-
* {@link store}`(adapter)` *before* calling `act()...build()`. The
|
|
3219
|
-
* orchestrator wires notify against whatever store is current at
|
|
3220
|
-
* construction time — late injection after build is unsupported.
|
|
3221
|
-
*/
|
|
3222
|
-
_notify_disposer;
|
|
3223
|
-
/**
|
|
3224
|
-
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
3225
|
-
* via {@link ActLifecycleEvents}.
|
|
3226
|
-
*/
|
|
3227
|
-
emit(event, args) {
|
|
3228
|
-
return this._emitter.emit(event, args);
|
|
3229
|
-
}
|
|
3230
|
-
/**
|
|
3231
|
-
* Register a listener for a lifecycle event. The listener receives the
|
|
3232
|
-
* event-specific payload.
|
|
3233
|
-
*/
|
|
3234
|
-
on(event, listener) {
|
|
3235
|
-
this._emitter.on(event, listener);
|
|
3236
|
-
return this;
|
|
3237
|
-
}
|
|
3238
|
-
/**
|
|
3239
|
-
* Remove a previously registered lifecycle listener.
|
|
3240
|
-
*/
|
|
3241
|
-
off(event, listener) {
|
|
3242
|
-
this._emitter.off(event, listener);
|
|
3243
|
-
return this;
|
|
3244
|
-
}
|
|
3245
|
-
/** Batch handlers for static-target projections (target → handler) */
|
|
3246
|
-
_batch_handlers;
|
|
3247
|
-
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
3248
|
-
_es;
|
|
3249
|
-
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
3250
|
-
_cd;
|
|
3251
|
-
/**
|
|
3252
|
-
* Event-name → owning state, computed at build time. The duplicate-event
|
|
3253
|
-
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
3254
|
-
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
3255
|
-
* set when seeding a `restart` snapshot in multi-state apps.
|
|
3256
|
-
*/
|
|
3257
|
-
_event_to_state;
|
|
3258
|
-
/**
|
|
3259
|
-
* Event-name → lane fan-in for selective arming (ACT-1103). Built by
|
|
3260
|
-
* `classifyRegistry` once per build. `"all"` means at least one of
|
|
3261
|
-
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3262
|
-
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3263
|
-
* reactions target.
|
|
3264
|
-
*/
|
|
3265
|
-
_event_to_lanes;
|
|
3266
|
-
/**
|
|
3267
|
-
* Audit dependency bag (#723). Built once at construction; held as
|
|
3268
|
-
* an immutable snapshot of the registry state the audit module
|
|
3269
|
-
* needs. Lives in `internal/audit.ts` — this orchestrator never
|
|
3270
|
-
* carries audit logic, only the deps + a one-liner that hands them
|
|
3271
|
-
* over.
|
|
3272
|
-
*/
|
|
3273
|
-
_audit_deps;
|
|
3274
|
-
/** Logger resolved at construction time (after user port configuration) */
|
|
3275
|
-
_logger = log();
|
|
3276
|
-
/** Wraps a public-method body so internal `store()`/`cache()` resolve to the
|
|
3277
|
-
* per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
|
|
3278
|
-
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
3279
|
-
* tests that dispose and re-seed mid-suite. */
|
|
3280
|
-
_scoped;
|
|
3281
|
-
/**
|
|
3282
|
-
* Correlation-id generator for originating actions. Bound at
|
|
3283
|
-
* construction from `options.correlator ?? defaultCorrelator`. The
|
|
3284
|
-
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
3285
|
-
* uses it via {@link closeCorrelation}.
|
|
3286
|
-
*/
|
|
3287
|
-
_correlator;
|
|
3288
|
-
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
3289
|
-
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
3290
|
-
_bound_do = this.do.bind(this);
|
|
3291
|
-
_bound_load = this.load.bind(this);
|
|
3292
|
-
_bound_query = this.query.bind(this);
|
|
3293
|
-
_bound_query_array = this.query_array.bind(this);
|
|
3294
|
-
/** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
|
|
3295
|
-
_handle;
|
|
3296
|
-
_handle_batch;
|
|
3297
|
-
/** Declared drain lanes (ACT-1103). */
|
|
3298
|
-
_lanes;
|
|
3299
|
-
/** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
|
|
3300
|
-
get lanes() {
|
|
3301
|
-
return this._lanes;
|
|
3302
|
-
}
|
|
3303
3331
|
/** True after the first `shutdown()` call. Guards idempotency. */
|
|
3304
3332
|
_shutdown_promise;
|
|
3305
3333
|
/**
|
|
@@ -3334,14 +3362,17 @@ var Act = class {
|
|
|
3334
3362
|
async _wireNotify(s) {
|
|
3335
3363
|
if (this._reactive_events.size === 0) return void 0;
|
|
3336
3364
|
if (!s.notify) return void 0;
|
|
3365
|
+
if (!this._listen) return void 0;
|
|
3337
3366
|
try {
|
|
3338
3367
|
return await s.notify((notification) => {
|
|
3339
3368
|
try {
|
|
3340
3369
|
this.emit("notified", notification);
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3370
|
+
if (this._drain) {
|
|
3371
|
+
const armed = this._armForEventNames(
|
|
3372
|
+
notification.events.map((e) => e.name)
|
|
3373
|
+
);
|
|
3374
|
+
if (armed) this._settle.schedule({ debounceMs: 0 });
|
|
3375
|
+
}
|
|
3345
3376
|
} catch (err) {
|
|
3346
3377
|
this._logger.error(err, "notified handler threw");
|
|
3347
3378
|
}
|
|
@@ -3592,6 +3623,8 @@ var Act = class {
|
|
|
3592
3623
|
* @see {@link start_correlations} for automatic correlation
|
|
3593
3624
|
*/
|
|
3594
3625
|
async drain(options = {}) {
|
|
3626
|
+
if (!this._drain)
|
|
3627
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
3595
3628
|
return this._scoped(() => this._drainAll(options));
|
|
3596
3629
|
}
|
|
3597
3630
|
/** Arm every active lane controller (ACT-1103). */
|
|
@@ -3692,6 +3725,7 @@ var Act = class {
|
|
|
3692
3725
|
* @see {@link stop_correlations} to stop automatic correlation
|
|
3693
3726
|
*/
|
|
3694
3727
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
3728
|
+
if (!this._drain) return { subscribed: 0, last_id: -1 };
|
|
3695
3729
|
return this._scoped(() => this._correlate.correlate(query));
|
|
3696
3730
|
}
|
|
3697
3731
|
/**
|
|
@@ -4117,6 +4151,7 @@ var Act = class {
|
|
|
4117
4151
|
* @see {@link correlate} for manual correlation
|
|
4118
4152
|
*/
|
|
4119
4153
|
settle(options = {}) {
|
|
4154
|
+
if (!this._drain) return;
|
|
4120
4155
|
this._settle.schedule(options);
|
|
4121
4156
|
}
|
|
4122
4157
|
};
|