brass-runtime 1.15.0 → 1.16.1

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.
Files changed (209) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +673 -136
  3. package/dist/agent/cli/main.cjs +40 -35
  4. package/dist/agent/cli/main.js +9 -4
  5. package/dist/agent/cli/main.mjs +9 -4
  6. package/dist/agent/index.cjs +8 -4
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.js +7 -3
  9. package/dist/agent/index.mjs +7 -3
  10. package/dist/chunk-2HQTDLHF.mjs +683 -0
  11. package/dist/chunk-36I3M4UC.mjs +370 -0
  12. package/dist/chunk-3AYM6WPJ.js +1629 -0
  13. package/dist/chunk-3LOYJFRR.cjs +300 -0
  14. package/dist/chunk-3RG5ZIWI.js +10 -0
  15. package/dist/chunk-3Y2RIUMM.js +300 -0
  16. package/dist/{chunk-VEZNF5GZ.cjs → chunk-4ROBZFL6.cjs} +130 -126
  17. package/dist/{chunk-3QMOKAS5.js → chunk-52OB2ROS.js} +9 -5
  18. package/dist/chunk-52PPNNI4.cjs +416 -0
  19. package/dist/chunk-5EC274J5.cjs +2874 -0
  20. package/dist/chunk-5QC7LRZ3.js +229 -0
  21. package/dist/chunk-5VRJNBLZ.mjs +2874 -0
  22. package/dist/chunk-62AZW6UT.cjs +313 -0
  23. package/dist/chunk-6IXXWIUM.js +683 -0
  24. package/dist/chunk-74ZTY6CP.js +2871 -0
  25. package/dist/chunk-76YMRMH2.cjs +777 -0
  26. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  27. package/dist/{chunk-4NHES7VK.mjs → chunk-7JIJOVCT.js} +27 -13
  28. package/dist/chunk-A2OM6NEH.mjs +194 -0
  29. package/dist/chunk-AGR5B2BC.cjs +683 -0
  30. package/dist/chunk-AVNQLJ5V.js +777 -0
  31. package/dist/chunk-B33ICAKP.js +313 -0
  32. package/dist/{chunk-ELOOF35R.mjs → chunk-B5JD23U7.mjs} +1 -1
  33. package/dist/chunk-BABBZK4Y.js +2024 -0
  34. package/dist/chunk-C3MDXTRZ.js +354 -0
  35. package/dist/chunk-CIZFIMK5.js +2193 -0
  36. package/dist/chunk-CZIVE6NT.cjs +354 -0
  37. package/dist/chunk-DNFJLJMW.mjs +354 -0
  38. package/dist/chunk-DNFO2EIZ.mjs +777 -0
  39. package/dist/chunk-EJ6BPYVR.mjs +416 -0
  40. package/dist/chunk-ENKODRU3.cjs +2193 -0
  41. package/dist/chunk-EOC4UHBS.mjs +229 -0
  42. package/dist/{chunk-BMH5AV44.js → chunk-FH2X7BVP.js} +756 -440
  43. package/dist/{chunk-PPUXIH5R.js → chunk-FHQGHPMO.mjs} +27 -13
  44. package/dist/{chunk-TGIFUAK4.cjs → chunk-GLE2WY7Z.cjs} +951 -635
  45. package/dist/{chunk-BDF4AMWX.mjs → chunk-GYM3LLGS.mjs} +756 -440
  46. package/dist/chunk-HLWLMW2F.mjs +2024 -0
  47. package/dist/chunk-JF5WGYJJ.cjs +194 -0
  48. package/dist/chunk-KH4SYAOS.mjs +1629 -0
  49. package/dist/chunk-KN32XNTH.mjs +313 -0
  50. package/dist/chunk-KQLYONSE.cjs +2871 -0
  51. package/dist/{chunk-STVLQ3XD.cjs → chunk-KZJQ723N.cjs} +92 -78
  52. package/dist/chunk-L2SYFEBS.js +194 -0
  53. package/dist/chunk-L6VB5N7Q.cjs +104 -0
  54. package/dist/{chunk-K6M7MDZ4.mjs → chunk-MBEJI5HF.mjs} +9 -5
  55. package/dist/chunk-MIIYDLGM.js +2874 -0
  56. package/dist/chunk-MOO4L7F4.mjs +104 -0
  57. package/dist/chunk-MT3OWDPC.mjs +2193 -0
  58. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  59. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  60. package/dist/chunk-PD4EJTQC.cjs +229 -0
  61. package/dist/chunk-PWC3RBQE.mjs +300 -0
  62. package/dist/chunk-Q2I37RP3.cjs +1629 -0
  63. package/dist/chunk-RKGKFN2A.js +416 -0
  64. package/dist/{chunk-R3R2FVLG.cjs → chunk-SA6HUJVI.cjs} +5 -5
  65. package/dist/chunk-TRM4JUZQ.js +104 -0
  66. package/dist/chunk-UB4B6OFY.js +370 -0
  67. package/dist/{chunk-TO7IKXYT.js → chunk-UCUBNWM2.js} +1 -1
  68. package/dist/chunk-VN44DYYT.cjs +2024 -0
  69. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  70. package/dist/client-CZHU674n.d.ts +820 -0
  71. package/dist/core/index.cjs +198 -4
  72. package/dist/core/index.d.ts +311 -212
  73. package/dist/core/index.js +237 -43
  74. package/dist/core/index.mjs +237 -43
  75. package/dist/{effect-CMOQKX8y.d.ts → effect-DIUHZ9IN.d.ts} +195 -1
  76. package/dist/effectRunner-CFLC32IK.cjs +8 -0
  77. package/dist/effectRunner-L4S7IPT3.js +8 -0
  78. package/dist/effectRunner-NNGG75QA.mjs +8 -0
  79. package/dist/http/index.cjs +1227 -2971
  80. package/dist/http/index.d.ts +826 -280
  81. package/dist/http/index.js +1089 -2833
  82. package/dist/http/index.mjs +1089 -2833
  83. package/dist/http/testing.cjs +161 -0
  84. package/dist/http/testing.d.ts +43 -0
  85. package/dist/http/testing.js +161 -0
  86. package/dist/http/testing.mjs +161 -0
  87. package/dist/index.cjs +486 -250
  88. package/dist/index.d.ts +87 -95
  89. package/dist/index.js +391 -155
  90. package/dist/index.mjs +391 -155
  91. package/dist/observability/index.cjs +162 -0
  92. package/dist/observability/index.d.ts +152 -0
  93. package/dist/observability/index.js +162 -0
  94. package/dist/observability/index.mjs +162 -0
  95. package/dist/perf/cli.cjs +401 -0
  96. package/dist/perf/cli.d.ts +1 -0
  97. package/dist/perf/cli.js +401 -0
  98. package/dist/perf/cli.mjs +401 -0
  99. package/dist/perf/index.cjs +141 -0
  100. package/dist/perf/index.d.ts +483 -0
  101. package/dist/perf/index.js +141 -0
  102. package/dist/perf/index.mjs +141 -0
  103. package/dist/schedule-CK3Ml_7p.d.ts +259 -0
  104. package/dist/schema/index.cjs +29 -0
  105. package/dist/schema/index.d.ts +179 -0
  106. package/dist/schema/index.js +29 -0
  107. package/dist/schema/index.mjs +29 -0
  108. package/dist/server-GJPg8ZSG.d.ts +675 -0
  109. package/dist/{stream-FQm9h4Mg.d.ts → stream-B4oK9JFP.d.ts} +1 -1
  110. package/dist/tracer-Hwt1cl7h.d.ts +189 -0
  111. package/dist/tracing-DqbTKGcf.d.ts +148 -0
  112. package/docs/ARCHITECTURE.md +292 -0
  113. package/docs/README.md +63 -0
  114. package/docs/adr/0001-ai-context-pack.md +32 -0
  115. package/docs/agent-apply-mode.md +104 -0
  116. package/docs/agent-approvals.md +110 -0
  117. package/docs/agent-batch.md +185 -0
  118. package/docs/agent-boundaries.md +112 -0
  119. package/docs/agent-chat-sessions.md +160 -0
  120. package/docs/agent-ci.md +17 -0
  121. package/docs/agent-cli.md +405 -0
  122. package/docs/agent-config.md +480 -0
  123. package/docs/agent-context-discovery.md +159 -0
  124. package/docs/agent-copilot-like-dx.md +126 -0
  125. package/docs/agent-declarative-optimized-planning.md +138 -0
  126. package/docs/agent-dx.md +224 -0
  127. package/docs/agent-env-files.md +126 -0
  128. package/docs/agent-follow-up-context.md +43 -0
  129. package/docs/agent-global-usage.md +180 -0
  130. package/docs/agent-init.md +109 -0
  131. package/docs/agent-install-and-configure.md +516 -0
  132. package/docs/agent-language-workspace-ux.md +99 -0
  133. package/docs/agent-llm-adapters.md +123 -0
  134. package/docs/agent-local-install.md +190 -0
  135. package/docs/agent-local-tests.md +51 -0
  136. package/docs/agent-observability.md +155 -0
  137. package/docs/agent-patch-quality-loop.md +162 -0
  138. package/docs/agent-presets.md +22 -0
  139. package/docs/agent-project-commands.md +237 -0
  140. package/docs/agent-project-intelligence.md +156 -0
  141. package/docs/agent-redaction.md +18 -0
  142. package/docs/agent-release-readiness.md +76 -0
  143. package/docs/agent-rollback-safety.md +162 -0
  144. package/docs/agent-rollback.md +23 -0
  145. package/docs/agent-run-artifacts.md +16 -0
  146. package/docs/agent-vscode-auto-discovery.md +137 -0
  147. package/docs/agent-vscode-batch-runner.md +100 -0
  148. package/docs/agent-vscode-chat-layout.md +90 -0
  149. package/docs/agent-vscode-clean-install.md +147 -0
  150. package/docs/agent-vscode-code-actions.md +70 -0
  151. package/docs/agent-vscode-diff-preview.md +45 -0
  152. package/docs/agent-vscode-inline-assist.md +56 -0
  153. package/docs/agent-vscode-install.md +186 -0
  154. package/docs/agent-vscode-model-setup.md +97 -0
  155. package/docs/agent-vscode-patch-preview.md +92 -0
  156. package/docs/agent-vscode-problems.md +79 -0
  157. package/docs/agent-vscode-project-dashboard.md +106 -0
  158. package/docs/agent-vscode-run-history.md +92 -0
  159. package/docs/agent-vscode-ux.md +73 -0
  160. package/docs/ai/INVARIANTS.md +84 -0
  161. package/docs/ai/PROJECT_MAP.md +338 -0
  162. package/docs/ai/PUBLIC_API.md +336 -0
  163. package/docs/ai/VALIDATION_MATRIX.md +67 -0
  164. package/docs/api-polish.md +37 -0
  165. package/docs/cancellation.md +162 -0
  166. package/docs/coverage.md +46 -0
  167. package/docs/getting-started.md +159 -0
  168. package/docs/guides/README.md +40 -0
  169. package/docs/guides/circuit-breaker.md +89 -0
  170. package/docs/guides/error-handling.md +91 -0
  171. package/docs/guides/getting-started.md +107 -0
  172. package/docs/guides/layers.md +189 -0
  173. package/docs/guides/metrics.md +101 -0
  174. package/docs/guides/resource-management.md +141 -0
  175. package/docs/guides/retry.md +215 -0
  176. package/docs/guides/semaphore.md +66 -0
  177. package/docs/guides/streams.md +117 -0
  178. package/docs/guides/supervisors.md +98 -0
  179. package/docs/guides/testing.md +162 -0
  180. package/docs/guides/tracing.md +71 -0
  181. package/docs/http-recipes.md +399 -0
  182. package/docs/http.md +749 -0
  183. package/docs/modules.md +285 -0
  184. package/docs/observability-collector-smoke.md +31 -0
  185. package/docs/observability-framework-examples.md +98 -0
  186. package/docs/observability.md +542 -0
  187. package/docs/otel-collector-smoke.yaml +27 -0
  188. package/docs/performance-profiler.md +199 -0
  189. package/docs/production-readiness.md +73 -0
  190. package/docs/recipes/README.md +12 -0
  191. package/docs/recipes/http-server.md +45 -0
  192. package/docs/recipes/layers.md +44 -0
  193. package/docs/recipes/performance.md +47 -0
  194. package/docs/recipes/runtime.md +41 -0
  195. package/docs/recipes/testing.md +41 -0
  196. package/docs/release.md +53 -0
  197. package/docs/wasm-bounded-queues.md +44 -0
  198. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  199. package/docs/wasm-fiber-engine.md +117 -0
  200. package/docs/wasm-scheduler-state-machine.md +122 -0
  201. package/docs/wasm-stream-chunks.md +54 -0
  202. package/package.json +48 -2
  203. package/dist/chunk-AR22SXML.js +0 -1043
  204. package/dist/chunk-BDYEENHT.js +0 -224
  205. package/dist/chunk-JFPU5GQI.mjs +0 -1043
  206. package/dist/chunk-MS34J5LY.cjs +0 -224
  207. package/dist/chunk-UMAZLXAB.mjs +0 -224
  208. package/dist/chunk-XPZNXSVN.cjs +0 -1043
  209. package/dist/tracing-DNT9jEbr.d.ts +0 -106
