brass-runtime 1.16.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 (210) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +283 -18
  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-N6VHMOWB.cjs → chunk-4ROBZFL6.cjs} +128 -128
  16. package/dist/{chunk-NC5SDRYE.js → chunk-52OB2ROS.js} +4 -4
  17. package/dist/{chunk-JX3LZQJH.cjs → chunk-52PPNNI4.cjs} +82 -20
  18. package/dist/{chunk-5YOQOXEQ.cjs → chunk-5EC274J5.cjs} +676 -293
  19. package/dist/chunk-5QC7LRZ3.js +229 -0
  20. package/dist/{chunk-7TL2LHQJ.js → chunk-5VRJNBLZ.mjs} +524 -141
  21. package/dist/chunk-62AZW6UT.cjs +313 -0
  22. package/dist/chunk-6IXXWIUM.js +683 -0
  23. package/dist/chunk-74ZTY6CP.js +2871 -0
  24. package/dist/chunk-76YMRMH2.cjs +777 -0
  25. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  26. package/dist/{chunk-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
  27. package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
  28. package/dist/chunk-AGR5B2BC.cjs +683 -0
  29. package/dist/chunk-AVNQLJ5V.js +777 -0
  30. package/dist/chunk-B33ICAKP.js +313 -0
  31. package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
  32. package/dist/chunk-BABBZK4Y.js +2024 -0
  33. package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
  34. package/dist/{chunk-F5EUMJL7.mjs → chunk-CIZFIMK5.js} +55 -5
  35. package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
  36. package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
  37. package/dist/chunk-DNFO2EIZ.mjs +777 -0
  38. package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
  39. package/dist/{chunk-JNFRRJYH.cjs → chunk-ENKODRU3.cjs} +242 -192
  40. package/dist/chunk-EOC4UHBS.mjs +229 -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-HLWLMW2F.mjs +2024 -0
  46. package/dist/{chunk-TVN5I4U6.cjs → chunk-JF5WGYJJ.cjs} +25 -24
  47. package/dist/{chunk-CY33PGEX.mjs → chunk-KH4SYAOS.mjs} +570 -51
  48. package/dist/chunk-KN32XNTH.mjs +313 -0
  49. package/dist/chunk-KQLYONSE.cjs +2871 -0
  50. package/dist/{chunk-7HUOJA4W.cjs → chunk-KZJQ723N.cjs} +90 -80
  51. package/dist/{chunk-CCKHV5BT.mjs → chunk-L2SYFEBS.js} +5 -4
  52. package/dist/{chunk-IJT6RRQ5.cjs → chunk-L6VB5N7Q.cjs} +20 -9
  53. package/dist/{chunk-ZGLD4TVZ.mjs → chunk-MBEJI5HF.mjs} +4 -4
  54. package/dist/{chunk-PRWCB3QL.mjs → chunk-MIIYDLGM.js} +524 -141
  55. package/dist/{chunk-H55LI6WY.js → chunk-MOO4L7F4.mjs} +15 -4
  56. package/dist/{chunk-7XOPAB5Q.js → chunk-MT3OWDPC.mjs} +55 -5
  57. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  58. package/dist/chunk-PD4EJTQC.cjs +229 -0
  59. package/dist/chunk-PWC3RBQE.mjs +300 -0
  60. package/dist/{chunk-MWXMNYJS.cjs → chunk-Q2I37RP3.cjs} +643 -124
  61. package/dist/{chunk-VFIUZG7J.mjs → chunk-RKGKFN2A.js} +79 -17
  62. package/dist/{chunk-NYL4D7SK.cjs → chunk-SA6HUJVI.cjs} +5 -5
  63. package/dist/{chunk-K2T3DV26.mjs → chunk-TRM4JUZQ.js} +15 -4
  64. package/dist/chunk-UB4B6OFY.js +370 -0
  65. package/dist/{chunk-G3XGCZDQ.js → chunk-UCUBNWM2.js} +1 -1
  66. package/dist/chunk-VN44DYYT.cjs +2024 -0
  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 +16 -531
  89. package/dist/observability/index.d.ts +81 -8
  90. package/dist/observability/index.js +23 -538
  91. package/dist/observability/index.mjs +23 -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-GJPg8ZSG.d.ts} +4 -3
  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 +63 -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 +336 -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/getting-started.md +159 -0
  165. package/docs/guides/README.md +40 -0
  166. package/docs/guides/circuit-breaker.md +89 -0
  167. package/docs/guides/error-handling.md +91 -0
  168. package/docs/guides/getting-started.md +107 -0
  169. package/docs/guides/layers.md +189 -0
  170. package/docs/guides/metrics.md +101 -0
  171. package/docs/guides/resource-management.md +141 -0
  172. package/docs/guides/retry.md +215 -0
  173. package/docs/guides/semaphore.md +66 -0
  174. package/docs/guides/streams.md +117 -0
  175. package/docs/guides/supervisors.md +98 -0
  176. package/docs/guides/testing.md +162 -0
  177. package/docs/guides/tracing.md +71 -0
  178. package/docs/http-recipes.md +399 -0
  179. package/docs/http.md +749 -0
  180. package/docs/modules.md +285 -0
  181. package/docs/observability-collector-smoke.md +31 -0
  182. package/docs/observability-framework-examples.md +98 -0
  183. package/docs/observability.md +542 -0
  184. package/docs/otel-collector-smoke.yaml +27 -0
  185. package/docs/performance-profiler.md +199 -0
  186. package/docs/production-readiness.md +73 -0
  187. package/docs/recipes/README.md +12 -0
  188. package/docs/recipes/http-server.md +45 -0
  189. package/docs/recipes/layers.md +44 -0
  190. package/docs/recipes/performance.md +47 -0
  191. package/docs/recipes/runtime.md +41 -0
  192. package/docs/recipes/testing.md +41 -0
  193. package/docs/release.md +53 -0
  194. package/docs/wasm-bounded-queues.md +44 -0
  195. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  196. package/docs/wasm-fiber-engine.md +117 -0
  197. package/docs/wasm-scheduler-state-machine.md +122 -0
  198. package/docs/wasm-stream-chunks.md +54 -0
  199. package/package.json +22 -2
  200. package/dist/chunk-45F7OKGT.cjs +0 -104
  201. package/dist/chunk-7V4KY4RL.mjs +0 -104
  202. package/dist/chunk-DJQ7OMMB.cjs +0 -144
  203. package/dist/chunk-GOV47PPB.mjs +0 -552
  204. package/dist/chunk-JF4XXPZ5.cjs +0 -552
  205. package/dist/chunk-KCPT2D6G.js +0 -552
  206. package/dist/chunk-NOYZIMUJ.mjs +0 -144
  207. package/dist/chunk-PNVFW245.js +0 -144
  208. package/dist/chunk-ROJC3NBJ.js +0 -104
  209. package/dist/effectRunner-3ZHAD3LE.cjs +0 -8
  210. package/dist/schedule-Fque9Abz.d.ts +0 -70
