@rotorsoft/act 0.41.0 → 0.43.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 +75 -3
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +26 -6
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +7 -0
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlator.d.ts +44 -0
- package/dist/@types/internal/correlator.d.ts.map +1 -0
- package/dist/@types/internal/event-sourcing.d.ts +10 -3
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +2 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/reactions.d.ts +1 -1
- package/dist/@types/internal/reactions.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +2 -2
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +41 -0
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +56 -0
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +80 -8
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-M5YFKVRV.js → chunk-QAB4SDOS.js} +89 -24
- package/dist/chunk-QAB4SDOS.js.map +1 -0
- package/dist/{chunk-AGWZY6YT.js → chunk-VMX7RPTC.js} +13 -2
- package/dist/chunk-VMX7RPTC.js.map +1 -0
- package/dist/index.cjs +232 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +138 -20
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +91 -25
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +4 -4
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs +13 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-AGWZY6YT.js.map +0 -1
- package/dist/chunk-M5YFKVRV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
sleep,
|
|
19
19
|
store,
|
|
20
20
|
validate
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-QAB4SDOS.js";
|
|
22
22
|
import {
|
|
23
23
|
ActorSchema,
|
|
24
24
|
CausationEventSchema,
|
|
@@ -29,12 +29,13 @@ import {
|
|
|
29
29
|
EventMetaSchema,
|
|
30
30
|
InvariantError,
|
|
31
31
|
LogLevels,
|
|
32
|
+
NonRetryableError,
|
|
32
33
|
QuerySchema,
|
|
33
34
|
StreamClosedError,
|
|
34
35
|
TargetSchema,
|
|
35
36
|
ValidationError,
|
|
36
37
|
ZodEmpty
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-VMX7RPTC.js";
|
|
38
39
|
import "./chunk-5WRI5ZAA.js";
|
|
39
40
|
|
|
40
41
|
// src/signals.ts
|
|
@@ -95,7 +96,6 @@ function classifyRegistry(registry, states) {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// src/internal/close-cycle.ts
|
|
98
|
-
import { randomUUID } from "crypto";
|
|
99
99
|
async function runCloseCycle(targets, deps) {
|
|
100
100
|
const targetMap = new Map(targets.map((t) => [t.stream, t]));
|
|
101
101
|
const streams = [...targetMap.keys()];
|
|
@@ -107,11 +107,10 @@ async function runCloseCycle(targets, deps) {
|
|
|
107
107
|
skipped
|
|
108
108
|
);
|
|
109
109
|
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
110
|
-
const correlation = randomUUID();
|
|
111
110
|
const { guarded, guardEvents } = await guardWithTombstones(
|
|
112
111
|
safe,
|
|
113
112
|
streamInfo,
|
|
114
|
-
correlation,
|
|
113
|
+
deps.correlation,
|
|
115
114
|
deps.tombstone,
|
|
116
115
|
skipped
|
|
117
116
|
);
|
|
@@ -129,7 +128,7 @@ async function runCloseCycle(targets, deps) {
|
|
|
129
128
|
guarded,
|
|
130
129
|
seedStates,
|
|
131
130
|
guardEvents,
|
|
132
|
-
correlation
|
|
131
|
+
deps.correlation
|
|
133
132
|
);
|
|
134
133
|
return { truncated, skipped };
|
|
135
134
|
}
|
|
@@ -370,8 +369,32 @@ var CorrelateCycle = class {
|
|
|
370
369
|
}
|
|
371
370
|
};
|
|
372
371
|
|
|
372
|
+
// src/internal/correlator.ts
|
|
373
|
+
import { randomInt } from "crypto";
|
|
374
|
+
var BASE = 36;
|
|
375
|
+
var SEG_WIDTH = 4;
|
|
376
|
+
var SEG_SPACE = BASE ** SEG_WIDTH;
|
|
377
|
+
function seg(n) {
|
|
378
|
+
return n.toString(BASE).padStart(SEG_WIDTH, "0");
|
|
379
|
+
}
|
|
380
|
+
var defaultCorrelator = ({ state: state2, action: action2 }) => {
|
|
381
|
+
const s = state2.slice(0, SEG_WIDTH).toLowerCase();
|
|
382
|
+
const a = action2.slice(0, SEG_WIDTH).toLowerCase();
|
|
383
|
+
const ts = seg(Date.now() % SEG_SPACE);
|
|
384
|
+
const rnd = seg(randomInt(SEG_SPACE));
|
|
385
|
+
return `${s}-${a}-${ts}${rnd}`;
|
|
386
|
+
};
|
|
387
|
+
function closeCorrelation(correlator, actor) {
|
|
388
|
+
return correlator({
|
|
389
|
+
state: "$close",
|
|
390
|
+
action: "close",
|
|
391
|
+
stream: "$close",
|
|
392
|
+
actor
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
373
396
|
// src/internal/drain-cycle.ts
|
|
374
|
-
import { randomUUID
|
|
397
|
+
import { randomUUID } from "crypto";
|
|
375
398
|
|
|
376
399
|
// src/internal/drain-ratio.ts
|
|
377
400
|
var RATIO_MIN = 0.2;
|
|
@@ -393,7 +416,7 @@ function computeLagLeadRatio(handled, lagging, leading) {
|
|
|
393
416
|
|
|
394
417
|
// src/internal/drain-cycle.ts
|
|
395
418
|
async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch, lagging, leading, eventLimit, leaseMillis, isDeferred) {
|
|
396
|
-
const leased = await ops.claim(lagging, leading,
|
|
419
|
+
const leased = await ops.claim(lagging, leading, randomUUID(), leaseMillis);
|
|
397
420
|
if (!leased.length) return void 0;
|
|
398
421
|
const active = isDeferred ? leased.filter((l) => !isDeferred(l.stream)) : leased;
|
|
399
422
|
if (!active.length) {
|
|
@@ -760,9 +783,12 @@ function computeBackoffDelay(retry, opts) {
|
|
|
760
783
|
function finalize(lease, handled, at, error, options, logger) {
|
|
761
784
|
if (!error) return { lease, handled, at };
|
|
762
785
|
logger.error(error);
|
|
763
|
-
const
|
|
786
|
+
const nonRetryable = error instanceof NonRetryableError;
|
|
787
|
+
const block2 = options.blockOnError && (nonRetryable || lease.retry >= options.maxRetries);
|
|
764
788
|
if (block2)
|
|
765
|
-
logger.error(
|
|
789
|
+
logger.error(
|
|
790
|
+
nonRetryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
791
|
+
);
|
|
766
792
|
const nextAttemptAt = !block2 && options.backoff ? Date.now() + computeBackoffDelay(lease.retry, options.backoff) : void 0;
|
|
767
793
|
return {
|
|
768
794
|
lease,
|
|
@@ -912,7 +938,6 @@ var block = (leases) => store().block(leases);
|
|
|
912
938
|
var subscribe = (streams) => store().subscribe(streams);
|
|
913
939
|
|
|
914
940
|
// src/internal/event-sourcing.ts
|
|
915
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
916
941
|
import { patch } from "@rotorsoft/act-patch";
|
|
917
942
|
async function snap(snapshot) {
|
|
918
943
|
try {
|
|
@@ -1000,7 +1025,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
1000
1025
|
}
|
|
1001
1026
|
return { event, state: state2, version, patches, snaps, cache_hit, replayed };
|
|
1002
1027
|
}
|
|
1003
|
-
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
1028
|
+
async function action(me, action2, target, payload, reactingTo, skipValidation = false, correlator = defaultCorrelator) {
|
|
1004
1029
|
const { stream, expectedVersion, actor } = target;
|
|
1005
1030
|
if (!stream) throw new Error("Missing target stream");
|
|
1006
1031
|
const validated = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
|
|
@@ -1046,7 +1071,12 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1046
1071
|
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
1047
1072
|
}));
|
|
1048
1073
|
const meta = {
|
|
1049
|
-
correlation: reactingTo?.meta.correlation ||
|
|
1074
|
+
correlation: reactingTo?.meta.correlation || correlator({
|
|
1075
|
+
action: action2,
|
|
1076
|
+
state: me.name,
|
|
1077
|
+
stream,
|
|
1078
|
+
actor: target.actor
|
|
1079
|
+
}),
|
|
1050
1080
|
causation: {
|
|
1051
1081
|
action: {
|
|
1052
1082
|
name: action2,
|
|
@@ -1150,12 +1180,21 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
1150
1180
|
exit?.(result, ...args);
|
|
1151
1181
|
return result;
|
|
1152
1182
|
});
|
|
1153
|
-
function buildEs(logger) {
|
|
1183
|
+
function buildEs(logger, correlator = defaultCorrelator) {
|
|
1184
|
+
const boundAction = (me, actionName, target, payload, reactingTo, skipValidation = false) => action(
|
|
1185
|
+
me,
|
|
1186
|
+
actionName,
|
|
1187
|
+
target,
|
|
1188
|
+
payload,
|
|
1189
|
+
reactingTo,
|
|
1190
|
+
skipValidation,
|
|
1191
|
+
correlator
|
|
1192
|
+
);
|
|
1154
1193
|
if (logger.level !== "trace") {
|
|
1155
1194
|
return {
|
|
1156
1195
|
snap,
|
|
1157
1196
|
load,
|
|
1158
|
-
action,
|
|
1197
|
+
action: boundAction,
|
|
1159
1198
|
tombstone
|
|
1160
1199
|
};
|
|
1161
1200
|
}
|
|
@@ -1185,7 +1224,7 @@ function buildEs(logger) {
|
|
|
1185
1224
|
);
|
|
1186
1225
|
}),
|
|
1187
1226
|
action: traced(
|
|
1188
|
-
|
|
1227
|
+
boundAction,
|
|
1189
1228
|
(snapshots, _me, _action, target) => {
|
|
1190
1229
|
const committed = snapshots.filter((s) => s.event);
|
|
1191
1230
|
if (committed.length) {
|
|
@@ -1293,7 +1332,8 @@ var Act = class {
|
|
|
1293
1332
|
this._states = _states;
|
|
1294
1333
|
this._batch_handlers = batchHandlers;
|
|
1295
1334
|
this._scoped = options.scoped ? (fn) => scoped.run(options.scoped, fn) : (fn) => fn();
|
|
1296
|
-
this.
|
|
1335
|
+
this._correlator = options.correlator ?? defaultCorrelator;
|
|
1336
|
+
this._es = buildEs(this._logger, this._correlator);
|
|
1297
1337
|
this._cd = buildDrain(this._logger);
|
|
1298
1338
|
this._handle = buildHandle({
|
|
1299
1339
|
logger: this._logger,
|
|
@@ -1406,6 +1446,13 @@ var Act = class {
|
|
|
1406
1446
|
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
1407
1447
|
* tests that dispose and re-seed mid-suite. */
|
|
1408
1448
|
_scoped;
|
|
1449
|
+
/**
|
|
1450
|
+
* Correlation-id generator for originating actions. Bound at
|
|
1451
|
+
* construction from `options.correlator ?? defaultCorrelator`. The
|
|
1452
|
+
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
1453
|
+
* uses it via {@link closeCorrelation}.
|
|
1454
|
+
*/
|
|
1455
|
+
_correlator;
|
|
1409
1456
|
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
1410
1457
|
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
1411
1458
|
_bound_do = this.do.bind(this);
|
|
@@ -1881,13 +1928,81 @@ var Act = class {
|
|
|
1881
1928
|
* @see {@link Store.reset} for the underlying store primitive
|
|
1882
1929
|
* @see {@link settle} for the debounced full-catch-up loop
|
|
1883
1930
|
*/
|
|
1884
|
-
async reset(
|
|
1931
|
+
async reset(input) {
|
|
1885
1932
|
return this._scoped(async () => {
|
|
1886
|
-
const count = await store().reset(
|
|
1933
|
+
const count = await store().reset(input);
|
|
1887
1934
|
if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
|
|
1888
1935
|
return count;
|
|
1889
1936
|
});
|
|
1890
1937
|
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Clear the blocked flag on streams without replaying their history.
|
|
1940
|
+
*
|
|
1941
|
+
* Use this to recover from a poison message after fixing the
|
|
1942
|
+
* underlying issue — the stream resumes from the next event after the
|
|
1943
|
+
* last successful ack, not from the beginning. Compare with
|
|
1944
|
+
* {@link reset}, which rebuilds from event 0 (suitable for projection
|
|
1945
|
+
* rebuilds, wrong for "I fixed the bug, please retry").
|
|
1946
|
+
*
|
|
1947
|
+
* Wraps `store().unblock(streams)` and raises the orchestrator's
|
|
1948
|
+
* internal "needs drain" flag so a settled app picks up the now-free
|
|
1949
|
+
* streams on the next cycle. Equivalent to calling `store().unblock(...)`
|
|
1950
|
+
* directly, but `store().unblock(...)` alone leaves the flag
|
|
1951
|
+
* untouched.
|
|
1952
|
+
*
|
|
1953
|
+
* @param streams - Stream names to unblock
|
|
1954
|
+
* @returns Count of streams that were actually flipped (were blocked)
|
|
1955
|
+
*
|
|
1956
|
+
* @example Recover from a 4xx webhook after fixing the bug
|
|
1957
|
+
* ```typescript
|
|
1958
|
+
* await app.unblock(["webhooks-out-customer-42"]);
|
|
1959
|
+
* // The stream resumes from the next event, not from zero.
|
|
1960
|
+
* ```
|
|
1961
|
+
*
|
|
1962
|
+
* @see {@link Store.unblock} for the underlying store primitive
|
|
1963
|
+
* @see {@link reset} for the rebuild-from-zero alternative
|
|
1964
|
+
*/
|
|
1965
|
+
async unblock(input) {
|
|
1966
|
+
return this._scoped(async () => {
|
|
1967
|
+
const count = await store().unblock(input);
|
|
1968
|
+
if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
|
|
1969
|
+
return count;
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Return every currently-blocked stream position. Convenience wrapper
|
|
1974
|
+
* around `store().query_streams(cb, { blocked: true })` for the common
|
|
1975
|
+
* "show me what's broken" operational query.
|
|
1976
|
+
*
|
|
1977
|
+
* Results are ordered by stream name, paginated by `limit` (default
|
|
1978
|
+
* 100). Pass `after` to fetch the next page (keyset cursor on the
|
|
1979
|
+
* stream name). For richer queries — including blocked + source
|
|
1980
|
+
* filters, or full unblocked introspection — drop to
|
|
1981
|
+
* `store().query_streams(...)` directly.
|
|
1982
|
+
*
|
|
1983
|
+
* @returns Array of {@link StreamPosition} for currently-blocked streams.
|
|
1984
|
+
*
|
|
1985
|
+
* @example Discover and recover
|
|
1986
|
+
* ```typescript
|
|
1987
|
+
* const blocked = await app.blocked_streams();
|
|
1988
|
+
* console.table(blocked.map(({ stream, retry, error }) => ({ stream, retry, error })));
|
|
1989
|
+
*
|
|
1990
|
+
* // Operator investigates, then bulk-unblocks the family:
|
|
1991
|
+
* await app.unblock({ stream: "^webhooks-out-" });
|
|
1992
|
+
* ```
|
|
1993
|
+
*/
|
|
1994
|
+
async blocked_streams(options) {
|
|
1995
|
+
return this._scoped(async () => {
|
|
1996
|
+
const positions = [];
|
|
1997
|
+
await store().query_streams(
|
|
1998
|
+
(p) => {
|
|
1999
|
+
positions.push(p);
|
|
2000
|
+
},
|
|
2001
|
+
{ blocked: true, after: options?.after, limit: options?.limit }
|
|
2002
|
+
);
|
|
2003
|
+
return positions;
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
1891
2006
|
/**
|
|
1892
2007
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
1893
2008
|
*
|
|
@@ -1966,12 +2081,14 @@ var Act = class {
|
|
|
1966
2081
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
1967
2082
|
return this._scoped(async () => {
|
|
1968
2083
|
await this.correlate({ limit: 1e3 });
|
|
2084
|
+
const closeActor = { id: "$close", name: "close" };
|
|
1969
2085
|
const result = await runCloseCycle(targets, {
|
|
1970
2086
|
reactiveEventsSize: this._reactive_events.size,
|
|
1971
2087
|
eventToState: this._event_to_state,
|
|
1972
2088
|
load: this._es.load,
|
|
1973
2089
|
tombstone: this._es.tombstone,
|
|
1974
|
-
logger: this._logger
|
|
2090
|
+
logger: this._logger,
|
|
2091
|
+
correlation: closeCorrelation(this._correlator, closeActor)
|
|
1975
2092
|
});
|
|
1976
2093
|
this.emit("closed", result);
|
|
1977
2094
|
return result;
|
|
@@ -2341,6 +2458,7 @@ export {
|
|
|
2341
2458
|
InMemoryStore,
|
|
2342
2459
|
InvariantError,
|
|
2343
2460
|
LogLevels,
|
|
2461
|
+
NonRetryableError,
|
|
2344
2462
|
PackageSchema,
|
|
2345
2463
|
QuerySchema,
|
|
2346
2464
|
SNAP_EVENT,
|