@rotorsoft/act 0.42.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/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  sleep,
19
19
  store,
20
20
  validate
21
- } from "./chunk-M5YFKVRV.js";
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-AGWZY6YT.js";
38
+ } from "./chunk-VMX7RPTC.js";
38
39
  import "./chunk-5WRI5ZAA.js";
39
40
 
40
41
  // src/signals.ts
@@ -782,9 +783,12 @@ function computeBackoffDelay(retry, opts) {
782
783
  function finalize(lease, handled, at, error, options, logger) {
783
784
  if (!error) return { lease, handled, at };
784
785
  logger.error(error);
785
- const block2 = lease.retry >= options.maxRetries && options.blockOnError;
786
+ const nonRetryable = error instanceof NonRetryableError;
787
+ const block2 = options.blockOnError && (nonRetryable || lease.retry >= options.maxRetries);
786
788
  if (block2)
787
- logger.error(`Blocking ${lease.stream} after ${lease.retry} retries.`);
789
+ logger.error(
790
+ nonRetryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
791
+ );
788
792
  const nextAttemptAt = !block2 && options.backoff ? Date.now() + computeBackoffDelay(lease.retry, options.backoff) : void 0;
789
793
  return {
790
794
  lease,
@@ -1924,13 +1928,81 @@ var Act = class {
1924
1928
  * @see {@link Store.reset} for the underlying store primitive
1925
1929
  * @see {@link settle} for the debounced full-catch-up loop
1926
1930
  */
1927
- async reset(streams) {
1931
+ async reset(input) {
1932
+ return this._scoped(async () => {
1933
+ const count = await store().reset(input);
1934
+ if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
1935
+ return count;
1936
+ });
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) {
1928
1966
  return this._scoped(async () => {
1929
- const count = await store().reset(streams);
1967
+ const count = await store().unblock(input);
1930
1968
  if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
1931
1969
  return count;
1932
1970
  });
1933
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
+ }
1934
2006
  /**
1935
2007
  * Bulk-update scheduling priority for streams matching `filter`.
1936
2008
  *
@@ -2386,6 +2458,7 @@ export {
2386
2458
  InMemoryStore,
2387
2459
  InvariantError,
2388
2460
  LogLevels,
2461
+ NonRetryableError,
2389
2462
  PackageSchema,
2390
2463
  QuerySchema,
2391
2464
  SNAP_EVENT,