@@ -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
@@ -0,0 +1,189 @@
1
+ # Layers (Dependency Injection)
2
+
3
+ Layers describe how to build services, wire dependencies, and release resources.
4
+ They are lazy values: acquisition happens only when a runtime evaluates
5
+ `provideLayer`, `provideLayerContext`, `buildLayer`, or the `Layer.*` aliases.
6
+
7
+ Layer 2.0 adds three pieces on top of the original API:
8
+
9
+ - typed `ServiceTag<A>` keys
10
+ - immutable `LayerContext` service maps
11
+ - scoped builds with memoization and idempotent finalizers
12
+
13
+ ## Typed Contexts
14
+
15
+ Use tags when a layer graph needs more than one service or when a service should
16
+ be retrieved by capability instead of object shape.
17
+
18
+ ```ts
19
+ import { type Async, Layer, Runtime, asyncSucceed, asyncSync } from "brass-runtime";
20
+
21
+ const finalizer = (run: () => void): Async<unknown, never, void> =>
22
+ asyncSync(() => run()) as Async<unknown, never, void>;
23
+
24
+ type Config = { readonly dbUrl: string };
25
+ type Db = { readonly query: (sql: string) => string };
26
+
27
+ const Config = Layer.tag<Config>("Config");
28
+ const Db = Layer.tag<Db>("Db");
29
+
30
+ const ConfigLayer = Layer.value(Config, { dbUrl: "postgres://local" });
31
+
32
+ const DbLayer = Layer.effect(
33
+ Db,
34
+ (ctx) => {
35
+ const config = ctx.unsafeGet(Config);
36
+ return asyncSucceed({
37
+ query: (sql) => `${sql} on ${config.dbUrl}`,
38
+ });
39
+ },
40
+ (db) => finalizer(() => {
41
+ db.query("close");
42
+ }),
43
+ );
44
+
45
+ const AppLayer = Layer.compose(ConfigLayer, DbLayer);
46
+
47
+ const runtime = Runtime.make({});
48
+ const result = await runtime.toPromise(
49
+ Layer.provideContext(AppLayer, (ctx) =>
50
+ asyncSucceed(ctx.unsafeGet(Db).query("select 1")),
51
+ ),
52
+ );
53
+ ```
54
+
55
+ `LayerContext` is immutable. `add` and `merge` return new contexts, and
56
+ right-hand services win when two contexts contain the same tag.
57
+
58
+ ## Plain Services
59
+
60
+ The original layer API is still supported for simple service shapes.
61
+
62
+ ```ts
63
+ import { layer, layerFrom, composeLayer, provideLayer, asyncSucceed, asyncSync } from "brass-runtime";
64
+
65
+ type Config = { readonly dbUrl: string };
66
+ type Db = { readonly query: (sql: string) => string };
67
+
68
+ const ConfigLayer = layer(() => asyncSucceed<Config>({
69
+ dbUrl: "postgres://local",
70
+ }));
71
+
72
+ const DbLayer = layerFrom<Config>()(
73
+ (config) => asyncSucceed<Db>({
74
+ query: (sql) => `${sql} on ${config.dbUrl}`,
75
+ }),
76
+ () => asyncSync(() => {
77
+ // close the connection pool here
78
+ }),
79
+ );
80
+
81
+ const AppLayer = composeLayer(ConfigLayer, DbLayer);
82
+
83
+ await runtime.toPromise(
84
+ provideLayer(AppLayer, (db) => asyncSucceed(db.query("select 1"))),
85
+ );
86
+ ```
87
+
88
+ ## Merging
89
+
90
+ `mergeLayer` combines independent layers. Plain object services are merged with
91
+ object spread; `LayerContext` services are merged by tag.
92
+
93
+ ```ts
94
+ import { Layer, mergeLayer, asyncSucceed } from "brass-runtime";
95
+
96
+ const Db = Layer.tag<{ readonly query: (sql: string) => string }>("Db");
97
+ const Cache = Layer.tag<{ readonly get: (key: string) => string | undefined }>("Cache");
98
+
99
+ const DbLayer = Layer.effect(Db, () => asyncSucceed({ query: (sql) => sql }));
100
+ const CacheLayer = Layer.effect(Cache, () => asyncSucceed({ get: () => undefined }));
101
+
102
+ const InfraLayer = mergeLayer(DbLayer, CacheLayer);
103
+
104
+ await runtime.toPromise(
105
+ Layer.provideContext(InfraLayer, (ctx) =>
106
+ asyncSucceed({
107
+ db: ctx.unsafeGet(Db),
108
+ cache: ctx.unsafeGet(Cache),
109
+ }),
110
+ ),
111
+ );
112
+ ```
113
+
114
+ ## Scoped Builds
115
+
116
+ Use `buildLayer` or `Layer.build` when a caller wants manual lifecycle control.
117
+ The returned scope memoizes shared layer instances during a build, releases in
118
+ reverse acquisition order, and makes `close()` idempotent.
119
+
120
+ ```ts
121
+ import { Layer, asyncSucceed } from "brass-runtime";
122
+
123
+ const built = await runtime.toPromise(Layer.build(InfraLayer));
124
+
125
+ try {
126
+ await runtime.toPromise(
127
+ built.use((ctx) => asyncSucceed(ctx.unsafeGet(Db).query("select 1"))),
128
+ );
129
+ } finally {
130
+ await runtime.toPromise(built.close());
131
+ }
132
+ ```
133
+
134
+ For advanced graph assembly, create an explicit scope:
135
+
136
+ ```ts
137
+ const scope = Layer.scope();
138
+
139
+ const db = await runtime.toPromise(scope.get(DbLayer));
140
+ const sameDb = await runtime.toPromise(scope.get(DbLayer));
141
+
142
+ db === sameDb; // true for the same layer object within the same scope
143
+
144
+ await runtime.toPromise(scope.close());
145
+ ```
146
+
147
+ After a scope is closed, further `scope.get(...)` calls fail.
148
+
149
+ ## Patterns
150
+
151
+ ### Singleton Service
152
+
153
+ ```ts
154
+ const Logger = Layer.tag<Console>("Logger");
155
+ const LoggerLayer = Layer.value(Logger, console);
156
+ ```
157
+
158
+ ### Service With Health Check
159
+
160
+ ```ts
161
+ const DbLayer = Layer.effect(
162
+ Db,
163
+ () => asyncSync(() => {
164
+ const pool = createPool();
165
+ pool.query("select 1");
166
+ return pool;
167
+ }),
168
+ (pool) => asyncSync(() => {
169
+ pool.close();
170
+ }),
171
+ );
172
+ ```
173
+
174
+ ### Test Doubles
175
+
176
+ ```ts
177
+ const RealDbLayer = Layer.effect(Db, () => asyncSucceed(createPool()));
178
+
179
+ const MockDbLayer = Layer.value(Db, {
180
+ query: (sql: string) => `mock:${sql}`,
181
+ });
182
+
183
+ const result = await runtime.toPromise(
184
+ Layer.provideContext(
185
+ process.env.NODE_ENV === "test" ? MockDbLayer : RealDbLayer,
186
+ (ctx) => asyncSucceed(ctx.unsafeGet(Db).query("select *")),
187
+ ),
188
+ );
189
+ ```