brass-runtime 1.16.0 → 1.17.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.
Files changed (219) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +287 -23
  3. package/dist/agent/cli/main.cjs +38 -38
  4. package/dist/agent/cli/main.js +6 -6
  5. package/dist/agent/cli/main.mjs +6 -6
  6. package/dist/agent/index.cjs +7 -7
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.js +6 -6
  9. package/dist/agent/index.mjs +6 -6
  10. package/dist/chunk-2HQTDLHF.mjs +683 -0
  11. package/dist/chunk-36I3M4UC.mjs +370 -0
  12. package/dist/{chunk-QY5FKYEQ.js → chunk-3AYM6WPJ.js} +570 -51
  13. package/dist/chunk-3LOYJFRR.cjs +300 -0
  14. package/dist/chunk-3Y2RIUMM.js +300 -0
  15. package/dist/{chunk-7XOPAB5Q.js → chunk-4P2HHGAX.mjs} +83 -5
  16. package/dist/{chunk-N6VHMOWB.cjs → chunk-4ROBZFL6.cjs} +128 -128
  17. package/dist/{chunk-NC5SDRYE.js → chunk-52OB2ROS.js} +4 -4
  18. package/dist/{chunk-JX3LZQJH.cjs → chunk-52PPNNI4.cjs} +82 -20
  19. package/dist/{chunk-5YOQOXEQ.cjs → chunk-5EC274J5.cjs} +676 -293
  20. package/dist/chunk-5QC7LRZ3.js +229 -0
  21. package/dist/{chunk-7TL2LHQJ.js → chunk-5VRJNBLZ.mjs} +524 -141
  22. package/dist/chunk-62AZW6UT.cjs +313 -0
  23. package/dist/chunk-6IXXWIUM.js +683 -0
  24. package/dist/chunk-6RY2FFN4.mjs +2024 -0
  25. package/dist/chunk-74ZTY6CP.js +2871 -0
  26. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  27. package/dist/{chunk-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
  28. package/dist/chunk-7X3K5RMS.js +2024 -0
  29. package/dist/chunk-7ZPEZ57L.cjs +2024 -0
  30. package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
  31. package/dist/chunk-AGR5B2BC.cjs +683 -0
  32. package/dist/chunk-B33ICAKP.js +313 -0
  33. package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
  34. package/dist/{chunk-F5EUMJL7.mjs → chunk-BKK77SBA.js} +83 -5
  35. package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
  36. package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
  37. package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
  38. package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
  39. package/dist/chunk-EOC4UHBS.mjs +229 -0
  40. package/dist/chunk-F6XWZQY4.cjs +777 -0
  41. package/dist/{chunk-7LVI2GIN.js → chunk-FH2X7BVP.js} +507 -72
  42. package/dist/{chunk-OOGJ73B6.js → chunk-FHQGHPMO.mjs} +20 -10
  43. package/dist/{chunk-WQ5QNU5R.cjs → chunk-GLE2WY7Z.cjs} +652 -217
  44. package/dist/{chunk-G6IQOE4P.mjs → chunk-GYM3LLGS.mjs} +507 -72
  45. package/dist/{chunk-TVN5I4U6.cjs → chunk-JF5WGYJJ.cjs} +25 -24
  46. package/dist/{chunk-CY33PGEX.mjs → chunk-KH4SYAOS.mjs} +570 -51
  47. package/dist/chunk-KN32XNTH.mjs +313 -0
  48. package/dist/chunk-KQLYONSE.cjs +2871 -0
  49. package/dist/{chunk-7HUOJA4W.cjs → chunk-KZJQ723N.cjs} +90 -80
  50. package/dist/{chunk-CCKHV5BT.mjs → chunk-L2SYFEBS.js} +5 -4
  51. package/dist/{chunk-IJT6RRQ5.cjs → chunk-L6VB5N7Q.cjs} +20 -9
  52. package/dist/{chunk-ZGLD4TVZ.mjs → chunk-MBEJI5HF.mjs} +4 -4
  53. package/dist/{chunk-PRWCB3QL.mjs → chunk-MIIYDLGM.js} +524 -141
  54. package/dist/{chunk-H55LI6WY.js → chunk-MOO4L7F4.mjs} +15 -4
  55. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  56. package/dist/chunk-PD4EJTQC.cjs +229 -0
  57. package/dist/chunk-PWC3RBQE.mjs +300 -0
  58. package/dist/{chunk-MWXMNYJS.cjs → chunk-Q2I37RP3.cjs} +643 -124
  59. package/dist/{chunk-VFIUZG7J.mjs → chunk-RKGKFN2A.js} +79 -17
  60. package/dist/{chunk-NYL4D7SK.cjs → chunk-SA6HUJVI.cjs} +5 -5
  61. package/dist/chunk-SK7UZRNI.mjs +777 -0
  62. package/dist/{chunk-K2T3DV26.mjs → chunk-TRM4JUZQ.js} +15 -4
  63. package/dist/chunk-UB4B6OFY.js +370 -0
  64. package/dist/{chunk-G3XGCZDQ.js → chunk-UCUBNWM2.js} +1 -1
  65. package/dist/chunk-VWIPB6I5.js +777 -0
  66. package/dist/{chunk-JNFRRJYH.cjs → chunk-WBGRHGBP.cjs} +270 -192
  67. package/dist/{client-CtFmoDvM.d.ts → client-CZHU674n.d.ts} +211 -36
  68. package/dist/core/index.cjs +135 -9
  69. package/dist/core/index.d.ts +238 -33
  70. package/dist/core/index.js +155 -29
  71. package/dist/core/index.mjs +155 -29
  72. package/dist/{effect-CGNl5Rqp.d.ts → effect-DIUHZ9IN.d.ts} +89 -1
  73. package/dist/effectRunner-CFLC32IK.cjs +8 -0
  74. package/dist/{effectRunner-A4CHJXJI.js → effectRunner-L4S7IPT3.js} +2 -2
  75. package/dist/{effectRunner-OPUF6QRN.mjs → effectRunner-NNGG75QA.mjs} +2 -2
  76. package/dist/http/index.cjs +324 -2986
  77. package/dist/http/index.d.ts +54 -68
  78. package/dist/http/index.js +238 -2900
  79. package/dist/http/index.mjs +238 -2900
  80. package/dist/http/testing.cjs +14 -12
  81. package/dist/http/testing.d.ts +5 -4
  82. package/dist/http/testing.js +10 -8
  83. package/dist/http/testing.mjs +10 -8
  84. package/dist/index.cjs +423 -255
  85. package/dist/index.d.ts +87 -69
  86. package/dist/index.js +301 -133
  87. package/dist/index.mjs +301 -133
  88. package/dist/observability/index.cjs +18 -531
  89. package/dist/observability/index.d.ts +81 -8
  90. package/dist/observability/index.js +25 -538
  91. package/dist/observability/index.mjs +25 -538
  92. package/dist/perf/cli.cjs +401 -0
  93. package/dist/perf/cli.d.ts +1 -0
  94. package/dist/perf/cli.js +401 -0
  95. package/dist/perf/cli.mjs +401 -0
  96. package/dist/perf/index.cjs +141 -0
  97. package/dist/perf/index.d.ts +483 -0
  98. package/dist/perf/index.js +141 -0
  99. package/dist/perf/index.mjs +141 -0
  100. package/dist/schedule-CK3Ml_7p.d.ts +259 -0
  101. package/dist/schema/index.cjs +6 -2
  102. package/dist/schema/index.d.ts +3 -1
  103. package/dist/schema/index.js +5 -1
  104. package/dist/schema/index.mjs +5 -1
  105. package/dist/{server-C8hDXA74.d.ts → server-D6JZ15_e.d.ts} +16 -4
  106. package/dist/{stream-dvSs0QS5.d.ts → stream-B4oK9JFP.d.ts} +1 -1
  107. package/dist/{tracer-B5tRH9H7.d.ts → tracer-Hwt1cl7h.d.ts} +13 -54
  108. package/dist/{tracing-Dt9S_6V8.d.ts → tracing-DqbTKGcf.d.ts} +1 -1
  109. package/docs/ARCHITECTURE.md +292 -0
  110. package/docs/README.md +65 -0
  111. package/docs/adr/0001-ai-context-pack.md +32 -0
  112. package/docs/agent-apply-mode.md +104 -0
  113. package/docs/agent-approvals.md +110 -0
  114. package/docs/agent-batch.md +185 -0
  115. package/docs/agent-boundaries.md +112 -0
  116. package/docs/agent-chat-sessions.md +160 -0
  117. package/docs/agent-ci.md +17 -0
  118. package/docs/agent-cli.md +405 -0
  119. package/docs/agent-config.md +480 -0
  120. package/docs/agent-context-discovery.md +159 -0
  121. package/docs/agent-copilot-like-dx.md +126 -0
  122. package/docs/agent-declarative-optimized-planning.md +138 -0
  123. package/docs/agent-dx.md +224 -0
  124. package/docs/agent-env-files.md +126 -0
  125. package/docs/agent-follow-up-context.md +43 -0
  126. package/docs/agent-global-usage.md +180 -0
  127. package/docs/agent-init.md +109 -0
  128. package/docs/agent-install-and-configure.md +516 -0
  129. package/docs/agent-language-workspace-ux.md +99 -0
  130. package/docs/agent-llm-adapters.md +123 -0
  131. package/docs/agent-local-install.md +190 -0
  132. package/docs/agent-local-tests.md +51 -0
  133. package/docs/agent-observability.md +155 -0
  134. package/docs/agent-patch-quality-loop.md +162 -0
  135. package/docs/agent-presets.md +22 -0
  136. package/docs/agent-project-commands.md +237 -0
  137. package/docs/agent-project-intelligence.md +156 -0
  138. package/docs/agent-redaction.md +18 -0
  139. package/docs/agent-release-readiness.md +76 -0
  140. package/docs/agent-rollback-safety.md +162 -0
  141. package/docs/agent-rollback.md +23 -0
  142. package/docs/agent-run-artifacts.md +16 -0
  143. package/docs/agent-vscode-auto-discovery.md +137 -0
  144. package/docs/agent-vscode-batch-runner.md +100 -0
  145. package/docs/agent-vscode-chat-layout.md +90 -0
  146. package/docs/agent-vscode-clean-install.md +147 -0
  147. package/docs/agent-vscode-code-actions.md +70 -0
  148. package/docs/agent-vscode-diff-preview.md +45 -0
  149. package/docs/agent-vscode-inline-assist.md +56 -0
  150. package/docs/agent-vscode-install.md +186 -0
  151. package/docs/agent-vscode-model-setup.md +97 -0
  152. package/docs/agent-vscode-patch-preview.md +92 -0
  153. package/docs/agent-vscode-problems.md +79 -0
  154. package/docs/agent-vscode-project-dashboard.md +106 -0
  155. package/docs/agent-vscode-run-history.md +92 -0
  156. package/docs/agent-vscode-ux.md +73 -0
  157. package/docs/ai/INVARIANTS.md +84 -0
  158. package/docs/ai/PROJECT_MAP.md +338 -0
  159. package/docs/ai/PUBLIC_API.md +339 -0
  160. package/docs/ai/VALIDATION_MATRIX.md +67 -0
  161. package/docs/api-polish.md +37 -0
  162. package/docs/cancellation.md +162 -0
  163. package/docs/coverage.md +46 -0
  164. package/docs/framework-integrations.md +38 -0
  165. package/docs/frameworks/angular.md +153 -0
  166. package/docs/frameworks/express.md +125 -0
  167. package/docs/frameworks/fastify.md +124 -0
  168. package/docs/frameworks/nestjs.md +282 -0
  169. package/docs/frameworks/nextjs.md +147 -0
  170. package/docs/frameworks/react.md +139 -0
  171. package/docs/frameworks/vanilla.md +224 -0
  172. package/docs/getting-started.md +159 -0
  173. package/docs/guides/README.md +40 -0
  174. package/docs/guides/circuit-breaker.md +89 -0
  175. package/docs/guides/error-handling.md +91 -0
  176. package/docs/guides/getting-started.md +107 -0
  177. package/docs/guides/layers.md +189 -0
  178. package/docs/guides/metrics.md +101 -0
  179. package/docs/guides/resource-management.md +141 -0
  180. package/docs/guides/retry.md +215 -0
  181. package/docs/guides/semaphore.md +66 -0
  182. package/docs/guides/streams.md +117 -0
  183. package/docs/guides/supervisors.md +98 -0
  184. package/docs/guides/testing.md +162 -0
  185. package/docs/guides/tracing.md +71 -0
  186. package/docs/http-recipes.md +399 -0
  187. package/docs/http.md +749 -0
  188. package/docs/modules.md +285 -0
  189. package/docs/nestjs.md +6 -0
  190. package/docs/observability-collector-smoke.md +31 -0
  191. package/docs/observability-framework-examples.md +110 -0
  192. package/docs/observability.md +649 -0
  193. package/docs/otel-collector-smoke.yaml +27 -0
  194. package/docs/performance-profiler.md +199 -0
  195. package/docs/production-readiness.md +73 -0
  196. package/docs/recipes/README.md +12 -0
  197. package/docs/recipes/http-server.md +45 -0
  198. package/docs/recipes/layers.md +44 -0
  199. package/docs/recipes/performance.md +47 -0
  200. package/docs/recipes/runtime.md +41 -0
  201. package/docs/recipes/testing.md +41 -0
  202. package/docs/release.md +53 -0
  203. package/docs/wasm-bounded-queues.md +44 -0
  204. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  205. package/docs/wasm-fiber-engine.md +117 -0
  206. package/docs/wasm-scheduler-state-machine.md +122 -0
  207. package/docs/wasm-stream-chunks.md +54 -0
  208. package/package.json +22 -2
  209. package/dist/chunk-45F7OKGT.cjs +0 -104
  210. package/dist/chunk-7V4KY4RL.mjs +0 -104
  211. package/dist/chunk-DJQ7OMMB.cjs +0 -144
  212. package/dist/chunk-GOV47PPB.mjs +0 -552
  213. package/dist/chunk-JF4XXPZ5.cjs +0 -552
  214. package/dist/chunk-KCPT2D6G.js +0 -552
  215. package/dist/chunk-NOYZIMUJ.mjs +0 -144
  216. package/dist/chunk-PNVFW245.js +0 -144
  217. package/dist/chunk-ROJC3NBJ.js +0 -104
  218. package/dist/effectRunner-3ZHAD3LE.cjs +0 -8
  219. package/dist/schedule-Fque9Abz.d.ts +0 -70
@@ -0,0 +1,224 @@
1
+ # Vanilla integration
2
+
3
+ Use this when an app does not have a framework-level DI or lifecycle system.
4
+ The same shape works for plain browser TypeScript, Node HTTP handlers, CLIs,
5
+ and small services.
6
+
7
+ ## Browser
8
+
9
+ Browser apps should send telemetry to a same-origin proxy. Do not ship Grafana
10
+ Cloud or collector credentials to the client.
11
+
12
+ ```ts
13
+ // brass.browser.ts
14
+ import { Runtime } from "brass-runtime/core";
15
+ import {
16
+ defineHttpPolicyPresets,
17
+ makeDefaultHttpClient,
18
+ } from "brass-runtime/http";
19
+ import {
20
+ makeObservability,
21
+ makeOtlpOptions,
22
+ withHttpObservability,
23
+ } from "brass-runtime/observability";
24
+
25
+ const policyPresets = defineHttpPolicyPresets({
26
+ readModel: {
27
+ lane: "read-model",
28
+ priority: 3,
29
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
30
+ },
31
+ command: {
32
+ lane: "command",
33
+ priority: 1,
34
+ retry: false,
35
+ },
36
+ });
37
+
38
+ export const brass = (() => {
39
+ const observability = makeObservability({
40
+ serviceName: "shop-web",
41
+ resource: { "deployment.environment": "browser" },
42
+ logs: false,
43
+ sampling: { ratio: 0.1, respectRemoteSampled: true, forceSampleOnError: true },
44
+ redaction: {},
45
+ cardinality: { maxValuesPerLabel: 100 },
46
+ otlp: makeOtlpOptions({
47
+ endpoint: "/api/otel",
48
+ timeoutMs: 10_000,
49
+ retry: { attempts: 2, initialDelayMs: 100, maxDelayMs: 1_000 },
50
+ pipeline: { maxQueueSize: 2_000, batchSize: 128, dropPolicy: "drop-oldest" },
51
+ }),
52
+ flushIntervalMs: 15_000,
53
+ autoStart: true,
54
+ });
55
+
56
+ const runtime = new Runtime({
57
+ env: observability.env,
58
+ hooks: observability.hooks,
59
+ });
60
+
61
+ const http = makeDefaultHttpClient({
62
+ baseUrl: "/api",
63
+ preset: "balanced",
64
+ timeoutMs: 5_000,
65
+ policyPresets,
66
+ middleware: [withHttpObservability(observability)],
67
+ });
68
+
69
+ return {
70
+ observability,
71
+ runtime,
72
+ http,
73
+ shutdown: async () => {
74
+ await http.shutdown();
75
+ await observability.shutdown();
76
+ },
77
+ };
78
+ })();
79
+ ```
80
+
81
+ Use it from ordinary browser code:
82
+
83
+ ```ts
84
+ type User = {
85
+ readonly id: string;
86
+ readonly name: string;
87
+ };
88
+
89
+ export async function loadCurrentUser(): Promise<User> {
90
+ const response = await brass.runtime.toPromise(
91
+ brass.http.getJson<User>("/users/me", {
92
+ policy: "readModel",
93
+ timeoutMs: 2_000,
94
+ }),
95
+ );
96
+
97
+ return response.body;
98
+ }
99
+
100
+ window.addEventListener("pagehide", () => {
101
+ void brass.shutdown();
102
+ });
103
+ ```
104
+
105
+ ## Node
106
+
107
+ On the server, Brass can send directly to a collector because credentials stay
108
+ inside the trusted process.
109
+
110
+ ```ts
111
+ // brass.node.ts
112
+ import { Runtime } from "brass-runtime/core";
113
+ import {
114
+ defineHttpPolicyPresets,
115
+ makeDefaultHttpClient,
116
+ } from "brass-runtime/http";
117
+ import {
118
+ makeObservability,
119
+ makeOtlpOptions,
120
+ withHttpObservability,
121
+ } from "brass-runtime/observability";
122
+
123
+ const policyPresets = defineHttpPolicyPresets({
124
+ readModel: {
125
+ lane: "read-model",
126
+ priority: 3,
127
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
128
+ },
129
+ command: {
130
+ lane: "command",
131
+ priority: 1,
132
+ retry: false,
133
+ },
134
+ });
135
+
136
+ export const brass = (() => {
137
+ const observability = makeObservability({
138
+ serviceName: process.env.OTEL_SERVICE_NAME ?? "vanilla-node",
139
+ serviceVersion: process.env.OTEL_SERVICE_VERSION,
140
+ resource: {
141
+ "deployment.environment": process.env.NODE_ENV ?? "development",
142
+ },
143
+ logs: { minLevel: "info" },
144
+ sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
145
+ redaction: {},
146
+ cardinality: { maxValuesPerLabel: 100 },
147
+ otlp: makeOtlpOptions({
148
+ endpoint: process.env.GRAFANA_OTLP_ENDPOINT ?? "http://grafana-alloy:4318",
149
+ headers: process.env.GRAFANA_OTLP_AUTHORIZATION
150
+ ? { Authorization: process.env.GRAFANA_OTLP_AUTHORIZATION }
151
+ : undefined,
152
+ timeoutMs: 10_000,
153
+ retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
154
+ pipeline: {
155
+ maxQueueSize: 10_000,
156
+ batchSize: 512,
157
+ dropPolicy: "drop-oldest",
158
+ shutdownTimeoutMs: 10_000,
159
+ },
160
+ }),
161
+ flushIntervalMs: 10_000,
162
+ autoStart: true,
163
+ });
164
+
165
+ const runtime = new Runtime({
166
+ env: observability.env,
167
+ hooks: observability.hooks,
168
+ });
169
+
170
+ const http = makeDefaultHttpClient({
171
+ baseUrl: process.env.API_BASE_URL ?? "https://api.internal",
172
+ preset: "production",
173
+ timeoutMs: 5_000,
174
+ policyPresets,
175
+ middleware: [withHttpObservability(observability)],
176
+ });
177
+
178
+ return {
179
+ observability,
180
+ runtime,
181
+ http,
182
+ shutdown: async () => {
183
+ await http.shutdown();
184
+ await observability.shutdown();
185
+ },
186
+ };
187
+ })();
188
+ ```
189
+
190
+ Use the Node request adapter when you receive `IncomingMessage`-like requests:
191
+
192
+ ```ts
193
+ import { createServer } from "node:http";
194
+ import {
195
+ makeNodeRequestObservabilityContext,
196
+ } from "brass-runtime/observability";
197
+ import { brass } from "./brass.node";
198
+
199
+ const server = createServer(async (req, res) => {
200
+ const ctx = makeNodeRequestObservabilityContext(brass.observability, req, {
201
+ route: req.url?.startsWith("/users/") ? "/users/:id" : req.url,
202
+ });
203
+
204
+ const response = await ctx.run(
205
+ ctx.withRequestSpan(
206
+ brass.http.getJson("/users/42", {
207
+ policy: "readModel",
208
+ timeoutMs: 2_000,
209
+ }),
210
+ ),
211
+ );
212
+
213
+ res.setHeader("content-type", "application/json");
214
+ res.end(JSON.stringify(response.body));
215
+ });
216
+
217
+ process.once("SIGTERM", async () => {
218
+ await new Promise<void>((resolve) => server.close(() => resolve()));
219
+ await brass.shutdown();
220
+ });
221
+
222
+ server.listen(process.env.PORT ?? 3000);
223
+ ```
224
+
@@ -0,0 +1,159 @@
1
+ # 🧵 Brass Runtime — Getting Started
2
+
3
+ `brass-runtime` es un runtime funcional y cooperativo para JavaScript/TypeScript inspirado en modelos como **ZIO**, **Effect** y **structured concurrency**.
4
+
5
+ Su objetivo es permitir escribir lógica **pura, cancelable y composable**, sin perder la posibilidad de ejecutarla fácilmente desde código imperativo.
6
+
7
+ ---
8
+
9
+ ## 📦 Instalación
10
+
11
+ ```bash
12
+ npm install brass-runtime
13
+ ```
14
+
15
+
16
+
17
+ ## 🧠 Conceptos clave
18
+
19
+ ### `Async<R, E, A>`
20
+ Un `Async` representa un **cálculo perezoso** que:
21
+
22
+ - puede requerir un entorno (`R`)
23
+ - puede fallar con un error (`E`)
24
+ - puede producir un valor (`A`)
25
+ - **no se ejecuta automáticamente**
26
+
27
+ Nada corre hasta que vos lo pedís explícitamente.
28
+
29
+ ---
30
+
31
+ ## 🚀 Ejemplo rápido
32
+
33
+ ```ts
34
+ import { makeHttp } from "brass-runtime/http";
35
+ import { toPromise } from "brass-runtime";
36
+
37
+ const http = makeHttp({
38
+ baseUrl: "https://jsonplaceholder.typicode.com",
39
+ });
40
+
41
+ async function main() {
42
+ const effect = http.get("/posts/1");
43
+
44
+ const result = await toPromise(effect, {});
45
+ console.log(result.status);
46
+ console.log(result.bodyText);
47
+ }
48
+
49
+ main();
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 🧩 ¿Por qué `toPromise`?
55
+
56
+ `toPromise` es el **puente entre el mundo funcional y el mundo imperativo**.
57
+
58
+ - `Async` es perezoso → no ejecuta nada por sí solo
59
+ - `toPromise` ejecuta el efecto en el runtime
60
+ - devuelve una `Promise` estándar
61
+
62
+ ```ts
63
+ const result = await toPromise(effect, env);
64
+ ```
65
+
66
+ Internamente:
67
+ - crea un *fiber*
68
+ - lo ejecuta en el scheduler
69
+ - espera el resultado
70
+ - lo transforma en `Promise`
71
+
72
+ ---
73
+
74
+ ## ⚙️ Estructura mental del runtime
75
+
76
+ ```
77
+ Async ---> Fiber ---> Scheduler ---> Resultado
78
+ | | |
79
+ | | +-- controla ejecución
80
+ | +-- maneja estado y cancelación
81
+ +-- describe el cómputo
82
+ ```
83
+
84
+ Nada se ejecuta hasta que:
85
+ ```ts
86
+ toPromise(effect, env)
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 🌐 Ejemplo HTTP completo
92
+
93
+ ```ts
94
+ import { makeHttp } from "brass-runtime/http";
95
+ import { toPromise } from "brass-runtime";
96
+
97
+ const http = makeHttp({
98
+ baseUrl: "https://jsonplaceholder.typicode.com",
99
+ });
100
+
101
+ async function main() {
102
+ const effect = http.get("/posts/1");
103
+
104
+ const result = await toPromise(effect, {});
105
+ console.log("Status:", result.status);
106
+ console.log("Body:", result.bodyText);
107
+ }
108
+
109
+ main();
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 🧩 ¿Por qué no usar `fetch` directamente?
115
+
116
+ Porque `Async` te da:
117
+
118
+ - Cancelación estructurada
119
+ - Composición funcional
120
+ - Control explícito de ejecución
121
+ - Testing determinístico
122
+ - Integración con fibras y scopes
123
+
124
+ ---
125
+
126
+ ## 🧠 Regla de oro
127
+
128
+ > **Los efectos no se ejecutan solos.**
129
+ > Se describen con `Async`, y se ejecutan solo con `toPromise` (o un runner equivalente).
130
+
131
+ ---
132
+
133
+ ## 🧪 Testing
134
+
135
+ ```ts
136
+ import { toPromise } from "brass-runtime";
137
+
138
+ test("fetch works", async () => {
139
+ const result = await toPromise(http.get("/posts/1"), {});
140
+ expect(result.status).toBe(200);
141
+ });
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 🧭 Próximos pasos
147
+
148
+ - Composición (`map`, `flatMap`)
149
+ - Cancelación con `AbortSignal`
150
+ - `race`, `timeout`, `retry`
151
+ - Integración con React / Bun / Workers
152
+
153
+
154
+ ---
155
+
156
+ ## Next
157
+
158
+ - Learn how interruption and `Scope` work: [Cancellation & interruption](./cancellation.md)
159
+ - Enable logging/tracing with hooks: [Observability](./observability.md)
@@ -0,0 +1,40 @@
1
+ # brass-runtime Guides
2
+
3
+ Guides for each feature of brass-runtime, organized by topic.
4
+
5
+ ## Core
6
+
7
+ - [Getting Started](./getting-started.md) — Install, first effect, basic patterns
8
+ - [Effects & Fibers](./effects-and-fibers.md) — The effect system, fibers, and concurrency
9
+ - [Error Handling](./error-handling.md) — Typed errors, catchTag, recovery patterns
10
+ - [Resource Management](./resource-management.md) — bracket, ensuring, managed resources
11
+
12
+ ## Concurrency
13
+
14
+ - [Streams & Pipelines](./streams.md) — ZStream, operators, fusion
15
+ - [Queue & Hub](./queue-and-hub.md) — Bounded queues, pub/sub
16
+ - [Semaphore & Rate Limiting](./semaphore.md) — Concurrency control
17
+ - [Circuit Breaker](./circuit-breaker.md) — Failure protection
18
+ - [Supervisors](./supervisors.md) — Restart and escalation policies for child fibers
19
+
20
+ ## Resilience
21
+
22
+ - [Retry & Backoff](./retry.md) — retry, retryWithBackoff, Schedule
23
+ - [Timeout](./timeout.md) — Effect timeouts with cancellation
24
+
25
+ ## Infrastructure
26
+
27
+ - [Layers (DI)](./layers.md) — Dependency injection with lifecycle
28
+ - [Ref (Shared State)](./ref.md) — Mutable state across fibers
29
+ - [Scheduler](./scheduler.md) — Task scheduling, lanes, budgets
30
+ - [Worker Pool](./worker-pool.md) — CPU-intensive offloading
31
+
32
+ ## Observability
33
+
34
+ - [Tracing](./tracing.md) — OpenTelemetry-compatible spans
35
+ - [Metrics](./metrics.md) — Counters, gauges, histograms
36
+ - [Graceful Shutdown](./shutdown.md) — Clean process termination
37
+
38
+ ## Testing
39
+
40
+ - [Testing Utilities](./testing.md) — TestRuntime, assertions, helpers
@@ -0,0 +1,89 @@
1
+ # Circuit Breaker
2
+
3
+ Protect against cascading failures when downstream services are unhealthy.
4
+
5
+ ## How it works
6
+
7
+ ```
8
+ CLOSED → (failures exceed threshold) → OPEN → (timeout expires) → HALF-OPEN
9
+ ↑ |
10
+ └──────────── (probe succeeds) ──────────────────────────────────────┘
11
+ (probe fails) → back to OPEN
12
+ ```
13
+
14
+ ## Basic usage
15
+
16
+ ```ts
17
+ import { makeCircuitBreaker } from "brass-runtime";
18
+
19
+ const breaker = makeCircuitBreaker({
20
+ failureThreshold: 5, // open after 5 consecutive failures
21
+ resetTimeoutMs: 30_000, // try again after 30 seconds
22
+ });
23
+
24
+ // Protect any effect
25
+ const result = await run(breaker.protect(callPaymentService()));
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ ```ts
31
+ const breaker = makeCircuitBreaker({
32
+ failureThreshold: 3, // trips after 3 failures
33
+ resetTimeoutMs: 10_000, // 10s cooldown
34
+ successThreshold: 2, // need 2 successes in half-open to close
35
+
36
+ // Only count certain errors as failures
37
+ isFailure: (error) => {
38
+ if (error._tag === "NotFound") return false; // 404 is not a failure
39
+ if (error._tag === "BadRequest") return false; // client error, not service
40
+ return true; // everything else counts
41
+ },
42
+
43
+ // Observe state transitions
44
+ onStateChange: (from, to) => {
45
+ metrics.counter("circuit_breaker_transitions", { from, to }).increment();
46
+ },
47
+ });
48
+ ```
49
+
50
+ ## With retry
51
+
52
+ ```ts
53
+ import { makeCircuitBreaker, retryWithBackoff } from "brass-runtime";
54
+
55
+ const breaker = makeCircuitBreaker({ failureThreshold: 5 });
56
+
57
+ // Retry, but stop if circuit opens
58
+ const resilient = retryWithBackoff(
59
+ breaker.protect(callService()),
60
+ {
61
+ maxRetries: 3,
62
+ shouldRetry: (error) => {
63
+ // Don't retry if circuit is open — it'll just fail fast anyway
64
+ if (error._tag === "CircuitBreakerOpen") return false;
65
+ return true;
66
+ },
67
+ }
68
+ );
69
+ ```
70
+
71
+ ## Monitoring
72
+
73
+ ```ts
74
+ const stats = breaker.stats();
75
+ // {
76
+ // state: "closed",
77
+ // failures: 2,
78
+ // successes: 0,
79
+ // totalRequests: 150,
80
+ // totalFailures: 5,
81
+ // totalSuccesses: 143,
82
+ // totalRejected: 2,
83
+ // lastFailureTime: 1699000000000,
84
+ // lastSuccessTime: 1699000001000,
85
+ // }
86
+
87
+ // Manual reset (e.g., after deploying a fix)
88
+ breaker.reset();
89
+ ```
@@ -0,0 +1,91 @@
1
+ # Error Handling
2
+
3
+ brass-runtime provides typed error handling with discriminated unions, enabling exhaustive pattern matching and type-safe recovery.
4
+
5
+ ## Tagged Errors
6
+
7
+ Define errors as discriminated unions:
8
+
9
+ ```ts
10
+ type AppError =
11
+ | { _tag: "NetworkError"; url: string; status: number }
12
+ | { _tag: "TimeoutError"; ms: number }
13
+ | { _tag: "NotFound"; id: string };
14
+ ```
15
+
16
+ ## catchTag — Handle specific errors
17
+
18
+ ```ts
19
+ import { catchTag } from "brass-runtime";
20
+
21
+ const result = catchTag(
22
+ fetchUser(id), // Async<R, AppError, User>
23
+ "NotFound", // catch only NotFound
24
+ (e) => asyncSucceed(defaultUser) // e is narrowed to { _tag: "NotFound"; id: string }
25
+ );
26
+ // Result type: Async<R, NetworkError | TimeoutError, User>
27
+ // NotFound is removed from the error type!
28
+ ```
29
+
30
+ ## catchTags — Handle multiple errors at once
31
+
32
+ ```ts
33
+ import { catchTags } from "brass-runtime";
34
+
35
+ const result = catchTags(fetchUser(id), {
36
+ NotFound: (e) => asyncSucceed(defaultUser),
37
+ TimeoutError: (e) => retryWithBackoff(fetchUser(id)),
38
+ });
39
+ // Only NetworkError remains in the error channel
40
+ ```
41
+
42
+ ## tagError — Wrap untyped errors
43
+
44
+ ```ts
45
+ import { tagError } from "brass-runtime";
46
+
47
+ // Wrap a fetch error with a tag
48
+ const typed = tagError(
49
+ rawFetch(url),
50
+ "NetworkError",
51
+ (e) => ({ url, message: String(e) })
52
+ );
53
+ // Error type: { _tag: "NetworkError"; url: string; message: string }
54
+ ```
55
+
56
+ ## orElse — Fallback on any error
57
+
58
+ ```ts
59
+ import { orElse } from "brass-runtime";
60
+
61
+ const result = orElse(
62
+ primaryDataSource(),
63
+ (error) => fallbackDataSource()
64
+ );
65
+ ```
66
+
67
+ ## mapError — Transform errors
68
+
69
+ ```ts
70
+ import { mapError } from "brass-runtime";
71
+
72
+ const wrapped = mapError(
73
+ effect,
74
+ (e) => ({ _tag: "ServiceError" as const, cause: e })
75
+ );
76
+ ```
77
+
78
+ ## Combining with retry
79
+
80
+ ```ts
81
+ import { catchTag, retryWithBackoff } from "brass-runtime";
82
+
83
+ const resilient = catchTag(
84
+ retryWithBackoff(fetchData(), {
85
+ maxRetries: 3,
86
+ shouldRetry: (e) => e._tag === "NetworkError",
87
+ }),
88
+ "TimeoutError",
89
+ () => asyncSucceed(cachedData)
90
+ );
91
+ ```
@@ -0,0 +1,107 @@
1
+ # Getting Started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install brass-runtime
7
+ ```
8
+
9
+ ## Your first effect
10
+
11
+ ```ts
12
+ import { Runtime, asyncSucceed, asyncFlatMap, asyncFail } from "brass-runtime";
13
+
14
+ // Create a runtime
15
+ const runtime = Runtime.make({});
16
+
17
+ // Effects are values — they describe what to do, not how
18
+ const greet = asyncSucceed("Hello, brass!");
19
+
20
+ // Run the effect
21
+ const result = await runtime.toPromise(greet);
22
+ console.log(result); // "Hello, brass!"
23
+ ```
24
+
25
+ ## Composing effects
26
+
27
+ ```ts
28
+ import { asyncSucceed, asyncFlatMap, asyncFail, asyncFold } from "brass-runtime";
29
+
30
+ // Chain effects with flatMap
31
+ const program = asyncFlatMap(
32
+ asyncSucceed(21),
33
+ (n) => asyncSucceed(n * 2)
34
+ );
35
+ // Result: 42
36
+
37
+ // Handle errors
38
+ const safe = asyncFold(
39
+ asyncFail("oops"),
40
+ (error) => asyncSucceed(`recovered from: ${error}`),
41
+ (value) => asyncSucceed(value)
42
+ );
43
+ ```
44
+
45
+ ## Async operations
46
+
47
+ ```ts
48
+ import { async } from "brass-runtime";
49
+
50
+ // Wrap any callback-based API
51
+ const fetchData = async((_env, cb) => {
52
+ const controller = new AbortController();
53
+
54
+ fetch("https://api.example.com/data", { signal: controller.signal })
55
+ .then(res => res.json())
56
+ .then(data => cb({ _tag: "Success", value: data }))
57
+ .catch(err => cb({ _tag: "Failure", cause: { _tag: "Fail", error: err } }));
58
+
59
+ // Return a canceler
60
+ return () => controller.abort();
61
+ });
62
+ ```
63
+
64
+ ## Concurrency with fibers
65
+
66
+ ```ts
67
+ import { Runtime, asyncSucceed, asyncFlatMap } from "brass-runtime";
68
+
69
+ const runtime = Runtime.make({});
70
+
71
+ // Fork runs an effect in a new fiber (lightweight thread)
72
+ const fiber = runtime.fork(longRunningEffect);
73
+
74
+ // Join waits for the fiber to complete
75
+ fiber.join((exit) => {
76
+ if (exit._tag === "Success") console.log(exit.value);
77
+ else console.log("Failed:", exit.cause);
78
+ });
79
+
80
+ // Interrupt cancels a fiber
81
+ fiber.interrupt();
82
+ ```
83
+
84
+ ## Streams
85
+
86
+ ```ts
87
+ import { fromArray, collectStream, via, mapP, filterP, andThen } from "brass-runtime";
88
+
89
+ // Create a stream from an array
90
+ const numbers = fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
91
+
92
+ // Build a pipeline (auto-fused for performance)
93
+ const pipeline = andThen(
94
+ mapP((x: number) => x * 2),
95
+ filterP((x: number) => x > 10)
96
+ );
97
+
98
+ // Apply and collect
99
+ const result = await runtime.toPromise(collectStream(via(numbers, pipeline)));
100
+ // [12, 14, 16, 18, 20]
101
+ ```
102
+
103
+ ## What's next?
104
+
105
+ - [Effects & Fibers](./effects-and-fibers.md) — Deep dive into the effect system
106
+ - [Error Handling](./error-handling.md) — Typed errors and recovery
107
+ - [Streams & Pipelines](./streams.md) — Stream processing with fusion