@rotorsoft/act 1.32.27 → 1.32.28
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 +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 +2 -0
- 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 +61 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +61 -56
- 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";
|
|
@@ -2682,20 +2686,24 @@ function fetch_failure_policy(registry) {
|
|
|
2682
2686
|
}
|
|
2683
2687
|
return { blockOnError, maxRetries };
|
|
2684
2688
|
}
|
|
2685
|
-
function
|
|
2686
|
-
|
|
2687
|
-
|
|
2689
|
+
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
2690
|
+
if (!error) return { lease, handled, acked_at: at };
|
|
2691
|
+
logger.error(error);
|
|
2692
|
+
const non_retryable = error instanceof NonRetryableError;
|
|
2693
|
+
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
2694
|
+
if (block2)
|
|
2695
|
+
logger.error(
|
|
2696
|
+
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
2697
|
+
);
|
|
2698
|
+
const next_attempt_at = block2 ? void 0 : Date.now() + compute_backoff_delay(lease.retry, options.backoff ?? DEFAULT_BACKOFF);
|
|
2688
2699
|
return {
|
|
2689
2700
|
lease,
|
|
2690
|
-
handled
|
|
2691
|
-
acked_at:
|
|
2692
|
-
error,
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
options.backoff ?? DEFAULT_BACKOFF
|
|
2697
|
-
)
|
|
2698
|
-
}
|
|
2701
|
+
handled,
|
|
2702
|
+
acked_at: at,
|
|
2703
|
+
error: error.message,
|
|
2704
|
+
block: block2,
|
|
2705
|
+
next_attempt_at,
|
|
2706
|
+
failed_at
|
|
2699
2707
|
};
|
|
2700
2708
|
}
|
|
2701
2709
|
function warn_misrouted(seen, stream, handler, event) {
|
|
@@ -2743,7 +2751,9 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2743
2751
|
resolved = dynamic ? resolver(event) : resolver;
|
|
2744
2752
|
} catch (error) {
|
|
2745
2753
|
failed ??= {
|
|
2746
|
-
error:
|
|
2754
|
+
error: new Error(
|
|
2755
|
+
`Resolver of reaction "${reaction.handler.name}" threw on ${String(event.name)} #${event.id} (stream "${event.stream}"): ${String(error)}`
|
|
2756
|
+
),
|
|
2747
2757
|
options: reaction.options
|
|
2748
2758
|
};
|
|
2749
2759
|
return false;
|
|
@@ -2765,23 +2775,29 @@ async function run_drain_cycle(deps, cycle) {
|
|
|
2765
2775
|
leased.map((lease) => {
|
|
2766
2776
|
const entry = fetch_map.get(lease.stream);
|
|
2767
2777
|
if (entry.fetch.error !== void 0) {
|
|
2768
|
-
const error = `Fetch failed for ${lease.stream}: ${entry.fetch.error}`;
|
|
2769
|
-
log().error(error);
|
|
2770
2778
|
const policy = fetch_failure_policy(registry);
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
return Promise.resolve(failed);
|
|
2779
|
+
return Promise.resolve(
|
|
2780
|
+
finalize(
|
|
2781
|
+
lease,
|
|
2782
|
+
0,
|
|
2783
|
+
lease.at,
|
|
2784
|
+
new Error(`Fetch failed for ${lease.stream}: ${entry.fetch.error}`),
|
|
2785
|
+
store_failing ? { ...policy, blockOnError: false } : policy,
|
|
2786
|
+
deps.logger
|
|
2787
|
+
)
|
|
2788
|
+
);
|
|
2782
2789
|
}
|
|
2783
2790
|
if (entry.failed)
|
|
2784
|
-
return Promise.resolve(
|
|
2791
|
+
return Promise.resolve(
|
|
2792
|
+
finalize(
|
|
2793
|
+
lease,
|
|
2794
|
+
0,
|
|
2795
|
+
lease.at,
|
|
2796
|
+
entry.failed.error,
|
|
2797
|
+
entry.failed.options,
|
|
2798
|
+
deps.logger
|
|
2799
|
+
)
|
|
2800
|
+
);
|
|
2785
2801
|
const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
|
|
2786
2802
|
const { payloads } = entry;
|
|
2787
2803
|
const exhausted = budget_exhausted(
|
|
@@ -2820,18 +2836,23 @@ var EMPTY_DRAIN = {
|
|
|
2820
2836
|
acked: [],
|
|
2821
2837
|
blocked: []
|
|
2822
2838
|
};
|
|
2839
|
+
var RETRY = "";
|
|
2823
2840
|
var DrainController = class {
|
|
2824
2841
|
_armed = false;
|
|
2825
2842
|
_locked = false;
|
|
2826
2843
|
_ratio = 0.5;
|
|
2827
2844
|
/**
|
|
2828
|
-
* Local wake at the earliest parked (backoff or defer) visit
|
|
2829
|
-
*
|
|
2830
|
-
*
|
|
2845
|
+
* Local wake at the earliest parked (backoff or defer) visit, or at the
|
|
2846
|
+
* lease expiry after a failed pass (the {@link RETRY} key); the schedule
|
|
2847
|
+
* itself is persisted. On an idle app nothing else would drain it. It
|
|
2848
|
+
* drains until a pass leases nothing, and an entry stays until handled or
|
|
2849
|
+
* until a later pass claims nothing.
|
|
2831
2850
|
*/
|
|
2832
2851
|
_defer = new DeferTimer(() => {
|
|
2833
2852
|
this._armed = true;
|
|
2834
|
-
void this._deps.run_scoped(() =>
|
|
2853
|
+
void this._deps.run_scoped(async () => {
|
|
2854
|
+
while ((await this.drain()).leased.length > 0) ;
|
|
2855
|
+
});
|
|
2835
2856
|
});
|
|
2836
2857
|
_worker;
|
|
2837
2858
|
_misrouted = /* @__PURE__ */ new Set();
|
|
@@ -2928,10 +2949,12 @@ var DrainController = class {
|
|
|
2928
2949
|
if (acked.length) this._deps.on_acked(acked);
|
|
2929
2950
|
if (closeable.length) await this._deps.on_close(closeable);
|
|
2930
2951
|
this._deps.breaker.passed();
|
|
2931
|
-
const has_errors = handled.some(({ error }) => error);
|
|
2932
|
-
if (!acked.length && !blocked.length && !has_errors) this._armed = false;
|
|
2933
2952
|
return { fetched, leased, acked, blocked };
|
|
2934
2953
|
} catch (error) {
|
|
2954
|
+
this._defer.set(
|
|
2955
|
+
RETRY,
|
|
2956
|
+
Date.now() + Math.max(leaseMillis, this._deps.breaker.cooldown_ms)
|
|
2957
|
+
);
|
|
2935
2958
|
this._deps.breaker.failed(Date.now(), error);
|
|
2936
2959
|
return EMPTY_DRAIN;
|
|
2937
2960
|
} finally {
|
|
@@ -3055,7 +3078,8 @@ var SettleLoop = class {
|
|
|
3055
3078
|
/**
|
|
3056
3079
|
* Schedule a settle pass. Multiple calls inside the debounce window
|
|
3057
3080
|
* coalesce into one cycle. The cycle runs correlate→drain in a loop
|
|
3058
|
-
* until no progress is made (no new subscriptions, no
|
|
3081
|
+
* until no progress is made (no new subscriptions, no events read, no
|
|
3082
|
+
* streams leased)
|
|
3059
3083
|
* or `maxPasses` is reached, then emits the `"settled"` lifecycle event
|
|
3060
3084
|
* via {@link SettleDeps.on_settled}.
|
|
3061
3085
|
*/
|
|
@@ -3095,7 +3119,7 @@ var SettleLoop = class {
|
|
|
3095
3119
|
acked: [...settled_drain.acked, ...drain.acked],
|
|
3096
3120
|
blocked: [...settled_drain.blocked, ...drain.blocked]
|
|
3097
3121
|
} : drain;
|
|
3098
|
-
const made_progress = subscribed > 0 || drain.
|
|
3122
|
+
const made_progress = subscribed > 0 || drain.leased.length > 0 || last_id > after_before;
|
|
3099
3123
|
if (!made_progress) break;
|
|
3100
3124
|
}
|
|
3101
3125
|
if (settled_drain) this._deps.on_settled(settled_drain);
|
|
@@ -3132,26 +3156,6 @@ var SettleLoop = class {
|
|
|
3132
3156
|
};
|
|
3133
3157
|
|
|
3134
3158
|
// 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
3159
|
function build_handle(deps) {
|
|
3156
3160
|
const { logger, reaction_scope } = deps;
|
|
3157
3161
|
return async (lease, payloads) => {
|
|
@@ -4297,7 +4301,8 @@ var Act = class {
|
|
|
4297
4301
|
* Call this after `app.do()` (or `app.reset()`) to schedule a background
|
|
4298
4302
|
* drain. Multiple rapid calls within the debounce window are coalesced
|
|
4299
4303
|
* into a single cycle. Runs correlate→drain in a loop until a pass makes
|
|
4300
|
-
* no progress — no new subscriptions, no
|
|
4304
|
+
* no progress — no new subscriptions, no events read, no streams leased —
|
|
4305
|
+
* then emits
|
|
4301
4306
|
* the `"settled"` lifecycle event. This means a single `settle()` call
|
|
4302
4307
|
* fully catches up paginated streams (e.g. after `reset()` on a long
|
|
4303
4308
|
* projection) without forcing callers to loop.
|