@rotorsoft/act 1.32.27 → 1.32.29
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/README.md +10 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +2 -1
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/reaction-builder.d.ts +3 -3
- package/dist/@types/builders/reaction-builder.d.ts.map +1 -1
- package/dist/@types/internal/circuit-breaker.d.ts +8 -5
- package/dist/@types/internal/circuit-breaker.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +13 -4
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +2 -1
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +1 -1
- package/dist/index.cjs +65 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +65 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1132,6 +1132,10 @@ var CircuitBreaker = class {
|
|
|
1132
1132
|
get failing() {
|
|
1133
1133
|
return this._failures > 0;
|
|
1134
1134
|
}
|
|
1135
|
+
/** How long the store is left alone after a failure before a retry. */
|
|
1136
|
+
get cooldown_ms() {
|
|
1137
|
+
return this._cooldown_ms;
|
|
1138
|
+
}
|
|
1135
1139
|
/** Current state given the wall-clock `now`. */
|
|
1136
1140
|
state(now) {
|
|
1137
1141
|
if (this._opened_at === void 0) return "closed";
|
|
@@ -1149,12 +1153,13 @@ var CircuitBreaker = class {
|
|
|
1149
1153
|
* if a half-open trial just failed. Returns the resulting state.
|
|
1150
1154
|
*
|
|
1151
1155
|
* Callers gate on the state directly — `state(now) === "open"` means skip.
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1156
|
+
* Every failure (re)schedules the `on_retry` wake, so one failed pass on
|
|
1157
|
+
* an idle app retries without waiting for the breaker to open; always
|
|
1158
|
+
* surfaces the failure via `on_error`.
|
|
1154
1159
|
*/
|
|
1155
1160
|
failed(now, error) {
|
|
1156
1161
|
const circuit = this._advance(now);
|
|
1157
|
-
|
|
1162
|
+
this._schedule_wake();
|
|
1158
1163
|
this._hooks.on_error?.(error, circuit);
|
|
1159
1164
|
return circuit;
|
|
1160
1165
|
}
|
|
@@ -2682,20 +2687,24 @@ function fetch_failure_policy(registry) {
|
|
|
2682
2687
|
}
|
|
2683
2688
|
return { blockOnError, maxRetries };
|
|
2684
2689
|
}
|
|
2685
|
-
function
|
|
2686
|
-
|
|
2687
|
-
|
|
2690
|
+
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
2691
|
+
if (!error) return { lease, handled, acked_at: at };
|
|
2692
|
+
logger.error(error);
|
|
2693
|
+
const non_retryable = error instanceof NonRetryableError;
|
|
2694
|
+
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
2695
|
+
if (block2)
|
|
2696
|
+
logger.error(
|
|
2697
|
+
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
2698
|
+
);
|
|
2699
|
+
const next_attempt_at = block2 ? void 0 : Date.now() + compute_backoff_delay(lease.retry, options.backoff ?? DEFAULT_BACKOFF);
|
|
2688
2700
|
return {
|
|
2689
2701
|
lease,
|
|
2690
|
-
handled
|
|
2691
|
-
acked_at:
|
|
2692
|
-
error,
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
options.backoff ?? DEFAULT_BACKOFF
|
|
2697
|
-
)
|
|
2698
|
-
}
|
|
2702
|
+
handled,
|
|
2703
|
+
acked_at: at,
|
|
2704
|
+
error: error.message,
|
|
2705
|
+
block: block2,
|
|
2706
|
+
next_attempt_at,
|
|
2707
|
+
failed_at
|
|
2699
2708
|
};
|
|
2700
2709
|
}
|
|
2701
2710
|
function warn_misrouted(seen, stream, handler, event) {
|
|
@@ -2743,7 +2752,9 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2743
2752
|
resolved = dynamic ? resolver(event) : resolver;
|
|
2744
2753
|
} catch (error) {
|
|
2745
2754
|
failed ??= {
|
|
2746
|
-
error:
|
|
2755
|
+
error: new Error(
|
|
2756
|
+
`Resolver of reaction "${reaction.handler.name}" threw on ${String(event.name)} #${event.id} (stream "${event.stream}"): ${String(error)}`
|
|
2757
|
+
),
|
|
2747
2758
|
options: reaction.options
|
|
2748
2759
|
};
|
|
2749
2760
|
return false;
|
|
@@ -2765,23 +2776,29 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2765
2776
|
leased.map((lease) => {
|
|
2766
2777
|
const entry = fetch_map.get(lease.stream);
|
|
2767
2778
|
if (entry.fetch.error !== void 0) {
|
|
2768
|
-
const error = `Fetch failed for ${lease.stream}: ${entry.fetch.error}`;
|
|
2769
|
-
log().error(error);
|
|
2770
2779
|
const policy = fetch_failure_policy(registry);
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
return Promise.resolve(failed);
|
|
2780
|
+
return Promise.resolve(
|
|
2781
|
+
finalize(
|
|
2782
|
+
lease,
|
|
2783
|
+
0,
|
|
2784
|
+
lease.at,
|
|
2785
|
+
new Error(`Fetch failed for ${lease.stream}: ${entry.fetch.error}`),
|
|
2786
|
+
store_failing ? { ...policy, blockOnError: false } : policy,
|
|
2787
|
+
deps.logger
|
|
2788
|
+
)
|
|
2789
|
+
);
|
|
2782
2790
|
}
|
|
2783
2791
|
if (entry.failed)
|
|
2784
|
-
return Promise.resolve(
|
|
2792
|
+
return Promise.resolve(
|
|
2793
|
+
finalize(
|
|
2794
|
+
lease,
|
|
2795
|
+
0,
|
|
2796
|
+
lease.at,
|
|
2797
|
+
entry.failed.error,
|
|
2798
|
+
entry.failed.options,
|
|
2799
|
+
deps.logger
|
|
2800
|
+
)
|
|
2801
|
+
);
|
|
2785
2802
|
const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
|
|
2786
2803
|
const { payloads } = entry;
|
|
2787
2804
|
const exhausted = budget_exhausted(
|
|
@@ -2820,18 +2837,23 @@ var EMPTY_DRAIN = {
|
|
|
2820
2837
|
acked: [],
|
|
2821
2838
|
blocked: []
|
|
2822
2839
|
};
|
|
2840
|
+
var RETRY = "";
|
|
2823
2841
|
var DrainController = class {
|
|
2824
2842
|
_armed = false;
|
|
2825
2843
|
_locked = false;
|
|
2826
2844
|
_ratio = 0.5;
|
|
2827
2845
|
/**
|
|
2828
|
-
* Local wake at the earliest parked (backoff or defer) visit
|
|
2829
|
-
*
|
|
2830
|
-
*
|
|
2846
|
+
* Local wake at the earliest parked (backoff or defer) visit, or at the
|
|
2847
|
+
* lease expiry after a failed pass (the {@link RETRY} key); the schedule
|
|
2848
|
+
* itself is persisted. On an idle app nothing else would drain it. It
|
|
2849
|
+
* drains until a pass leases nothing or the controller stops, and an entry
|
|
2850
|
+
* stays until handled or until a later pass claims nothing.
|
|
2831
2851
|
*/
|
|
2832
2852
|
_defer = new DeferTimer(() => {
|
|
2833
2853
|
this._armed = true;
|
|
2834
|
-
void this._deps.run_scoped(() =>
|
|
2854
|
+
void this._deps.run_scoped(async () => {
|
|
2855
|
+
while (!this._stopped && (await this.drain()).leased.length > 0) ;
|
|
2856
|
+
});
|
|
2835
2857
|
});
|
|
2836
2858
|
_worker;
|
|
2837
2859
|
_misrouted = /* @__PURE__ */ new Set();
|
|
@@ -2928,10 +2950,12 @@ var DrainController = class {
|
|
|
2928
2950
|
if (acked.length) this._deps.on_acked(acked);
|
|
2929
2951
|
if (closeable.length) await this._deps.on_close(closeable);
|
|
2930
2952
|
this._deps.breaker.passed();
|
|
2931
|
-
const has_errors = handled.some(({ error }) => error);
|
|
2932
|
-
if (!acked.length && !blocked.length && !has_errors) this._armed = false;
|
|
2933
2953
|
return { fetched, leased, acked, blocked };
|
|
2934
2954
|
} catch (error) {
|
|
2955
|
+
this._defer.set(
|
|
2956
|
+
RETRY,
|
|
2957
|
+
Date.now() + Math.max(leaseMillis, this._deps.breaker.cooldown_ms)
|
|
2958
|
+
);
|
|
2935
2959
|
this._deps.breaker.failed(Date.now(), error);
|
|
2936
2960
|
return EMPTY_DRAIN;
|
|
2937
2961
|
} finally {
|
|
@@ -3055,7 +3079,8 @@ var SettleLoop = class {
|
|
|
3055
3079
|
/**
|
|
3056
3080
|
* Schedule a settle pass. Multiple calls inside the debounce window
|
|
3057
3081
|
* coalesce into one cycle. The cycle runs correlate→drain in a loop
|
|
3058
|
-
* until no progress is made (no new subscriptions, no
|
|
3082
|
+
* until no progress is made (no new subscriptions, no events read, no
|
|
3083
|
+
* streams leased)
|
|
3059
3084
|
* or `maxPasses` is reached, then emits the `"settled"` lifecycle event
|
|
3060
3085
|
* via {@link SettleDeps.on_settled}.
|
|
3061
3086
|
*/
|
|
@@ -3095,7 +3120,7 @@ var SettleLoop = class {
|
|
|
3095
3120
|
acked: [...settled_drain.acked, ...drain.acked],
|
|
3096
3121
|
blocked: [...settled_drain.blocked, ...drain.blocked]
|
|
3097
3122
|
} : drain;
|
|
3098
|
-
const made_progress = subscribed > 0 || drain.
|
|
3123
|
+
const made_progress = subscribed > 0 || drain.leased.length > 0 || last_id > after_before;
|
|
3099
3124
|
if (!made_progress) break;
|
|
3100
3125
|
}
|
|
3101
3126
|
if (settled_drain) this._deps.on_settled(settled_drain);
|
|
@@ -3132,26 +3157,6 @@ var SettleLoop = class {
|
|
|
3132
3157
|
};
|
|
3133
3158
|
|
|
3134
3159
|
// src/builders/reaction-builder.ts
|
|
3135
|
-
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
3136
|
-
if (!error) return { lease, handled, acked_at: at };
|
|
3137
|
-
logger.error(error);
|
|
3138
|
-
const non_retryable = error instanceof NonRetryableError;
|
|
3139
|
-
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
3140
|
-
if (block2)
|
|
3141
|
-
logger.error(
|
|
3142
|
-
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
3143
|
-
);
|
|
3144
|
-
const next_attempt_at = block2 ? void 0 : Date.now() + compute_backoff_delay(lease.retry, options.backoff ?? DEFAULT_BACKOFF);
|
|
3145
|
-
return {
|
|
3146
|
-
lease,
|
|
3147
|
-
handled,
|
|
3148
|
-
acked_at: at,
|
|
3149
|
-
error: error.message,
|
|
3150
|
-
block: block2,
|
|
3151
|
-
next_attempt_at,
|
|
3152
|
-
failed_at
|
|
3153
|
-
};
|
|
3154
|
-
}
|
|
3155
3160
|
function build_handle(deps) {
|
|
3156
3161
|
const { logger, reaction_scope } = deps;
|
|
3157
3162
|
return async (lease, payloads) => {
|
|
@@ -4297,7 +4302,8 @@ var Act = class {
|
|
|
4297
4302
|
* Call this after `app.do()` (or `app.reset()`) to schedule a background
|
|
4298
4303
|
* drain. Multiple rapid calls within the debounce window are coalesced
|
|
4299
4304
|
* into a single cycle. Runs correlate→drain in a loop until a pass makes
|
|
4300
|
-
* no progress — no new subscriptions, no
|
|
4305
|
+
* no progress — no new subscriptions, no events read, no streams leased —
|
|
4306
|
+
* then emits
|
|
4301
4307
|
* the `"settled"` lifecycle event. This means a single `settle()` call
|
|
4302
4308
|
* fully catches up paginated streams (e.g. after `reset()` on a long
|
|
4303
4309
|
* projection) without forcing callers to loop.
|