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.
- package/CHANGELOG.md +17 -0
- package/README.md +673 -136
- package/dist/agent/cli/main.cjs +40 -35
- package/dist/agent/cli/main.js +9 -4
- package/dist/agent/cli/main.mjs +9 -4
- package/dist/agent/index.cjs +8 -4
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +7 -3
- package/dist/agent/index.mjs +7 -3
- package/dist/chunk-2HQTDLHF.mjs +683 -0
- package/dist/chunk-36I3M4UC.mjs +370 -0
- package/dist/chunk-3AYM6WPJ.js +1629 -0
- package/dist/chunk-3LOYJFRR.cjs +300 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3Y2RIUMM.js +300 -0
- package/dist/{chunk-VEZNF5GZ.cjs → chunk-4ROBZFL6.cjs} +130 -126
- package/dist/{chunk-3QMOKAS5.js → chunk-52OB2ROS.js} +9 -5
- package/dist/chunk-52PPNNI4.cjs +416 -0
- package/dist/chunk-5EC274J5.cjs +2874 -0
- package/dist/chunk-5QC7LRZ3.js +229 -0
- package/dist/chunk-5VRJNBLZ.mjs +2874 -0
- package/dist/chunk-62AZW6UT.cjs +313 -0
- package/dist/chunk-6IXXWIUM.js +683 -0
- package/dist/chunk-74ZTY6CP.js +2871 -0
- package/dist/chunk-76YMRMH2.cjs +777 -0
- package/dist/chunk-7CMJS3QE.mjs +2871 -0
- package/dist/{chunk-4NHES7VK.mjs → chunk-7JIJOVCT.js} +27 -13
- package/dist/chunk-A2OM6NEH.mjs +194 -0
- package/dist/chunk-AGR5B2BC.cjs +683 -0
- package/dist/chunk-AVNQLJ5V.js +777 -0
- package/dist/chunk-B33ICAKP.js +313 -0
- package/dist/{chunk-ELOOF35R.mjs → chunk-B5JD23U7.mjs} +1 -1
- package/dist/chunk-BABBZK4Y.js +2024 -0
- package/dist/chunk-C3MDXTRZ.js +354 -0
- package/dist/chunk-CIZFIMK5.js +2193 -0
- package/dist/chunk-CZIVE6NT.cjs +354 -0
- package/dist/chunk-DNFJLJMW.mjs +354 -0
- package/dist/chunk-DNFO2EIZ.mjs +777 -0
- package/dist/chunk-EJ6BPYVR.mjs +416 -0
- package/dist/chunk-ENKODRU3.cjs +2193 -0
- package/dist/chunk-EOC4UHBS.mjs +229 -0
- package/dist/{chunk-BMH5AV44.js → chunk-FH2X7BVP.js} +756 -440
- package/dist/{chunk-PPUXIH5R.js → chunk-FHQGHPMO.mjs} +27 -13
- package/dist/{chunk-TGIFUAK4.cjs → chunk-GLE2WY7Z.cjs} +951 -635
- package/dist/{chunk-BDF4AMWX.mjs → chunk-GYM3LLGS.mjs} +756 -440
- package/dist/chunk-HLWLMW2F.mjs +2024 -0
- package/dist/chunk-JF5WGYJJ.cjs +194 -0
- package/dist/chunk-KH4SYAOS.mjs +1629 -0
- package/dist/chunk-KN32XNTH.mjs +313 -0
- package/dist/chunk-KQLYONSE.cjs +2871 -0
- package/dist/{chunk-STVLQ3XD.cjs → chunk-KZJQ723N.cjs} +92 -78
- package/dist/chunk-L2SYFEBS.js +194 -0
- package/dist/chunk-L6VB5N7Q.cjs +104 -0
- package/dist/{chunk-K6M7MDZ4.mjs → chunk-MBEJI5HF.mjs} +9 -5
- package/dist/chunk-MIIYDLGM.js +2874 -0
- package/dist/chunk-MOO4L7F4.mjs +104 -0
- package/dist/chunk-MT3OWDPC.mjs +2193 -0
- package/dist/chunk-MVGUEJ5Z.cjs +370 -0
- package/dist/chunk-OBGZSXTJ.cjs +10 -0
- package/dist/chunk-PD4EJTQC.cjs +229 -0
- package/dist/chunk-PWC3RBQE.mjs +300 -0
- package/dist/chunk-Q2I37RP3.cjs +1629 -0
- package/dist/chunk-RKGKFN2A.js +416 -0
- package/dist/{chunk-R3R2FVLG.cjs → chunk-SA6HUJVI.cjs} +5 -5
- package/dist/chunk-TRM4JUZQ.js +104 -0
- package/dist/chunk-UB4B6OFY.js +370 -0
- package/dist/{chunk-TO7IKXYT.js → chunk-UCUBNWM2.js} +1 -1
- package/dist/chunk-VN44DYYT.cjs +2024 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/client-CZHU674n.d.ts +820 -0
- package/dist/core/index.cjs +198 -4
- package/dist/core/index.d.ts +311 -212
- package/dist/core/index.js +237 -43
- package/dist/core/index.mjs +237 -43
- package/dist/{effect-CMOQKX8y.d.ts → effect-DIUHZ9IN.d.ts} +195 -1
- package/dist/effectRunner-CFLC32IK.cjs +8 -0
- package/dist/effectRunner-L4S7IPT3.js +8 -0
- package/dist/effectRunner-NNGG75QA.mjs +8 -0
- package/dist/http/index.cjs +1227 -2971
- package/dist/http/index.d.ts +826 -280
- package/dist/http/index.js +1089 -2833
- package/dist/http/index.mjs +1089 -2833
- package/dist/http/testing.cjs +161 -0
- package/dist/http/testing.d.ts +43 -0
- package/dist/http/testing.js +161 -0
- package/dist/http/testing.mjs +161 -0
- package/dist/index.cjs +486 -250
- package/dist/index.d.ts +87 -95
- package/dist/index.js +391 -155
- package/dist/index.mjs +391 -155
- package/dist/observability/index.cjs +162 -0
- package/dist/observability/index.d.ts +152 -0
- package/dist/observability/index.js +162 -0
- package/dist/observability/index.mjs +162 -0
- package/dist/perf/cli.cjs +401 -0
- package/dist/perf/cli.d.ts +1 -0
- package/dist/perf/cli.js +401 -0
- package/dist/perf/cli.mjs +401 -0
- package/dist/perf/index.cjs +141 -0
- package/dist/perf/index.d.ts +483 -0
- package/dist/perf/index.js +141 -0
- package/dist/perf/index.mjs +141 -0
- package/dist/schedule-CK3Ml_7p.d.ts +259 -0
- package/dist/schema/index.cjs +29 -0
- package/dist/schema/index.d.ts +179 -0
- package/dist/schema/index.js +29 -0
- package/dist/schema/index.mjs +29 -0
- package/dist/server-GJPg8ZSG.d.ts +675 -0
- package/dist/{stream-FQm9h4Mg.d.ts → stream-B4oK9JFP.d.ts} +1 -1
- package/dist/tracer-Hwt1cl7h.d.ts +189 -0
- package/dist/tracing-DqbTKGcf.d.ts +148 -0
- package/docs/ARCHITECTURE.md +292 -0
- package/docs/README.md +63 -0
- package/docs/adr/0001-ai-context-pack.md +32 -0
- package/docs/agent-apply-mode.md +104 -0
- package/docs/agent-approvals.md +110 -0
- package/docs/agent-batch.md +185 -0
- package/docs/agent-boundaries.md +112 -0
- package/docs/agent-chat-sessions.md +160 -0
- package/docs/agent-ci.md +17 -0
- package/docs/agent-cli.md +405 -0
- package/docs/agent-config.md +480 -0
- package/docs/agent-context-discovery.md +159 -0
- package/docs/agent-copilot-like-dx.md +126 -0
- package/docs/agent-declarative-optimized-planning.md +138 -0
- package/docs/agent-dx.md +224 -0
- package/docs/agent-env-files.md +126 -0
- package/docs/agent-follow-up-context.md +43 -0
- package/docs/agent-global-usage.md +180 -0
- package/docs/agent-init.md +109 -0
- package/docs/agent-install-and-configure.md +516 -0
- package/docs/agent-language-workspace-ux.md +99 -0
- package/docs/agent-llm-adapters.md +123 -0
- package/docs/agent-local-install.md +190 -0
- package/docs/agent-local-tests.md +51 -0
- package/docs/agent-observability.md +155 -0
- package/docs/agent-patch-quality-loop.md +162 -0
- package/docs/agent-presets.md +22 -0
- package/docs/agent-project-commands.md +237 -0
- package/docs/agent-project-intelligence.md +156 -0
- package/docs/agent-redaction.md +18 -0
- package/docs/agent-release-readiness.md +76 -0
- package/docs/agent-rollback-safety.md +162 -0
- package/docs/agent-rollback.md +23 -0
- package/docs/agent-run-artifacts.md +16 -0
- package/docs/agent-vscode-auto-discovery.md +137 -0
- package/docs/agent-vscode-batch-runner.md +100 -0
- package/docs/agent-vscode-chat-layout.md +90 -0
- package/docs/agent-vscode-clean-install.md +147 -0
- package/docs/agent-vscode-code-actions.md +70 -0
- package/docs/agent-vscode-diff-preview.md +45 -0
- package/docs/agent-vscode-inline-assist.md +56 -0
- package/docs/agent-vscode-install.md +186 -0
- package/docs/agent-vscode-model-setup.md +97 -0
- package/docs/agent-vscode-patch-preview.md +92 -0
- package/docs/agent-vscode-problems.md +79 -0
- package/docs/agent-vscode-project-dashboard.md +106 -0
- package/docs/agent-vscode-run-history.md +92 -0
- package/docs/agent-vscode-ux.md +73 -0
- package/docs/ai/INVARIANTS.md +84 -0
- package/docs/ai/PROJECT_MAP.md +338 -0
- package/docs/ai/PUBLIC_API.md +336 -0
- package/docs/ai/VALIDATION_MATRIX.md +67 -0
- package/docs/api-polish.md +37 -0
- package/docs/cancellation.md +162 -0
- package/docs/coverage.md +46 -0
- package/docs/getting-started.md +159 -0
- package/docs/guides/README.md +40 -0
- package/docs/guides/circuit-breaker.md +89 -0
- package/docs/guides/error-handling.md +91 -0
- package/docs/guides/getting-started.md +107 -0
- package/docs/guides/layers.md +189 -0
- package/docs/guides/metrics.md +101 -0
- package/docs/guides/resource-management.md +141 -0
- package/docs/guides/retry.md +215 -0
- package/docs/guides/semaphore.md +66 -0
- package/docs/guides/streams.md +117 -0
- package/docs/guides/supervisors.md +98 -0
- package/docs/guides/testing.md +162 -0
- package/docs/guides/tracing.md +71 -0
- package/docs/http-recipes.md +399 -0
- package/docs/http.md +749 -0
- package/docs/modules.md +285 -0
- package/docs/observability-collector-smoke.md +31 -0
- package/docs/observability-framework-examples.md +98 -0
- package/docs/observability.md +542 -0
- package/docs/otel-collector-smoke.yaml +27 -0
- package/docs/performance-profiler.md +199 -0
- package/docs/production-readiness.md +73 -0
- package/docs/recipes/README.md +12 -0
- package/docs/recipes/http-server.md +45 -0
- package/docs/recipes/layers.md +44 -0
- package/docs/recipes/performance.md +47 -0
- package/docs/recipes/runtime.md +41 -0
- package/docs/recipes/testing.md +41 -0
- package/docs/release.md +53 -0
- package/docs/wasm-bounded-queues.md +44 -0
- package/docs/wasm-engine-observability-benchmarks.md +85 -0
- package/docs/wasm-fiber-engine.md +117 -0
- package/docs/wasm-scheduler-state-machine.md +122 -0
- package/docs/wasm-stream-chunks.md +54 -0
- package/package.json +48 -2
- package/dist/chunk-AR22SXML.js +0 -1043
- package/dist/chunk-BDYEENHT.js +0 -224
- package/dist/chunk-JFPU5GQI.mjs +0 -1043
- package/dist/chunk-MS34J5LY.cjs +0 -224
- package/dist/chunk-UMAZLXAB.mjs +0 -224
- package/dist/chunk-XPZNXSVN.cjs +0 -1043
- package/dist/tracing-DNT9jEbr.d.ts +0 -106
package/README.md
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# brass-runtime
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A ZIO-inspired effect runtime for TypeScript with structured concurrency,
|
|
4
|
+
runtime diagnostics, pull-based streams, and a production-grade HTTP client.
|
|
4
5
|
|
|
5
|
-
`
|
|
6
|
-
Higher-level modules (HTTP, streaming utilities, integrations) are built **on top of the runtime**, not baked into it.
|
|
6
|
+
Built without `Promise`/`async`/`await` as the primary semantic primitive. Effects are values — lazy, composable, and cancelable by default.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
```bash
|
|
9
|
+
npm i brass-runtime
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What it does
|
|
15
|
+
|
|
16
|
+
**Runtime** — algebraic effects, fibers, scopes, scheduler, interruptibility
|
|
17
|
+
regions, fiber-local refs, typed layers, semaphores, circuit breakers, rich
|
|
18
|
+
`Cause<E>` failures, metrics, tracing, and an opt-in flight recorder.
|
|
19
|
+
|
|
20
|
+
**Streams** — pull-based streams with backpressure, bounded buffers, hubs,
|
|
21
|
+
pipelines, fusion optimization, and a small fluent DX facade.
|
|
22
|
+
|
|
23
|
+
**HTTP** — lazy/cancelable client and server primitives with typed routing,
|
|
24
|
+
schema validation, health/readiness probes, adaptive concurrency, compression,
|
|
25
|
+
batching, prewarm, cache, dedup, priority, retry, and observability.
|
|
26
|
+
|
|
27
|
+
**Production signals** — dependency-free schemas, Prometheus/OTLP exporters,
|
|
28
|
+
structured logs, W3C trace propagation, sampling, redaction, bounded exporters,
|
|
29
|
+
and explicit flush/shutdown.
|
|
30
|
+
|
|
31
|
+
**Performance profiler** — runtime primitives, HTTP layer comparison, memory
|
|
32
|
+
retention reports, observability overhead, CLI/JSON output, and actionable
|
|
33
|
+
recommendations.
|
|
34
|
+
|
|
35
|
+
**Optional engine and tools** — Rust/WASM-backed state machines plus the
|
|
36
|
+
experimental Brass Agent CLI/VS Code workflow.
|
|
9
37
|
|
|
10
38
|
---
|
|
11
39
|
|
|
@@ -15,223 +43,732 @@ Higher-level modules (HTTP, streaming utilities, integrations) are built **on to
|
|
|
15
43
|
- **Async is explicit** — no hidden Promise semantics
|
|
16
44
|
- **Concurrency is structured** — fibers, scopes, finalizers
|
|
17
45
|
- **Side effects are interpreted** — not executed eagerly
|
|
18
|
-
- **Higher-level APIs are libraries,
|
|
19
|
-
|
|
20
|
-
If you like ZIO’s separation between `zio-core`, `zio-streams`, and `zio-http`, this project follows the same spirit.
|
|
46
|
+
- **Higher-level APIs are libraries** — HTTP, streams, agent are built on top of core
|
|
21
47
|
|
|
22
48
|
---
|
|
23
49
|
|
|
24
|
-
##
|
|
50
|
+
## Quick start
|
|
25
51
|
|
|
26
|
-
|
|
27
|
-
- Algebraic async representation: `Async<R, E, A>`
|
|
28
|
-
- Cooperative `Scheduler` (observable / testable)
|
|
29
|
-
- Lightweight `Fiber`s with interruption & finalizers
|
|
30
|
-
- Structured `Scope`s for resource safety
|
|
31
|
-
- ZStream-style streams with backpressure
|
|
52
|
+
### Run an effect
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
```ts
|
|
55
|
+
import { runPromise, succeed } from "brass-runtime";
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
const value = await runPromise(succeed(42));
|
|
58
|
+
```
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
Use `makeRuntime` when you want explicit runtime options, and `runExit` when
|
|
61
|
+
you want the full `Exit`/`Cause` instead of a rejected promise.
|
|
39
62
|
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
### Inspect failure causes
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { Cause, formatCause } from "brass-runtime";
|
|
67
|
+
|
|
68
|
+
const cause = Cause.then(
|
|
69
|
+
Cause.fail("database unavailable"),
|
|
70
|
+
Cause.both(Cause.interrupt(), Cause.die(new Error("release failed"))),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
console.log(formatCause(cause));
|
|
42
74
|
```
|
|
43
75
|
|
|
44
|
-
|
|
76
|
+
`Cause<E>` preserves typed failures, defects, interruptions, and sequential or
|
|
77
|
+
parallel composition (`Then` / `Both`) so diagnostics can explain what happened
|
|
78
|
+
without flattening every failure into a single thrown value.
|
|
45
79
|
|
|
46
|
-
|
|
80
|
+
### Mask interruption
|
|
47
81
|
|
|
48
|
-
|
|
82
|
+
```ts
|
|
83
|
+
import { async, flatMap, succeed, uninterruptibleMask } from "brass-runtime";
|
|
84
|
+
|
|
85
|
+
const effect = uninterruptibleMask((restore) =>
|
|
86
|
+
flatMap(succeed("acquired"), (resource) =>
|
|
87
|
+
restore(async((_env, cb) => {
|
|
88
|
+
setTimeout(() => cb({ _tag: "Success", value: `used:${resource}` }), 10);
|
|
89
|
+
})),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `uninterruptible(effect)` for critical regions and
|
|
95
|
+
`uninterruptibleMask((restore) => ...)` when only part of the region should be
|
|
96
|
+
interruptible again. Pending interruption is deferred until the protected region
|
|
97
|
+
exits; restored sub-effects remain cancelable.
|
|
98
|
+
|
|
99
|
+
### Fiber-local context
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { Runtime, makeFiberRef } from "brass-runtime";
|
|
103
|
+
|
|
104
|
+
const requestId = makeFiberRef("anonymous");
|
|
105
|
+
const runtime = Runtime.make({});
|
|
106
|
+
|
|
107
|
+
const result = await runtime.toPromise(
|
|
108
|
+
requestId.locally("req-123", requestId.get()),
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`FiberRef` values are local to the running fiber, inherited by child fibers at
|
|
113
|
+
fork time, isolated from child mutations, and restored after `locally` regions
|
|
114
|
+
even when the region fails or is interrupted.
|
|
115
|
+
|
|
116
|
+
### Explain runtime behavior
|
|
49
117
|
|
|
50
118
|
```ts
|
|
51
|
-
import { Runtime,
|
|
119
|
+
import { Runtime, async, makeRuntimeRecorder } from "brass-runtime";
|
|
120
|
+
|
|
121
|
+
const recorder = makeRuntimeRecorder({ maxEvents: 5000 });
|
|
122
|
+
const runtime = new Runtime({ env: {}, hooks: recorder.hooks });
|
|
52
123
|
|
|
53
|
-
|
|
124
|
+
await runtime.toPromise(async((_env, cb) => {
|
|
125
|
+
setTimeout(() => cb({ _tag: "Success", value: "ok" }), 10);
|
|
126
|
+
}));
|
|
54
127
|
|
|
55
|
-
|
|
56
|
-
console.log(value); // 123
|
|
128
|
+
console.log(recorder.explain());
|
|
57
129
|
```
|
|
58
130
|
|
|
59
|
-
|
|
131
|
+
The flight recorder is opt-in and keeps a bounded ring buffer of runtime events:
|
|
132
|
+
fiber start/end/suspend/resume, scopes, supervisor events, logs, spans, and
|
|
133
|
+
trace context when available.
|
|
134
|
+
|
|
135
|
+
### Recommended HTTP client
|
|
60
136
|
|
|
61
137
|
```ts
|
|
62
|
-
import {
|
|
138
|
+
import { makeDefaultHttpClient, s } from "brass-runtime/http";
|
|
63
139
|
|
|
64
|
-
const
|
|
140
|
+
const User = s.object({
|
|
141
|
+
id: s.number({ int: true }),
|
|
142
|
+
name: s.string({ minLength: 1 }),
|
|
143
|
+
role: s.enum(["admin", "user"] as const).optional(),
|
|
144
|
+
});
|
|
65
145
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
scope.close(); // interrupts child fibers + runs finalizers
|
|
146
|
+
const http = makeDefaultHttpClient({
|
|
147
|
+
baseUrl: "https://api.example.com",
|
|
148
|
+
headers: { accept: "application/json" },
|
|
70
149
|
});
|
|
150
|
+
|
|
151
|
+
const user = await http.getJson("/users/1", { schema: User }).unsafeRunPromise();
|
|
152
|
+
|
|
153
|
+
console.log(user.body.name);
|
|
154
|
+
console.log(http.stats());
|
|
155
|
+
console.log(http.compression?.stats());
|
|
71
156
|
```
|
|
72
157
|
|
|
73
|
-
|
|
158
|
+
`makeDefaultHttpClient` is the batteries-included entrypoint: timeout,
|
|
159
|
+
deduplication, priority scheduling, retry, adaptive concurrency, safe-method
|
|
160
|
+
response cache, decompression, stats, `cancelAll`, and JSON/text helpers. Use
|
|
161
|
+
`preset: "production"` when you want that production-ready shape explicitly,
|
|
162
|
+
`preset: "balanced"` to skip the default cache, or `preset: "minimal"` for a
|
|
163
|
+
cheap wire client with the same helper API. `preset: "default"` remains the
|
|
164
|
+
same full preset for compatibility.
|
|
74
165
|
|
|
75
|
-
|
|
166
|
+
The HTTP stack is meant to replace the usual `fetch` wrapper plus Zod/Valibot
|
|
167
|
+
glue: schemas are dependency-free, responses and request bodies are validated in
|
|
168
|
+
the same effect, config validation fails at construction time, and the client
|
|
169
|
+
still owns cancellation, retries, compression, observability, and adaptive
|
|
170
|
+
limits as one pipeline.
|
|
76
171
|
|
|
77
|
-
|
|
172
|
+
Custom Promise clients such as Axios can be injected without making the
|
|
173
|
+
consumer manage `AbortSignal` or `Async` plumbing:
|
|
78
174
|
|
|
79
|
-
|
|
175
|
+
```ts
|
|
176
|
+
import {
|
|
177
|
+
defineHttpPolicyPresets,
|
|
178
|
+
formatHttpError,
|
|
179
|
+
isRetryableHttpError,
|
|
180
|
+
makeDefaultHttpClient,
|
|
181
|
+
promiseHttpTransport,
|
|
182
|
+
} from "brass-runtime/http";
|
|
183
|
+
|
|
184
|
+
const transport = promiseHttpTransport()
|
|
185
|
+
.requestConfig(({ request, url }) => ({
|
|
186
|
+
url: url.toString(),
|
|
187
|
+
method: request.method,
|
|
188
|
+
headers: request.headers,
|
|
189
|
+
data: request.body,
|
|
190
|
+
responseType: "json",
|
|
191
|
+
}))
|
|
192
|
+
.send((config) => axiosInstance.request(config))
|
|
193
|
+
.json();
|
|
194
|
+
|
|
195
|
+
const axiosHttp = makeDefaultHttpClient({
|
|
196
|
+
baseUrl: "https://api.example.com",
|
|
197
|
+
transport,
|
|
198
|
+
});
|
|
80
199
|
|
|
81
|
-
|
|
200
|
+
try {
|
|
201
|
+
await axiosHttp.getJson("/users/1").unsafeRunPromise();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (isRetryableHttpError(error)) {
|
|
204
|
+
console.warn("transient upstream failure");
|
|
205
|
+
}
|
|
206
|
+
console.error(formatHttpError(error));
|
|
207
|
+
}
|
|
208
|
+
```
|
|
82
209
|
|
|
83
|
-
|
|
210
|
+
Brass injects the runtime `AbortSignal` into object configs before `send` and
|
|
211
|
+
normalizes external failures with `toHttpError`, including Axios-like
|
|
212
|
+
`response.status`, aborts, and common timeout codes.
|
|
84
213
|
|
|
85
|
-
|
|
86
|
-
- Explicit wire/content separation
|
|
87
|
-
- Middleware-friendly (logging, retry, timeout, etc.)
|
|
88
|
-
- Integrated with fiber interruption via `AbortController`
|
|
214
|
+
Repeated execution intent can be named once with policy presets:
|
|
89
215
|
|
|
90
|
-
|
|
216
|
+
```ts
|
|
217
|
+
const policies = defineHttpPolicyPresets({
|
|
218
|
+
readModel: {
|
|
219
|
+
lane: "read-model",
|
|
220
|
+
poolKey: "users-api",
|
|
221
|
+
priority: 2,
|
|
222
|
+
retry: { maxRetries: 2, baseDelayMs: 50 },
|
|
223
|
+
},
|
|
224
|
+
});
|
|
91
225
|
|
|
92
|
-
|
|
226
|
+
const http = makeDefaultHttpClient({
|
|
227
|
+
baseUrl: "https://api.example.com",
|
|
228
|
+
policyPresets: policies,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await http.getJson("/users/1", {
|
|
232
|
+
policy: { preset: "readModel", dedupKey: "users:1" },
|
|
233
|
+
}).unsafeRunPromise();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The default adaptive limiter uses the `aggressive` preset: warmup sample floor,
|
|
237
|
+
P5 baseline, error-rate signal, priority-aware queueing, jittered probes,
|
|
238
|
+
proportional headroom, capped decreases, and TTL-evicted per-key state.
|
|
239
|
+
Call `shutdown()` for explicit cleanup.
|
|
240
|
+
|
|
241
|
+
The same schema DSL is available outside HTTP:
|
|
93
242
|
|
|
94
243
|
```ts
|
|
95
|
-
import {
|
|
96
|
-
|
|
244
|
+
import { Schema } from "brass-runtime/schema";
|
|
245
|
+
|
|
246
|
+
const Config = Schema.object({
|
|
247
|
+
port: Schema.int({ min: 1 }),
|
|
248
|
+
callbackUrl: Schema.url(),
|
|
249
|
+
});
|
|
97
250
|
|
|
98
|
-
|
|
251
|
+
const config = Config.parse({ port: 3000, callbackUrl: "https://example.com/cb" });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Schemas can validate request bodies before the HTTP request is sent:
|
|
99
255
|
|
|
100
|
-
|
|
256
|
+
```ts
|
|
257
|
+
import { Schema } from "brass-runtime/schema";
|
|
101
258
|
|
|
102
|
-
const
|
|
259
|
+
const CreateUser = Schema.object({
|
|
260
|
+
name: Schema.string({ minLength: 1 }),
|
|
261
|
+
});
|
|
103
262
|
|
|
104
|
-
|
|
105
|
-
|
|
263
|
+
await http.postJson(
|
|
264
|
+
"/users",
|
|
265
|
+
{ name: "Ada" },
|
|
266
|
+
{ bodySchema: CreateUser, schema: User }
|
|
267
|
+
).unsafeRunPromise();
|
|
106
268
|
```
|
|
107
269
|
|
|
108
|
-
|
|
270
|
+
The same validation machinery checks runtime, HTTP, and observability configs
|
|
271
|
+
at construction time, so invalid values fail with field paths like
|
|
272
|
+
`$.otlp.pipeline.batchSize` instead of surfacing later as ambiguous behavior.
|
|
273
|
+
|
|
274
|
+
### HTTP server
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import { asyncSucceed, asyncSync, runPromise, useResource } from "brass-runtime";
|
|
278
|
+
import { HttpServer, s } from "brass-runtime/http";
|
|
279
|
+
|
|
280
|
+
const User = s.object({
|
|
281
|
+
id: s.nonEmptyString(),
|
|
282
|
+
name: s.nonEmptyString(),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const router = HttpServer.router([
|
|
286
|
+
HttpServer.route("GET", "/users/:id", {
|
|
287
|
+
params: s.object({ id: s.nonEmptyString() }),
|
|
288
|
+
response: User,
|
|
289
|
+
}, (ctx) =>
|
|
290
|
+
asyncSucceed(HttpServer.json({
|
|
291
|
+
id: ctx.params.id,
|
|
292
|
+
name: "Ada",
|
|
293
|
+
})),
|
|
294
|
+
),
|
|
295
|
+
HttpServer.healthRoute(),
|
|
296
|
+
HttpServer.readinessRoute(),
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
await runPromise(
|
|
300
|
+
useResource(
|
|
301
|
+
router.listen({ port: 3000 }),
|
|
302
|
+
(server) => asyncSync(() => {
|
|
303
|
+
console.log(server.url());
|
|
304
|
+
}),
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Routes infer `:params` from the path, optional schemas validate
|
|
310
|
+
params/query/body/response, middleware is effect-based, and the Node listener is
|
|
311
|
+
available as a managed resource for graceful shutdown.
|
|
312
|
+
|
|
313
|
+
### Discoverable HTTP builder
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
import { httpClientBuilder } from "brass-runtime/http";
|
|
317
|
+
|
|
318
|
+
declare const token: string;
|
|
319
|
+
|
|
320
|
+
const http = httpClientBuilder()
|
|
321
|
+
.baseUrl("https://api.example.com")
|
|
322
|
+
.production()
|
|
323
|
+
.balancedLimiter({ maxLimit: 128 })
|
|
324
|
+
.header("authorization", `Bearer ${token}`)
|
|
325
|
+
.cache({ ttlSeconds: 30, maxEntries: 512 })
|
|
326
|
+
.retry({ maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 })
|
|
327
|
+
.build();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### HTTP test helpers
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import {
|
|
334
|
+
makeJsonHttpResponse,
|
|
335
|
+
makeMockHttpClient,
|
|
336
|
+
runHttpEffect,
|
|
337
|
+
} from "brass-runtime/http/testing";
|
|
338
|
+
|
|
339
|
+
const mock = makeMockHttpClient((req) => makeJsonHttpResponse({ url: req.url }));
|
|
340
|
+
const response = await runHttpEffect(mock({ method: "GET", url: "/users/1" }));
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Domain-specific layers still fit in the same config:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const http = makeDefaultHttpClient({
|
|
347
|
+
baseUrl: "https://api.example.com",
|
|
348
|
+
batch: {
|
|
349
|
+
windowMs: 50,
|
|
350
|
+
maxBatchSize: 20,
|
|
351
|
+
batchKey: (req) => req.url.startsWith("/graphql") ? "graphql" : "",
|
|
352
|
+
batch: {
|
|
353
|
+
coalesce: (reqs) => ({ method: "POST", url: "/graphql/batch", body: JSON.stringify(reqs) }),
|
|
354
|
+
split: (res, reqs) => JSON.parse(res.bodyText),
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const response = await http.getJson<User>("/users/1").unsafeRunPromise();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Adaptive concurrency
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import { makeAdaptiveLimiterConfig, makeHttp } from "brass-runtime/http";
|
|
366
|
+
|
|
367
|
+
const http = makeHttp({
|
|
368
|
+
adaptiveLimiter: makeAdaptiveLimiterConfig("balanced", {
|
|
369
|
+
maxLimit: 100,
|
|
370
|
+
stateTtlMs: 300_000,
|
|
371
|
+
warmupRequests: 20,
|
|
372
|
+
minSamples: 25,
|
|
373
|
+
decreaseCooldownSamples: 3,
|
|
374
|
+
decreaseThreshold: 0.6,
|
|
375
|
+
maxDecreaseRatio: 0.15,
|
|
376
|
+
historySize: 64,
|
|
377
|
+
onLimitChange: (event) => console.log(`limit: ${event.previousLimit} → ${event.newLimit}`),
|
|
378
|
+
}),
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
console.log(http.adaptiveLimiter?.dump());
|
|
382
|
+
console.log(http.adaptiveLimiter?.history("https://api.example.com"));
|
|
383
|
+
http.shutdown?.();
|
|
384
|
+
```
|
|
109
385
|
|
|
110
|
-
|
|
386
|
+
More end-to-end examples live in [`docs/http-recipes.md`](docs/http-recipes.md).
|
|
111
387
|
|
|
112
|
-
|
|
388
|
+
### Connection pre-warming
|
|
113
389
|
|
|
114
|
-
|
|
390
|
+
```ts
|
|
391
|
+
import { makePrewarmManager } from "brass-runtime/http";
|
|
392
|
+
|
|
393
|
+
const prewarm = makePrewarmManager({
|
|
394
|
+
origins: ["https://api.example.com", "https://cdn.example.com"],
|
|
395
|
+
keepAliveDurationMs: 55000,
|
|
396
|
+
autoRefresh: true,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
await prewarm.warmAll();
|
|
400
|
+
// Subsequent requests skip TCP+TLS handshake
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Response compression
|
|
115
404
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
405
|
+
```ts
|
|
406
|
+
import { makeCompressionMiddleware, makeDefaultHttpClient } from "brass-runtime/http";
|
|
407
|
+
|
|
408
|
+
const { middleware, stats } = makeCompressionMiddleware({ encodings: ["br", "gzip"] });
|
|
409
|
+
const baseClient = makeDefaultHttpClient({
|
|
410
|
+
baseUrl: "https://api.example.com",
|
|
411
|
+
compression: false,
|
|
412
|
+
});
|
|
413
|
+
const client = baseClient.with(middleware);
|
|
414
|
+
// Responses are transparently decompressed (gzip, brotli, deflate)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Production observability
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
import { Runtime, asyncSucceed } from "brass-runtime/core";
|
|
421
|
+
import {
|
|
422
|
+
makeObservability,
|
|
423
|
+
runObservedHttpServerEffect,
|
|
424
|
+
withHttpObservability,
|
|
425
|
+
} from "brass-runtime/observability";
|
|
426
|
+
import { makeDefaultHttpClient } from "brass-runtime/http";
|
|
427
|
+
|
|
428
|
+
const obs = makeObservability({
|
|
429
|
+
serviceName: "api",
|
|
430
|
+
logs: { minLevel: "info" },
|
|
431
|
+
sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
|
|
432
|
+
redaction: {},
|
|
433
|
+
cardinality: { maxValuesPerLabel: 100 },
|
|
434
|
+
otlp: {
|
|
435
|
+
metricsUrl: "http://collector:4318/v1/metrics",
|
|
436
|
+
tracesUrl: "http://collector:4318/v1/traces",
|
|
437
|
+
logsUrl: "http://collector:4318/v1/logs",
|
|
438
|
+
},
|
|
439
|
+
flushIntervalMs: 10_000,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const runtime = new Runtime({ env: obs.env, hooks: obs.hooks });
|
|
443
|
+
const client = makeDefaultHttpClient({
|
|
444
|
+
baseUrl: "https://api.example.com",
|
|
445
|
+
middleware: [withHttpObservability(obs)],
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await runObservedHttpServerEffect(
|
|
449
|
+
obs,
|
|
450
|
+
{ method: "GET", route: "/users/:id" },
|
|
451
|
+
asyncSucceed("ok")
|
|
452
|
+
);
|
|
453
|
+
await runtime.toPromise(client.getText("/health"));
|
|
454
|
+
await obs.shutdown();
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
HTTP client observability automatically reads adaptive limiter diagnostics when
|
|
458
|
+
the wrapped client owns a limiter, exposing gauges for current limit, queue
|
|
459
|
+
depth, utilization, error rate, request/completion rate, rejection rate, and
|
|
460
|
+
state count.
|
|
461
|
+
It also reads `req.policy` automatically: logs and span attributes include
|
|
462
|
+
`preset`, `lane`, `poolKey`, `dedupKey`, `priority`, and retry overrides when present.
|
|
463
|
+
Metric labels stay conservative by default; opt into stable labels with
|
|
464
|
+
`withHttpObservability({ policy: { labelKeys: ["preset", "lane", "poolKey"] } })`.
|
|
465
|
+
|
|
466
|
+
### Performance profiler
|
|
125
467
|
|
|
126
468
|
```bash
|
|
127
|
-
npm run
|
|
128
|
-
|
|
469
|
+
npm run perf
|
|
470
|
+
npm run perf:json
|
|
471
|
+
npm run benchmark:perf
|
|
472
|
+
npm run perf:runtime:ab
|
|
473
|
+
npm run perf:runtime:soak
|
|
474
|
+
npm run perf:runtime:budget
|
|
475
|
+
npm run perf:http:memory
|
|
476
|
+
npm run perf:history
|
|
477
|
+
```
|
|
129
478
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
479
|
+
```ts
|
|
480
|
+
import { runBrassPerformanceProfile } from "brass-runtime/perf";
|
|
481
|
+
|
|
482
|
+
const report = await runBrassPerformanceProfile({
|
|
483
|
+
http: {
|
|
484
|
+
calls: 20_000,
|
|
485
|
+
concurrency: 512,
|
|
486
|
+
delayMs: 2,
|
|
487
|
+
forceGc: true,
|
|
488
|
+
variants: ["default-json", "default-json-observed"],
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
console.log(report.recommendations);
|
|
136
493
|
```
|
|
137
494
|
|
|
138
|
-
|
|
495
|
+
The profiler compares runtime primitives, runtime A/B variants, runtime-only
|
|
496
|
+
soak behavior, `node:http`, Brass wire/default HTTP clients, HTTP long-run
|
|
497
|
+
memory, observability overhead, history/baseline regressions, and memory deltas. Use
|
|
498
|
+
[`docs/performance-profiler.md`](docs/performance-profiler.md) for focused
|
|
499
|
+
commands and `node --expose-gc` runs.
|
|
139
500
|
|
|
140
|
-
|
|
501
|
+
Perf runs can be persisted and compared locally:
|
|
141
502
|
|
|
142
|
-
|
|
503
|
+
```bash
|
|
504
|
+
npm run perf -- --profile runtime-ab --record-history --save-baseline runtime-main
|
|
505
|
+
npm run perf -- --profile runtime-ab --compare-baseline runtime-main --fail-on-baseline-regression
|
|
506
|
+
```
|
|
143
507
|
|
|
144
|
-
-
|
|
145
|
-
- `Pull` semantics
|
|
146
|
-
- Bounded buffers
|
|
147
|
-
- Deterministic resource cleanup
|
|
508
|
+
### First-release recipes
|
|
148
509
|
|
|
149
|
-
|
|
510
|
+
Copyable happy paths live in [`docs/recipes`](docs/recipes/README.md):
|
|
150
511
|
|
|
151
|
-
-
|
|
152
|
-
-
|
|
512
|
+
- runtime execution
|
|
513
|
+
- typed layers
|
|
514
|
+
- HTTP server
|
|
515
|
+
- testing
|
|
516
|
+
- performance baselines
|
|
153
517
|
|
|
154
|
-
|
|
518
|
+
### Structured concurrency
|
|
155
519
|
|
|
156
|
-
|
|
520
|
+
```ts
|
|
521
|
+
import { Runtime, asyncSucceed, withScope } from "brass-runtime";
|
|
157
522
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
523
|
+
const runtime = Runtime.make({});
|
|
524
|
+
|
|
525
|
+
await runtime.toPromise(
|
|
526
|
+
withScope(runtime, (scope) => {
|
|
527
|
+
scope.fork(asyncSucceed("child"));
|
|
528
|
+
// scope close interrupts children + runs finalizers
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Streams
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
import { Runtime, Stream } from "brass-runtime";
|
|
537
|
+
|
|
538
|
+
const runtime = Runtime.make({});
|
|
539
|
+
const result = await Stream
|
|
540
|
+
.from([1, 2, 3, 4, 5])
|
|
541
|
+
.map((n) => n * 2)
|
|
542
|
+
.collect(runtime);
|
|
543
|
+
// [2, 4, 6, 8, 10]
|
|
544
|
+
```
|
|
166
545
|
|
|
167
546
|
---
|
|
168
547
|
|
|
169
|
-
##
|
|
548
|
+
## Package exports
|
|
549
|
+
|
|
550
|
+
| Import | Purpose |
|
|
551
|
+
|--------|---------|
|
|
552
|
+
| `brass-runtime` | Core runtime: effects, fibers, scheduler, streams, layers |
|
|
553
|
+
| `brass-runtime/core` | Stable core surface (preferred for new code) |
|
|
554
|
+
| `brass-runtime/http` | Default HTTP client factory, lifecycle middleware, compression, batching, prewarm, adaptive limiter |
|
|
555
|
+
| `brass-runtime/http/testing` | Dependency-free mock clients, mock fetch, response factories, and effect runner helpers |
|
|
556
|
+
| `brass-runtime/schema` | Dependency-free runtime schema DSL with type inference |
|
|
557
|
+
| `brass-runtime/observability` | Prometheus/OTLP exporters, logs, spans, trace propagation, request adapters |
|
|
558
|
+
| `brass-runtime/perf` | Runtime, HTTP, observability, memory, and baseline performance profiler |
|
|
559
|
+
| `brass-runtime/agent` | Brass Agent core (experimental) |
|
|
170
560
|
|
|
171
|
-
|
|
172
|
-
- Abortable async integration (`fromPromiseAbortable`)
|
|
173
|
-
- Fiber-safe `toPromise` for examples & DX
|
|
174
|
-
- HTTP client module built on top of the runtime
|
|
561
|
+
CLI: `brass-agent`
|
|
175
562
|
|
|
176
563
|
---
|
|
177
564
|
|
|
178
|
-
##
|
|
565
|
+
## HTTP middleware pipeline
|
|
179
566
|
|
|
180
|
-
|
|
567
|
+
The lifecycle client composes middleware in this order (innermost to outermost):
|
|
181
568
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
569
|
+
```
|
|
570
|
+
Wire → Priority → Retry → Cache → Batch → Dedup
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Adaptive limiting lives in the wire client, before lifecycle middleware.
|
|
574
|
+
`makeDefaultHttpClient` can then wrap the lifecycle stack with response
|
|
575
|
+
compression and caller middleware; caller middleware is outermost.
|
|
188
576
|
|
|
189
|
-
|
|
577
|
+
Each layer is independently optional. Set to `false` or omit to disable.
|
|
190
578
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
579
|
+
| Layer | What it does |
|
|
580
|
+
|-------|-------------|
|
|
581
|
+
| **Adaptive Limiter** | Gradient-based dynamic concurrency control per origin |
|
|
582
|
+
| **Priority** | Priority queue for request scheduling (0-9 levels) |
|
|
583
|
+
| **Retry** | Exponential backoff with circuit breaker awareness |
|
|
584
|
+
| **Cache** | LRU + TTL + stale-while-revalidate |
|
|
585
|
+
| **Batch** | Time-window request coalescing with split/distribute |
|
|
586
|
+
| **Dedup** | Ref-counted in-flight request deduplication |
|
|
587
|
+
| **Compression** | Transparent gzip/br/deflate decompression |
|
|
588
|
+
| **Prewarm** | Proactive TCP+TLS connection establishment |
|
|
197
589
|
|
|
198
|
-
|
|
590
|
+
All layers emit lifecycle events, track stats, and support cancellation.
|
|
199
591
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
592
|
+
The recommended `makeDefaultHttpClient` factory wires the default preset
|
|
593
|
+
for you and accepts extra middleware, so observability can be attached with
|
|
594
|
+
`middleware: [withHttpObservability(obs)]` without coupling HTTP to exporters.
|
|
595
|
+
Per-request `policy` travels through that stack and is visible to observability
|
|
596
|
+
without being forwarded to the host transport.
|
|
203
597
|
|
|
204
598
|
---
|
|
205
599
|
|
|
206
|
-
##
|
|
600
|
+
## WASM engine
|
|
207
601
|
|
|
208
|
-
-
|
|
209
|
-
- **Deterministic execution**: scheduler is observable & testable
|
|
210
|
-
- **Resource safety is structural**: scopes guarantee cleanup
|
|
211
|
-
- **Libraries compose via functions**: middleware, not inheritance
|
|
602
|
+
Optional Rust/WASM-backed components for strict execution:
|
|
212
603
|
|
|
213
|
-
|
|
604
|
+
- Fiber engine state machine
|
|
605
|
+
- Scheduler queue
|
|
606
|
+
- Bounded queues
|
|
607
|
+
- Permit pool
|
|
608
|
+
- Retry planner
|
|
214
609
|
|
|
215
|
-
|
|
610
|
+
```bash
|
|
611
|
+
npm run build:wasm # requires wasm-pack
|
|
612
|
+
```
|
|
216
613
|
|
|
217
|
-
|
|
218
|
-
- Prefer libraries on top of the runtime over changes in the core
|
|
219
|
-
- Small, focused PRs are welcome (your repo may enforce PR-only changes)
|
|
614
|
+
The WASM engine never silently falls back to TypeScript — if you request WASM and it's unavailable, it fails explicitly.
|
|
220
615
|
|
|
221
616
|
---
|
|
222
617
|
|
|
223
|
-
##
|
|
618
|
+
## Brass Agent (experimental)
|
|
619
|
+
|
|
620
|
+
A CLI-first coding agent built on the runtime. Inspects workspaces, discovers validation commands, gathers bounded context, asks an LLM for patches, and applies/rolls back changes under policy.
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
npm run agent:vscode:install # VS Code extension
|
|
624
|
+
brass-agent --doctor # check setup
|
|
625
|
+
brass-agent --init # initialize workspace
|
|
626
|
+
brass-agent --preset inspect # run inspection
|
|
627
|
+
```
|
|
224
628
|
|
|
225
|
-
|
|
629
|
+
Docs: [Install](./docs/agent-install-and-configure.md) · [CLI](./docs/agent-cli.md) · [Project intelligence](./docs/agent-project-intelligence.md) · [VS Code](./docs/agent-vscode-install.md)
|
|
226
630
|
|
|
227
|
-
|
|
631
|
+
---
|
|
228
632
|
|
|
229
|
-
|
|
633
|
+
## Testing
|
|
230
634
|
|
|
231
635
|
```bash
|
|
232
|
-
npm
|
|
636
|
+
npm test # vitest suite
|
|
637
|
+
npm run test:types # TypeScript type checking
|
|
638
|
+
npm run test:coverage # coverage with baseline gate
|
|
639
|
+
npm run release:check # full release gate: types, tests, build, CJS, perf budgets
|
|
640
|
+
npm run benchmark # runtime, HTTP lifecycle, and 100k local HTTP concurrency
|
|
641
|
+
npm run benchmark:runtime # Runtime Performance Track
|
|
642
|
+
npm run benchmark:runtime:budget # Runtime Performance Track regression budget
|
|
643
|
+
npm run perf:runtime:ab # Runtime A/B Performance Lab
|
|
644
|
+
npm run perf:runtime:soak # Runtime-only soak profile
|
|
645
|
+
npm run perf:runtime:budget # Runtime profiler budget
|
|
646
|
+
npm run perf:http:memory # HTTP long-run memory lab
|
|
647
|
+
npm run benchmark -- http-concurrent # HTTP compare mode variants
|
|
648
|
+
node --expose-gc --import tsx src/benchmarks/runner.ts http-concurrent # HTTP memory/limiter diagnostics
|
|
649
|
+
npm run benchmark:adaptive
|
|
650
|
+
npm run benchmark:adaptive:soak
|
|
651
|
+
npm run benchmark:http:budget
|
|
652
|
+
npm run benchmark:http:soak
|
|
653
|
+
npm run benchmark:observability
|
|
654
|
+
npm run benchmark:observability:budget
|
|
655
|
+
npm run smoke:observability:collector # requires local OTEL collector
|
|
233
656
|
```
|
|
234
657
|
|
|
235
|
-
|
|
658
|
+
Property-based tests use `fast-check` with 100+ iterations per property. Each HTTP middleware has dedicated property tests verifying correctness invariants.
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## Docs
|
|
663
|
+
|
|
664
|
+
- [Getting Started](./docs/getting-started.md)
|
|
665
|
+
- [Architecture](./docs/ARCHITECTURE.md)
|
|
666
|
+
- [Cancellation & Interruption](./docs/cancellation.md)
|
|
667
|
+
- [Observability: Hooks & Tracing](./docs/observability.md)
|
|
668
|
+
- [Observability framework examples](./docs/observability-framework-examples.md)
|
|
669
|
+
- [Observability collector smoke](./docs/observability-collector-smoke.md)
|
|
670
|
+
- [HTTP module](./docs/http.md)
|
|
671
|
+
- [Production readiness](./docs/production-readiness.md)
|
|
672
|
+
- [Streams guide](./docs/guides/streams.md)
|
|
673
|
+
- [Testing guide](./docs/guides/testing.md)
|
|
674
|
+
- [WASM engine](./docs/wasm-fiber-engine.md)
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## Features
|
|
679
|
+
|
|
680
|
+
### Runtime (core)
|
|
681
|
+
|
|
682
|
+
- [x] Sync effect values via `ZIO<R, E, A>` aliases
|
|
683
|
+
- [x] Algebraic async: `Async<R, E, A>`
|
|
684
|
+
- [x] Rich `Cause<E>` failure trees with pretty printing
|
|
685
|
+
- [x] Interruptibility regions with `uninterruptible` / `uninterruptibleMask`
|
|
686
|
+
- [x] Fiber-local refs with fork inheritance and scoped restoration
|
|
687
|
+
- [x] TS TestRuntime with deterministic scheduler and virtual clock
|
|
688
|
+
- [x] Schedule 2.0 with drivers, runtime-clock budgets, observability, and HTTP integration
|
|
689
|
+
- [x] Cooperative scheduler (observable, testable)
|
|
690
|
+
- [x] Fibers with interruption & finalizers
|
|
691
|
+
- [x] Structured scopes & resource safety
|
|
692
|
+
- [x] Runtime flight recorder for bounded execution traces
|
|
693
|
+
- [x] Layer 2.0 typed contexts, semaphores, circuit breakers
|
|
694
|
+
- [x] Metrics, tracing, runtime hooks
|
|
695
|
+
- [x] Worker pools
|
|
696
|
+
- [x] WASM engine (optional)
|
|
697
|
+
|
|
698
|
+
### Streams
|
|
699
|
+
|
|
700
|
+
- [x] Pull-based streams with backpressure
|
|
701
|
+
- [x] Fluent `Stream` / `Pipeline` DX facade
|
|
702
|
+
- [x] Bounded buffers, queues, hubs
|
|
703
|
+
- [x] Pipelines with fusion optimization
|
|
704
|
+
- [x] Stream merge, zip, broadcast
|
|
705
|
+
- [x] Chunks & batch operators
|
|
706
|
+
|
|
707
|
+
### HTTP
|
|
708
|
+
|
|
709
|
+
- [x] Lazy, cancelable HTTP client
|
|
710
|
+
- [x] Effect-based Node HTTP server listener with resource lifecycle
|
|
711
|
+
- [x] Declarative typed routing with params/query/body/response schemas
|
|
712
|
+
- [x] Health/readiness routes backed by runtime health reports
|
|
713
|
+
- [x] Schema-validated JSON helpers
|
|
714
|
+
- [x] Discoverable builder API
|
|
715
|
+
- [x] Test helper subpath
|
|
716
|
+
- [x] Lifecycle client with middleware composition
|
|
717
|
+
- [x] Response cache (LRU + TTL + SWR)
|
|
718
|
+
- [x] Request deduplication (ref-counted)
|
|
719
|
+
- [x] Priority scheduling
|
|
720
|
+
- [x] Retry with exponential backoff
|
|
721
|
+
- [x] Response compression (gzip, br, deflate)
|
|
722
|
+
- [x] Request batching (time-window coalesce/split)
|
|
723
|
+
- [x] Connection pre-warming (probes, auto-refresh)
|
|
724
|
+
- [x] Adaptive concurrency (gradient-based)
|
|
725
|
+
- [x] Adaptive limiter presets, diagnostics, observability gauges, and soak benchmark
|
|
726
|
+
- [x] Circuit breaker
|
|
727
|
+
- [x] Tracing & validation
|
|
728
|
+
|
|
729
|
+
### Schema
|
|
730
|
+
|
|
731
|
+
- [x] Dependency-free schema DSL
|
|
732
|
+
- [x] Type inference via `InferSchema`
|
|
733
|
+
- [x] Object, array, record, union, enum, literal, custom schemas
|
|
734
|
+
- [x] Optional, nullable, refine, transform
|
|
735
|
+
- [x] Path-rich validation issues
|
|
736
|
+
|
|
737
|
+
### Observability
|
|
738
|
+
|
|
739
|
+
- [x] Runtime metrics sink and Prometheus exporter
|
|
740
|
+
- [x] OTLP JSON/HTTP exporters for metrics, traces, and logs
|
|
741
|
+
- [x] Structured logging with context propagation and redaction
|
|
742
|
+
- [x] W3C trace-context extract/inject helpers
|
|
743
|
+
- [x] Client and server HTTP observability helpers
|
|
744
|
+
- [x] Adaptive limiter metrics on HTTP client spans and Prometheus gauges
|
|
745
|
+
- [x] Sampling, force-sample-on-error, and bounded trace retention
|
|
746
|
+
- [x] Bounded exporter queues with retry, timeout, drop policy, and single-flight flush
|
|
747
|
+
- [x] Fetch/Node/Express/Fastify/Nest-style examples and collector smoke script
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## Design notes
|
|
752
|
+
|
|
753
|
+
- **No hidden Promises** — async is always modeled explicitly via `Async`
|
|
754
|
+
- **Deterministic execution** — scheduler is observable and testable
|
|
755
|
+
- **Resource safety is structural** — scopes guarantee cleanup
|
|
756
|
+
- **Middleware composes via functions** — `(next: HttpClientFn) => HttpClientFn`
|
|
757
|
+
- **Cancellation propagates** — ref-counted through the entire middleware stack
|
|
758
|
+
- **Stats are frozen snapshots** — no mutable state leaks to consumers
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
## Contributing
|
|
763
|
+
|
|
764
|
+
- Runtime invariants matter — avoid sneaking Promises into core semantics
|
|
765
|
+
- Prefer libraries on top of the runtime over changes in core
|
|
766
|
+
- Add property tests when an invariant is broad
|
|
767
|
+
- Keep tests close to the changed module
|
|
768
|
+
- Small, focused PRs are welcome
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## License
|
|
236
773
|
|
|
237
|
-
|
|
774
|
+
MIT © 2025
|