@@ -1,224 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
-
3
-
4
-
5
-
6
-
7
-
8
- var _chunkTGIFUAK4cjs = require('./chunk-TGIFUAK4.cjs');
9
-
10
- // src/core/runtime/combinators.ts
11
- function sleep(ms) {
12
- return _chunkTGIFUAK4cjs.asyncEffect.call(void 0, (_env, cb) => {
13
- const delay = Math.max(0, Math.floor(ms));
14
- const id = setTimeout(() => cb({ _tag: "Success", value: void 0 }), delay);
15
- return () => clearTimeout(id);
16
- });
17
- }
18
- function timeout(effect, ms) {
19
- return _chunkTGIFUAK4cjs.asyncEffect.call(void 0, (env, cb) => {
20
- let done = false;
21
- let timerId;
22
- let effectRunning = true;
23
- timerId = setTimeout(() => {
24
- if (done) return;
25
- done = true;
26
- effectRunning = false;
27
- cb({
28
- _tag: "Failure",
29
- cause: { _tag: "Fail", error: { _tag: "TimeoutError", ms } }
30
- });
31
- }, Math.max(0, Math.floor(ms)));
32
- const runtime = _chunkTGIFUAK4cjs.unsafeGetCurrentRuntime.call(void 0, );
33
- if (runtime) {
34
- const fiber = runtime.fork(effect);
35
- fiber.join((exit) => {
36
- if (done) return;
37
- done = true;
38
- clearTimeout(timerId);
39
- cb(exit);
40
- });
41
- return () => {
42
- if (done) return;
43
- done = true;
44
- clearTimeout(timerId);
45
- fiber.interrupt();
46
- };
47
- }
48
- return () => {
49
- if (done) return;
50
- done = true;
51
- clearTimeout(timerId);
52
- };
53
- });
54
- }
55
- function retry(effect, policy) {
56
- const shouldRetry = _nullishCoalesce(policy.shouldRetry, () => ( (() => true)));
57
- const jitter = _nullishCoalesce(policy.jitter, () => ( "full"));
58
- const maxElapsedMs = policy.maxElapsedMs;
59
- const computeDelay = (attempt) => {
60
- const exp = policy.baseDelayMs * Math.pow(2, attempt);
61
- const capped = Math.min(exp, policy.maxDelayMs);
62
- if (jitter === "none") return capped;
63
- return Math.floor(Math.random() * capped);
64
- };
65
- const loop = (attempt, startedAt) => _chunkTGIFUAK4cjs.asyncFold.call(void 0,
66
- effect,
67
- (error) => {
68
- if (attempt >= policy.maxRetries) return _chunkTGIFUAK4cjs.asyncFail.call(void 0, error);
69
- if (!shouldRetry(error, attempt)) return _chunkTGIFUAK4cjs.asyncFail.call(void 0, error);
70
- if (maxElapsedMs !== void 0) {
71
- const elapsed = performance.now() - startedAt;
72
- if (elapsed >= maxElapsedMs) return _chunkTGIFUAK4cjs.asyncFail.call(void 0, error);
73
- }
74
- const delay = computeDelay(attempt);
75
- return _chunkTGIFUAK4cjs.asyncFlatMap.call(void 0, sleep(delay), () => loop(attempt + 1, startedAt));
76
- },
77
- (value) => _chunkTGIFUAK4cjs.asyncSucceed.call(void 0, value)
78
- );
79
- return _chunkTGIFUAK4cjs.asyncFlatMap.call(void 0,
80
- { _tag: "Sync", thunk: () => performance.now() },
81
- (startedAt) => loop(0, startedAt)
82
- );
83
- }
84
- function retryN(effect, n) {
85
- return retry(effect, {
86
- maxRetries: n,
87
- baseDelayMs: 0,
88
- maxDelayMs: 0,
89
- jitter: "none"
90
- });
91
- }
92
- function retryWithBackoff(effect, opts = {}) {
93
- return retry(effect, {
94
- maxRetries: _nullishCoalesce(opts.maxRetries, () => ( 3)),
95
- baseDelayMs: _nullishCoalesce(opts.baseDelayMs, () => ( 100)),
96
- maxDelayMs: _nullishCoalesce(opts.maxDelayMs, () => ( 1e4)),
97
- maxElapsedMs: opts.maxElapsedMs,
98
- shouldRetry: opts.shouldRetry,
99
- jitter: "full"
100
- });
101
- }
102
-
103
- // src/core/runtime/circuitBreaker.ts
104
- function makeCircuitBreaker(config = {}) {
105
- const failureThreshold = _nullishCoalesce(config.failureThreshold, () => ( 5));
106
- const resetTimeoutMs = _nullishCoalesce(config.resetTimeoutMs, () => ( 3e4));
107
- const successThreshold = _nullishCoalesce(config.successThreshold, () => ( 1));
108
- const isFailure = _nullishCoalesce(config.isFailure, () => ( (() => true)));
109
- const onStateChange = config.onStateChange;
110
- let currentState = "closed";
111
- let consecutiveFailures = 0;
112
- let consecutiveSuccesses = 0;
113
- let openedAt = 0;
114
- let totalRequests = 0;
115
- let totalFailures = 0;
116
- let totalSuccesses = 0;
117
- let totalRejected = 0;
118
- let lastFailureTime = null;
119
- let lastSuccessTime = null;
120
- const transition = (to) => {
121
- if (currentState === to) return;
122
- const from = currentState;
123
- currentState = to;
124
- _optionalChain([onStateChange, 'optionalCall', _ => _(from, to)]);
125
- };
126
- const onSuccess = () => {
127
- totalSuccesses++;
128
- lastSuccessTime = Date.now();
129
- consecutiveFailures = 0;
130
- if (currentState === "half-open") {
131
- consecutiveSuccesses++;
132
- if (consecutiveSuccesses >= successThreshold) {
133
- consecutiveSuccesses = 0;
134
- transition("closed");
135
- }
136
- }
137
- };
138
- const onFailure = (error) => {
139
- if (!isFailure(error)) {
140
- onSuccess();
141
- return;
142
- }
143
- totalFailures++;
144
- lastFailureTime = Date.now();
145
- consecutiveSuccesses = 0;
146
- consecutiveFailures++;
147
- if (currentState === "half-open") {
148
- openedAt = Date.now();
149
- transition("open");
150
- } else if (currentState === "closed" && consecutiveFailures >= failureThreshold) {
151
- openedAt = Date.now();
152
- transition("open");
153
- }
154
- };
155
- const shouldAllow = () => {
156
- switch (currentState) {
157
- case "closed":
158
- return true;
159
- case "open": {
160
- const elapsed = Date.now() - openedAt;
161
- if (elapsed >= resetTimeoutMs) {
162
- transition("half-open");
163
- return true;
164
- }
165
- return false;
166
- }
167
- case "half-open":
168
- return true;
169
- }
170
- };
171
- const protect = (effect) => {
172
- totalRequests++;
173
- if (!shouldAllow()) {
174
- totalRejected++;
175
- return _chunkTGIFUAK4cjs.asyncFail.call(void 0, {
176
- _tag: "CircuitBreakerOpen",
177
- openSince: openedAt,
178
- failures: consecutiveFailures
179
- });
180
- }
181
- return _chunkTGIFUAK4cjs.asyncFold.call(void 0,
182
- effect,
183
- (error) => {
184
- onFailure(error);
185
- return _chunkTGIFUAK4cjs.asyncFail.call(void 0, error);
186
- },
187
- (value) => {
188
- onSuccess();
189
- return _chunkTGIFUAK4cjs.asyncSucceed.call(void 0, value);
190
- }
191
- );
192
- };
193
- const stats = () => ({
194
- state: currentState,
195
- failures: consecutiveFailures,
196
- successes: consecutiveSuccesses,
197
- totalRequests,
198
- totalFailures,
199
- totalSuccesses,
200
- totalRejected,
201
- lastFailureTime,
202
- lastSuccessTime
203
- });
204
- const reset = () => {
205
- consecutiveFailures = 0;
206
- consecutiveSuccesses = 0;
207
- transition("closed");
208
- };
209
- return {
210
- state: () => currentState,
211
- protect,
212
- stats,
213
- reset
214
- };
215
- }
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
- exports.sleep = sleep; exports.timeout = timeout; exports.retry = retry; exports.retryN = retryN; exports.retryWithBackoff = retryWithBackoff; exports.makeCircuitBreaker = makeCircuitBreaker;
@@ -1,224 +0,0 @@
1
- import {
2
- asyncEffect,
3
- asyncFail,
4
- asyncFlatMap,
5
- asyncFold,
6
- asyncSucceed,
7
- unsafeGetCurrentRuntime
8
- } from "./chunk-BDF4AMWX.mjs";
9
-
10
- // src/core/runtime/combinators.ts
11
- function sleep(ms) {
12
- return asyncEffect((_env, cb) => {
13
- const delay = Math.max(0, Math.floor(ms));
14
- const id = setTimeout(() => cb({ _tag: "Success", value: void 0 }), delay);
15
- return () => clearTimeout(id);
16
- });
17
- }
18
- function timeout(effect, ms) {
19
- return asyncEffect((env, cb) => {
20
- let done = false;
21
- let timerId;
22
- let effectRunning = true;
23
- timerId = setTimeout(() => {
24
- if (done) return;
25
- done = true;
26
- effectRunning = false;
27
- cb({
28
- _tag: "Failure",
29
- cause: { _tag: "Fail", error: { _tag: "TimeoutError", ms } }
30
- });
31
- }, Math.max(0, Math.floor(ms)));
32
- const runtime = unsafeGetCurrentRuntime();
33
- if (runtime) {
34
- const fiber = runtime.fork(effect);
35
- fiber.join((exit) => {
36
- if (done) return;
37
- done = true;
38
- clearTimeout(timerId);
39
- cb(exit);
40
- });
41
- return () => {
42
- if (done) return;
43
- done = true;
44
- clearTimeout(timerId);
45
- fiber.interrupt();
46
- };
47
- }
48
- return () => {
49
- if (done) return;
50
- done = true;
51
- clearTimeout(timerId);
52
- };
53
- });
54
- }
55
- function retry(effect, policy) {
56
- const shouldRetry = policy.shouldRetry ?? (() => true);
57
- const jitter = policy.jitter ?? "full";
58
- const maxElapsedMs = policy.maxElapsedMs;
59
- const computeDelay = (attempt) => {
60
- const exp = policy.baseDelayMs * Math.pow(2, attempt);
61
- const capped = Math.min(exp, policy.maxDelayMs);
62
- if (jitter === "none") return capped;
63
- return Math.floor(Math.random() * capped);
64
- };
65
- const loop = (attempt, startedAt) => asyncFold(
66
- effect,
67
- (error) => {
68
- if (attempt >= policy.maxRetries) return asyncFail(error);
69
- if (!shouldRetry(error, attempt)) return asyncFail(error);
70
- if (maxElapsedMs !== void 0) {
71
- const elapsed = performance.now() - startedAt;
72
- if (elapsed >= maxElapsedMs) return asyncFail(error);
73
- }
74
- const delay = computeDelay(attempt);
75
- return asyncFlatMap(sleep(delay), () => loop(attempt + 1, startedAt));
76
- },
77
- (value) => asyncSucceed(value)
78
- );
79
- return asyncFlatMap(
80
- { _tag: "Sync", thunk: () => performance.now() },
81
- (startedAt) => loop(0, startedAt)
82
- );
83
- }
84
- function retryN(effect, n) {
85
- return retry(effect, {
86
- maxRetries: n,
87
- baseDelayMs: 0,
88
- maxDelayMs: 0,
89
- jitter: "none"
90
- });
91
- }
92
- function retryWithBackoff(effect, opts = {}) {
93
- return retry(effect, {
94
- maxRetries: opts.maxRetries ?? 3,
95
- baseDelayMs: opts.baseDelayMs ?? 100,
96
- maxDelayMs: opts.maxDelayMs ?? 1e4,
97
- maxElapsedMs: opts.maxElapsedMs,
98
- shouldRetry: opts.shouldRetry,
99
- jitter: "full"
100
- });
101
- }
102
-
103
- // src/core/runtime/circuitBreaker.ts
104
- function makeCircuitBreaker(config = {}) {
105
- const failureThreshold = config.failureThreshold ?? 5;
106
- const resetTimeoutMs = config.resetTimeoutMs ?? 3e4;
107
- const successThreshold = config.successThreshold ?? 1;
108
- const isFailure = config.isFailure ?? (() => true);
109
- const onStateChange = config.onStateChange;
110
- let currentState = "closed";
111
- let consecutiveFailures = 0;
112
- let consecutiveSuccesses = 0;
113
- let openedAt = 0;
114
- let totalRequests = 0;
115
- let totalFailures = 0;
116
- let totalSuccesses = 0;
117
- let totalRejected = 0;
118
- let lastFailureTime = null;
119
- let lastSuccessTime = null;
120
- const transition = (to) => {
121
- if (currentState === to) return;
122
- const from = currentState;
123
- currentState = to;
124
- onStateChange?.(from, to);
125
- };
126
- const onSuccess = () => {
127
- totalSuccesses++;
128
- lastSuccessTime = Date.now();
129
- consecutiveFailures = 0;
130
- if (currentState === "half-open") {
131
- consecutiveSuccesses++;
132
- if (consecutiveSuccesses >= successThreshold) {
133
- consecutiveSuccesses = 0;
134
- transition("closed");
135
- }
136
- }
137
- };
138
- const onFailure = (error) => {
139
- if (!isFailure(error)) {
140
- onSuccess();
141
- return;
142
- }
143
- totalFailures++;
144
- lastFailureTime = Date.now();
145
- consecutiveSuccesses = 0;
146
- consecutiveFailures++;
147
- if (currentState === "half-open") {
148
- openedAt = Date.now();
149
- transition("open");
150
- } else if (currentState === "closed" && consecutiveFailures >= failureThreshold) {
151
- openedAt = Date.now();
152
- transition("open");
153
- }
154
- };
155
- const shouldAllow = () => {
156
- switch (currentState) {
157
- case "closed":
158
- return true;
159
- case "open": {
160
- const elapsed = Date.now() - openedAt;
161
- if (elapsed >= resetTimeoutMs) {
162
- transition("half-open");
163
- return true;
164
- }
165
- return false;
166
- }
167
- case "half-open":
168
- return true;
169
- }
170
- };
171
- const protect = (effect) => {
172
- totalRequests++;
173
- if (!shouldAllow()) {
174
- totalRejected++;
175
- return asyncFail({
176
- _tag: "CircuitBreakerOpen",
177
- openSince: openedAt,
178
- failures: consecutiveFailures
179
- });
180
- }
181
- return asyncFold(
182
- effect,
183
- (error) => {
184
- onFailure(error);
185
- return asyncFail(error);
186
- },
187
- (value) => {
188
- onSuccess();
189
- return asyncSucceed(value);
190
- }
191
- );
192
- };
193
- const stats = () => ({
194
- state: currentState,
195
- failures: consecutiveFailures,
196
- successes: consecutiveSuccesses,
197
- totalRequests,
198
- totalFailures,
199
- totalSuccesses,
200
- totalRejected,
201
- lastFailureTime,
202
- lastSuccessTime
203
- });
204
- const reset = () => {
205
- consecutiveFailures = 0;
206
- consecutiveSuccesses = 0;
207
- transition("closed");
208
- };
209
- return {
210
- state: () => currentState,
211
- protect,
212
- stats,
213
- reset
214
- };
215
- }
216
-
217
- export {
218
- sleep,
219
- timeout,
220
- retry,
221
- retryN,
222
- retryWithBackoff,
223
- makeCircuitBreaker
224
- };