@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.cjs
CHANGED
|
@@ -46,6 +46,7 @@ __export(index_exports, {
|
|
|
46
46
|
InMemoryStore: () => InMemoryStore,
|
|
47
47
|
InvariantError: () => InvariantError,
|
|
48
48
|
LogLevels: () => LogLevels,
|
|
49
|
+
NonRetryableError: () => NonRetryableError,
|
|
49
50
|
PackageSchema: () => PackageSchema,
|
|
50
51
|
QuerySchema: () => QuerySchema,
|
|
51
52
|
SNAP_EVENT: () => SNAP_EVENT,
|
|
@@ -286,7 +287,8 @@ var Errors = {
|
|
|
286
287
|
ValidationError: "ERR_VALIDATION",
|
|
287
288
|
InvariantError: "ERR_INVARIANT",
|
|
288
289
|
ConcurrencyError: "ERR_CONCURRENCY",
|
|
289
|
-
StreamClosedError: "ERR_STREAM_CLOSED"
|
|
290
|
+
StreamClosedError: "ERR_STREAM_CLOSED",
|
|
291
|
+
NonRetryableError: "ERR_NON_RETRYABLE"
|
|
290
292
|
};
|
|
291
293
|
var ValidationError = class extends Error {
|
|
292
294
|
constructor(target, payload, details) {
|
|
@@ -329,6 +331,15 @@ var StreamClosedError = class extends Error {
|
|
|
329
331
|
this.name = Errors.StreamClosedError;
|
|
330
332
|
}
|
|
331
333
|
};
|
|
334
|
+
var NonRetryableError = class extends Error {
|
|
335
|
+
/** The original failure, if any. Mirrors the standard `Error.cause` shape. */
|
|
336
|
+
cause;
|
|
337
|
+
constructor(message, options) {
|
|
338
|
+
super(message);
|
|
339
|
+
this.name = Errors.NonRetryableError;
|
|
340
|
+
this.cause = options?.cause;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
332
343
|
|
|
333
344
|
// src/utils.ts
|
|
334
345
|
var import_zod3 = require("zod");
|
|
@@ -597,6 +608,20 @@ var InMemoryStream = class {
|
|
|
597
608
|
this._leased_by = void 0;
|
|
598
609
|
this._leased_until = void 0;
|
|
599
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Clear the blocked flag and lease bookkeeping without touching the
|
|
613
|
+
* watermark. Returns true if the stream was actually blocked (and is
|
|
614
|
+
* now flipped); false otherwise.
|
|
615
|
+
*/
|
|
616
|
+
unblock() {
|
|
617
|
+
if (!this._blocked) return false;
|
|
618
|
+
this._blocked = false;
|
|
619
|
+
this._retry = -1;
|
|
620
|
+
this._error = "";
|
|
621
|
+
this._leased_by = void 0;
|
|
622
|
+
this._leased_until = void 0;
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
600
625
|
};
|
|
601
626
|
var InMemoryStore = class {
|
|
602
627
|
// stored events
|
|
@@ -832,19 +857,81 @@ var InMemoryStore = class {
|
|
|
832
857
|
return leases.map((l) => this._streams.get(l.stream)?.block(l, l.error)).filter((l) => !!l);
|
|
833
858
|
}
|
|
834
859
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
860
|
+
* Build a predicate from a {@link StreamFilter}. Compiled regexes are
|
|
861
|
+
* cached in the closure so callers can apply it across the streams
|
|
862
|
+
* map without re-compiling per iteration.
|
|
863
|
+
*/
|
|
864
|
+
_filterPredicate(filter) {
|
|
865
|
+
const streamRe = filter.stream && !filter.stream_exact ? new RegExp(filter.stream) : void 0;
|
|
866
|
+
const sourceRe = filter.source && !filter.source_exact ? new RegExp(filter.source) : void 0;
|
|
867
|
+
return (s) => {
|
|
868
|
+
if (filter.stream !== void 0) {
|
|
869
|
+
if (filter.stream_exact ? s.stream !== filter.stream : !streamRe.test(s.stream))
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
if (filter.source !== void 0) {
|
|
873
|
+
if (s.source === void 0) return false;
|
|
874
|
+
if (filter.source_exact ? s.source !== filter.source : !sourceRe.test(s.source))
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
if (filter.blocked !== void 0 && s.blocked !== filter.blocked)
|
|
878
|
+
return false;
|
|
879
|
+
return true;
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Reset watermarks to -1, clearing retry, blocked, error, and lease
|
|
884
|
+
* state so the matched streams can be replayed from the beginning.
|
|
885
|
+
* Accepts either an explicit list of names or a {@link StreamFilter}.
|
|
886
|
+
*
|
|
887
|
+
* @param input - Stream names or a filter selecting the streams to reset.
|
|
838
888
|
* @returns Count of streams that were actually reset.
|
|
839
889
|
*/
|
|
840
|
-
async reset(
|
|
890
|
+
async reset(input) {
|
|
841
891
|
await sleep();
|
|
842
892
|
let count = 0;
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
s
|
|
847
|
-
|
|
893
|
+
if (Array.isArray(input)) {
|
|
894
|
+
for (const name of input) {
|
|
895
|
+
const s = this._streams.get(name);
|
|
896
|
+
if (s) {
|
|
897
|
+
s.reset();
|
|
898
|
+
count++;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
const matches = this._filterPredicate(input);
|
|
903
|
+
for (const s of this._streams.values()) {
|
|
904
|
+
if (matches(s)) {
|
|
905
|
+
s.reset();
|
|
906
|
+
count++;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return count;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Clear the blocked flag (and retry / error / lease) on the matched
|
|
914
|
+
* streams without touching the watermark. Streams that aren't blocked
|
|
915
|
+
* at call time are silently skipped. Accepts either an explicit list
|
|
916
|
+
* of names or a {@link StreamFilter}. The filter form always restricts
|
|
917
|
+
* to blocked streams — passing `blocked: false` matches nothing.
|
|
918
|
+
* See {@link Store.unblock}.
|
|
919
|
+
*
|
|
920
|
+
* @param input - Stream names or a filter selecting the streams to unblock.
|
|
921
|
+
* @returns Count of streams that were actually flipped (were blocked).
|
|
922
|
+
*/
|
|
923
|
+
async unblock(input) {
|
|
924
|
+
await sleep();
|
|
925
|
+
let count = 0;
|
|
926
|
+
if (Array.isArray(input)) {
|
|
927
|
+
for (const name of input) {
|
|
928
|
+
const s = this._streams.get(name);
|
|
929
|
+
if (s?.unblock()) count++;
|
|
930
|
+
}
|
|
931
|
+
} else {
|
|
932
|
+
const matches = this._filterPredicate({ ...input, blocked: true });
|
|
933
|
+
for (const s of this._streams.values()) {
|
|
934
|
+
if (matches(s) && s.unblock()) count++;
|
|
848
935
|
}
|
|
849
936
|
}
|
|
850
937
|
return count;
|
|
@@ -860,21 +947,10 @@ var InMemoryStore = class {
|
|
|
860
947
|
*/
|
|
861
948
|
async prioritize(filter, priority) {
|
|
862
949
|
await sleep();
|
|
863
|
-
const
|
|
864
|
-
const sourceRe = filter.source && !filter.source_exact ? new RegExp(filter.source) : void 0;
|
|
950
|
+
const matches = this._filterPredicate(filter);
|
|
865
951
|
let count = 0;
|
|
866
952
|
for (const s of this._streams.values()) {
|
|
867
|
-
if (
|
|
868
|
-
if (filter.stream_exact ? s.stream !== filter.stream : !streamRe.test(s.stream))
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
if (filter.source !== void 0) {
|
|
872
|
-
if (s.source === void 0) continue;
|
|
873
|
-
if (filter.source_exact ? s.source !== filter.source : !sourceRe.test(s.source))
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
if (filter.blocked !== void 0 && s.blocked !== filter.blocked)
|
|
877
|
-
continue;
|
|
953
|
+
if (!matches(s)) continue;
|
|
878
954
|
if (s.priority !== priority) {
|
|
879
955
|
s.setPriority(priority);
|
|
880
956
|
count++;
|
|
@@ -1090,7 +1166,6 @@ function classifyRegistry(registry, states) {
|
|
|
1090
1166
|
}
|
|
1091
1167
|
|
|
1092
1168
|
// src/internal/close-cycle.ts
|
|
1093
|
-
var import_node_crypto = require("crypto");
|
|
1094
1169
|
async function runCloseCycle(targets, deps) {
|
|
1095
1170
|
const targetMap = new Map(targets.map((t) => [t.stream, t]));
|
|
1096
1171
|
const streams = [...targetMap.keys()];
|
|
@@ -1102,11 +1177,10 @@ async function runCloseCycle(targets, deps) {
|
|
|
1102
1177
|
skipped
|
|
1103
1178
|
);
|
|
1104
1179
|
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
1105
|
-
const correlation = (0, import_node_crypto.randomUUID)();
|
|
1106
1180
|
const { guarded, guardEvents } = await guardWithTombstones(
|
|
1107
1181
|
safe,
|
|
1108
1182
|
streamInfo,
|
|
1109
|
-
correlation,
|
|
1183
|
+
deps.correlation,
|
|
1110
1184
|
deps.tombstone,
|
|
1111
1185
|
skipped
|
|
1112
1186
|
);
|
|
@@ -1124,7 +1198,7 @@ async function runCloseCycle(targets, deps) {
|
|
|
1124
1198
|
guarded,
|
|
1125
1199
|
seedStates,
|
|
1126
1200
|
guardEvents,
|
|
1127
|
-
correlation
|
|
1201
|
+
deps.correlation
|
|
1128
1202
|
);
|
|
1129
1203
|
return { truncated, skipped };
|
|
1130
1204
|
}
|
|
@@ -1365,6 +1439,30 @@ var CorrelateCycle = class {
|
|
|
1365
1439
|
}
|
|
1366
1440
|
};
|
|
1367
1441
|
|
|
1442
|
+
// src/internal/correlator.ts
|
|
1443
|
+
var import_node_crypto = require("crypto");
|
|
1444
|
+
var BASE = 36;
|
|
1445
|
+
var SEG_WIDTH = 4;
|
|
1446
|
+
var SEG_SPACE = BASE ** SEG_WIDTH;
|
|
1447
|
+
function seg(n) {
|
|
1448
|
+
return n.toString(BASE).padStart(SEG_WIDTH, "0");
|
|
1449
|
+
}
|
|
1450
|
+
var defaultCorrelator = ({ state: state2, action: action2 }) => {
|
|
1451
|
+
const s = state2.slice(0, SEG_WIDTH).toLowerCase();
|
|
1452
|
+
const a = action2.slice(0, SEG_WIDTH).toLowerCase();
|
|
1453
|
+
const ts = seg(Date.now() % SEG_SPACE);
|
|
1454
|
+
const rnd = seg((0, import_node_crypto.randomInt)(SEG_SPACE));
|
|
1455
|
+
return `${s}-${a}-${ts}${rnd}`;
|
|
1456
|
+
};
|
|
1457
|
+
function closeCorrelation(correlator, actor) {
|
|
1458
|
+
return correlator({
|
|
1459
|
+
state: "$close",
|
|
1460
|
+
action: "close",
|
|
1461
|
+
stream: "$close",
|
|
1462
|
+
actor
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1368
1466
|
// src/internal/drain-cycle.ts
|
|
1369
1467
|
var import_node_crypto2 = require("crypto");
|
|
1370
1468
|
|
|
@@ -1755,9 +1853,12 @@ function computeBackoffDelay(retry, opts) {
|
|
|
1755
1853
|
function finalize(lease, handled, at, error, options, logger) {
|
|
1756
1854
|
if (!error) return { lease, handled, at };
|
|
1757
1855
|
logger.error(error);
|
|
1758
|
-
const
|
|
1856
|
+
const nonRetryable = error instanceof NonRetryableError;
|
|
1857
|
+
const block2 = options.blockOnError && (nonRetryable || lease.retry >= options.maxRetries);
|
|
1759
1858
|
if (block2)
|
|
1760
|
-
logger.error(
|
|
1859
|
+
logger.error(
|
|
1860
|
+
nonRetryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
1861
|
+
);
|
|
1761
1862
|
const nextAttemptAt = !block2 && options.backoff ? Date.now() + computeBackoffDelay(lease.retry, options.backoff) : void 0;
|
|
1762
1863
|
return {
|
|
1763
1864
|
lease,
|
|
@@ -1907,7 +2008,6 @@ var block = (leases) => store2().block(leases);
|
|
|
1907
2008
|
var subscribe = (streams) => store2().subscribe(streams);
|
|
1908
2009
|
|
|
1909
2010
|
// src/internal/event-sourcing.ts
|
|
1910
|
-
var import_node_crypto3 = require("crypto");
|
|
1911
2011
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
1912
2012
|
async function snap(snapshot) {
|
|
1913
2013
|
try {
|
|
@@ -1995,7 +2095,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
1995
2095
|
}
|
|
1996
2096
|
return { event, state: state2, version, patches, snaps, cache_hit, replayed };
|
|
1997
2097
|
}
|
|
1998
|
-
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
2098
|
+
async function action(me, action2, target, payload, reactingTo, skipValidation = false, correlator = defaultCorrelator) {
|
|
1999
2099
|
const { stream, expectedVersion, actor } = target;
|
|
2000
2100
|
if (!stream) throw new Error("Missing target stream");
|
|
2001
2101
|
const validated = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
|
|
@@ -2041,7 +2141,12 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2041
2141
|
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
2042
2142
|
}));
|
|
2043
2143
|
const meta = {
|
|
2044
|
-
correlation: reactingTo?.meta.correlation || (
|
|
2144
|
+
correlation: reactingTo?.meta.correlation || correlator({
|
|
2145
|
+
action: action2,
|
|
2146
|
+
state: me.name,
|
|
2147
|
+
stream,
|
|
2148
|
+
actor: target.actor
|
|
2149
|
+
}),
|
|
2045
2150
|
causation: {
|
|
2046
2151
|
action: {
|
|
2047
2152
|
name: action2,
|
|
@@ -2145,12 +2250,21 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
2145
2250
|
exit?.(result, ...args);
|
|
2146
2251
|
return result;
|
|
2147
2252
|
});
|
|
2148
|
-
function buildEs(logger) {
|
|
2253
|
+
function buildEs(logger, correlator = defaultCorrelator) {
|
|
2254
|
+
const boundAction = (me, actionName, target, payload, reactingTo, skipValidation = false) => action(
|
|
2255
|
+
me,
|
|
2256
|
+
actionName,
|
|
2257
|
+
target,
|
|
2258
|
+
payload,
|
|
2259
|
+
reactingTo,
|
|
2260
|
+
skipValidation,
|
|
2261
|
+
correlator
|
|
2262
|
+
);
|
|
2149
2263
|
if (logger.level !== "trace") {
|
|
2150
2264
|
return {
|
|
2151
2265
|
snap,
|
|
2152
2266
|
load,
|
|
2153
|
-
action,
|
|
2267
|
+
action: boundAction,
|
|
2154
2268
|
tombstone
|
|
2155
2269
|
};
|
|
2156
2270
|
}
|
|
@@ -2180,7 +2294,7 @@ function buildEs(logger) {
|
|
|
2180
2294
|
);
|
|
2181
2295
|
}),
|
|
2182
2296
|
action: traced(
|
|
2183
|
-
|
|
2297
|
+
boundAction,
|
|
2184
2298
|
(snapshots, _me, _action, target) => {
|
|
2185
2299
|
const committed = snapshots.filter((s) => s.event);
|
|
2186
2300
|
if (committed.length) {
|
|
@@ -2288,7 +2402,8 @@ var Act = class {
|
|
|
2288
2402
|
this._states = _states;
|
|
2289
2403
|
this._batch_handlers = batchHandlers;
|
|
2290
2404
|
this._scoped = options.scoped ? (fn) => scoped.run(options.scoped, fn) : (fn) => fn();
|
|
2291
|
-
this.
|
|
2405
|
+
this._correlator = options.correlator ?? defaultCorrelator;
|
|
2406
|
+
this._es = buildEs(this._logger, this._correlator);
|
|
2292
2407
|
this._cd = buildDrain(this._logger);
|
|
2293
2408
|
this._handle = buildHandle({
|
|
2294
2409
|
logger: this._logger,
|
|
@@ -2401,6 +2516,13 @@ var Act = class {
|
|
|
2401
2516
|
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
2402
2517
|
* tests that dispose and re-seed mid-suite. */
|
|
2403
2518
|
_scoped;
|
|
2519
|
+
/**
|
|
2520
|
+
* Correlation-id generator for originating actions. Bound at
|
|
2521
|
+
* construction from `options.correlator ?? defaultCorrelator`. The
|
|
2522
|
+
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
2523
|
+
* uses it via {@link closeCorrelation}.
|
|
2524
|
+
*/
|
|
2525
|
+
_correlator;
|
|
2404
2526
|
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
2405
2527
|
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
2406
2528
|
_bound_do = this.do.bind(this);
|
|
@@ -2876,13 +2998,81 @@ var Act = class {
|
|
|
2876
2998
|
* @see {@link Store.reset} for the underlying store primitive
|
|
2877
2999
|
* @see {@link settle} for the debounced full-catch-up loop
|
|
2878
3000
|
*/
|
|
2879
|
-
async reset(
|
|
3001
|
+
async reset(input) {
|
|
2880
3002
|
return this._scoped(async () => {
|
|
2881
|
-
const count = await store2().reset(
|
|
3003
|
+
const count = await store2().reset(input);
|
|
2882
3004
|
if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
|
|
2883
3005
|
return count;
|
|
2884
3006
|
});
|
|
2885
3007
|
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Clear the blocked flag on streams without replaying their history.
|
|
3010
|
+
*
|
|
3011
|
+
* Use this to recover from a poison message after fixing the
|
|
3012
|
+
* underlying issue — the stream resumes from the next event after the
|
|
3013
|
+
* last successful ack, not from the beginning. Compare with
|
|
3014
|
+
* {@link reset}, which rebuilds from event 0 (suitable for projection
|
|
3015
|
+
* rebuilds, wrong for "I fixed the bug, please retry").
|
|
3016
|
+
*
|
|
3017
|
+
* Wraps `store().unblock(streams)` and raises the orchestrator's
|
|
3018
|
+
* internal "needs drain" flag so a settled app picks up the now-free
|
|
3019
|
+
* streams on the next cycle. Equivalent to calling `store().unblock(...)`
|
|
3020
|
+
* directly, but `store().unblock(...)` alone leaves the flag
|
|
3021
|
+
* untouched.
|
|
3022
|
+
*
|
|
3023
|
+
* @param streams - Stream names to unblock
|
|
3024
|
+
* @returns Count of streams that were actually flipped (were blocked)
|
|
3025
|
+
*
|
|
3026
|
+
* @example Recover from a 4xx webhook after fixing the bug
|
|
3027
|
+
* ```typescript
|
|
3028
|
+
* await app.unblock(["webhooks-out-customer-42"]);
|
|
3029
|
+
* // The stream resumes from the next event, not from zero.
|
|
3030
|
+
* ```
|
|
3031
|
+
*
|
|
3032
|
+
* @see {@link Store.unblock} for the underlying store primitive
|
|
3033
|
+
* @see {@link reset} for the rebuild-from-zero alternative
|
|
3034
|
+
*/
|
|
3035
|
+
async unblock(input) {
|
|
3036
|
+
return this._scoped(async () => {
|
|
3037
|
+
const count = await store2().unblock(input);
|
|
3038
|
+
if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
|
|
3039
|
+
return count;
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
/**
|
|
3043
|
+
* Return every currently-blocked stream position. Convenience wrapper
|
|
3044
|
+
* around `store().query_streams(cb, { blocked: true })` for the common
|
|
3045
|
+
* "show me what's broken" operational query.
|
|
3046
|
+
*
|
|
3047
|
+
* Results are ordered by stream name, paginated by `limit` (default
|
|
3048
|
+
* 100). Pass `after` to fetch the next page (keyset cursor on the
|
|
3049
|
+
* stream name). For richer queries — including blocked + source
|
|
3050
|
+
* filters, or full unblocked introspection — drop to
|
|
3051
|
+
* `store().query_streams(...)` directly.
|
|
3052
|
+
*
|
|
3053
|
+
* @returns Array of {@link StreamPosition} for currently-blocked streams.
|
|
3054
|
+
*
|
|
3055
|
+
* @example Discover and recover
|
|
3056
|
+
* ```typescript
|
|
3057
|
+
* const blocked = await app.blocked_streams();
|
|
3058
|
+
* console.table(blocked.map(({ stream, retry, error }) => ({ stream, retry, error })));
|
|
3059
|
+
*
|
|
3060
|
+
* // Operator investigates, then bulk-unblocks the family:
|
|
3061
|
+
* await app.unblock({ stream: "^webhooks-out-" });
|
|
3062
|
+
* ```
|
|
3063
|
+
*/
|
|
3064
|
+
async blocked_streams(options) {
|
|
3065
|
+
return this._scoped(async () => {
|
|
3066
|
+
const positions = [];
|
|
3067
|
+
await store2().query_streams(
|
|
3068
|
+
(p) => {
|
|
3069
|
+
positions.push(p);
|
|
3070
|
+
},
|
|
3071
|
+
{ blocked: true, after: options?.after, limit: options?.limit }
|
|
3072
|
+
);
|
|
3073
|
+
return positions;
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
2886
3076
|
/**
|
|
2887
3077
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
2888
3078
|
*
|
|
@@ -2961,12 +3151,14 @@ var Act = class {
|
|
|
2961
3151
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
2962
3152
|
return this._scoped(async () => {
|
|
2963
3153
|
await this.correlate({ limit: 1e3 });
|
|
3154
|
+
const closeActor = { id: "$close", name: "close" };
|
|
2964
3155
|
const result = await runCloseCycle(targets, {
|
|
2965
3156
|
reactiveEventsSize: this._reactive_events.size,
|
|
2966
3157
|
eventToState: this._event_to_state,
|
|
2967
3158
|
load: this._es.load,
|
|
2968
3159
|
tombstone: this._es.tombstone,
|
|
2969
|
-
logger: this._logger
|
|
3160
|
+
logger: this._logger,
|
|
3161
|
+
correlation: closeCorrelation(this._correlator, closeActor)
|
|
2970
3162
|
});
|
|
2971
3163
|
this.emit("closed", result);
|
|
2972
3164
|
return result;
|
|
@@ -3337,6 +3529,7 @@ function action_builder(state2) {
|
|
|
3337
3529
|
InMemoryStore,
|
|
3338
3530
|
InvariantError,
|
|
3339
3531
|
LogLevels,
|
|
3532
|
+
NonRetryableError,
|
|
3340
3533
|
PackageSchema,
|
|
3341
3534
|
QuerySchema,
|
|
3342
3535
|
SNAP_EVENT,
|