brass-runtime 1.15.0 → 1.16.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.
- package/README.md +409 -137
- 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-PPUXIH5R.js → chunk-2WC63LJK.mjs} +11 -7
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-45F7OKGT.cjs +104 -0
- package/dist/chunk-5YOQOXEQ.cjs +2491 -0
- package/dist/{chunk-STVLQ3XD.cjs → chunk-7HUOJA4W.cjs} +78 -74
- package/dist/{chunk-BMH5AV44.js → chunk-7LVI2GIN.js} +251 -370
- package/dist/chunk-7TL2LHQJ.js +2491 -0
- package/dist/chunk-7V4KY4RL.mjs +104 -0
- package/dist/chunk-7XOPAB5Q.js +2143 -0
- package/dist/chunk-CCKHV5BT.mjs +193 -0
- package/dist/{chunk-AR22SXML.js → chunk-CY33PGEX.mjs} +488 -421
- package/dist/chunk-DJQ7OMMB.cjs +144 -0
- package/dist/chunk-F5EUMJL7.mjs +2143 -0
- package/dist/chunk-FM4W4QPL.js +193 -0
- package/dist/{chunk-TO7IKXYT.js → chunk-G3XGCZDQ.js} +1 -1
- package/dist/{chunk-BDF4AMWX.mjs → chunk-G6IQOE4P.mjs} +251 -370
- package/dist/chunk-GOV47PPB.mjs +552 -0
- package/dist/chunk-H55LI6WY.js +93 -0
- package/dist/chunk-IJT6RRQ5.cjs +93 -0
- package/dist/{chunk-ELOOF35R.mjs → chunk-J3H54ZRV.mjs} +1 -1
- package/dist/chunk-JF4XXPZ5.cjs +552 -0
- package/dist/chunk-JNFRRJYH.cjs +2143 -0
- package/dist/chunk-JX3LZQJH.cjs +354 -0
- package/dist/chunk-K2T3DV26.mjs +93 -0
- package/dist/chunk-KCPT2D6G.js +552 -0
- package/dist/chunk-MWXMNYJS.cjs +1110 -0
- package/dist/{chunk-VEZNF5GZ.cjs → chunk-N6VHMOWB.cjs} +130 -126
- package/dist/{chunk-3QMOKAS5.js → chunk-NC5SDRYE.js} +9 -5
- package/dist/chunk-NOYZIMUJ.mjs +144 -0
- package/dist/{chunk-R3R2FVLG.cjs → chunk-NYL4D7SK.cjs} +5 -5
- package/dist/chunk-OBGZSXTJ.cjs +10 -0
- package/dist/{chunk-4NHES7VK.mjs → chunk-OOGJ73B6.js} +11 -7
- package/dist/chunk-PNVFW245.js +144 -0
- package/dist/chunk-PRWCB3QL.mjs +2491 -0
- package/dist/{chunk-JFPU5GQI.mjs → chunk-QY5FKYEQ.js} +488 -421
- package/dist/chunk-ROJC3NBJ.js +104 -0
- package/dist/chunk-SPUEME2B.cjs +343 -0
- package/dist/chunk-TDVMADDN.js +343 -0
- package/dist/chunk-TVN5I4U6.cjs +193 -0
- package/dist/chunk-U5KWK3PX.mjs +343 -0
- package/dist/chunk-VFIUZG7J.mjs +354 -0
- package/dist/{chunk-TGIFUAK4.cjs → chunk-WQ5QNU5R.cjs} +459 -578
- package/dist/chunk-XDZOO4L5.js +354 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/{chunk-K6M7MDZ4.mjs → chunk-ZGLD4TVZ.mjs} +9 -5
- package/dist/client-CtFmoDvM.d.ts +645 -0
- package/dist/core/index.cjs +72 -4
- package/dist/core/index.d.ts +92 -198
- package/dist/core/index.js +106 -38
- package/dist/core/index.mjs +106 -38
- package/dist/{effect-CMOQKX8y.d.ts → effect-CGNl5Rqp.d.ts} +107 -1
- package/dist/effectRunner-3ZHAD3LE.cjs +8 -0
- package/dist/effectRunner-A4CHJXJI.js +8 -0
- package/dist/effectRunner-OPUF6QRN.mjs +8 -0
- package/dist/http/index.cjs +2189 -1271
- package/dist/http/index.d.ts +830 -270
- package/dist/http/index.js +2008 -1090
- package/dist/http/index.mjs +2008 -1090
- package/dist/http/testing.cjs +159 -0
- package/dist/http/testing.d.ts +42 -0
- package/dist/http/testing.js +159 -0
- package/dist/http/testing.mjs +159 -0
- package/dist/index.cjs +246 -178
- package/dist/index.d.ts +9 -35
- package/dist/index.js +120 -52
- package/dist/index.mjs +120 -52
- package/dist/observability/index.cjs +677 -0
- package/dist/observability/index.d.ts +79 -0
- package/dist/observability/index.js +677 -0
- package/dist/observability/index.mjs +677 -0
- package/dist/schedule-Fque9Abz.d.ts +70 -0
- package/dist/schema/index.cjs +25 -0
- package/dist/schema/index.d.ts +177 -0
- package/dist/schema/index.js +25 -0
- package/dist/schema/index.mjs +25 -0
- package/dist/server-C8hDXA74.d.ts +674 -0
- package/dist/{stream-FQm9h4Mg.d.ts → stream-dvSs0QS5.d.ts} +1 -1
- package/dist/tracer-B5tRH9H7.d.ts +230 -0
- package/dist/tracing-Dt9S_6V8.d.ts +148 -0
- package/package.json +27 -1
- package/dist/chunk-BDYEENHT.js +0 -224
- 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,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# brass-runtime
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A ZIO-inspired effect runtime for TypeScript with structured concurrency, pull-based streams, and a production-grade HTTP client.
|
|
4
4
|
|
|
5
|
-
`
|
|
6
|
-
Higher-level modules (HTTP, streaming utilities, integrations) are built **on top of the runtime**, not baked into it.
|
|
5
|
+
Built without `Promise`/`async`/`await` as the primary semantic primitive. Effects are values — lazy, composable, and cancelable by default.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm i brass-runtime
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
**Core runtime** — algebraic effects, fibers, scopes, scheduler, layers, semaphores, circuit breakers, metrics, tracing.
|
|
16
|
+
|
|
17
|
+
**Streams** — pull-based with backpressure, bounded buffers, queues, hubs, pipelines, fusion optimization.
|
|
18
|
+
|
|
19
|
+
**HTTP client** — lazy/cancelable requests with a full middleware pipeline: adaptive concurrency, compression, batching, connection pre-warming, caching, deduplication, priority scheduling, and retry with backoff.
|
|
20
|
+
|
|
21
|
+
**Schema validation** — dependency-free runtime schemas for JSON, config, and protocol boundaries, with typed inference and path-rich validation issues.
|
|
22
|
+
|
|
23
|
+
**Observability export** — Prometheus metrics, OTLP metrics/traces/logs, structured logging, W3C trace-context propagation, request adapters, sampling, redaction, bounded exporters, and production flush/shutdown controls.
|
|
24
|
+
|
|
25
|
+
**WASM engine** — optional Rust/WASM-backed state machines for strict scheduling and bounded queues.
|
|
26
|
+
|
|
27
|
+
**Brass Agent** — experimental CLI coding agent with workspace inspection, LLM integration, and VS Code extension.
|
|
9
28
|
|
|
10
29
|
---
|
|
11
30
|
|
|
@@ -15,141 +34,373 @@ Higher-level modules (HTTP, streaming utilities, integrations) are built **on to
|
|
|
15
34
|
- **Async is explicit** — no hidden Promise semantics
|
|
16
35
|
- **Concurrency is structured** — fibers, scopes, finalizers
|
|
17
36
|
- **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.
|
|
37
|
+
- **Higher-level APIs are libraries** — HTTP, streams, agent are built on top of core
|
|
21
38
|
|
|
22
39
|
---
|
|
23
40
|
|
|
24
|
-
##
|
|
41
|
+
## Quick start
|
|
25
42
|
|
|
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
|
|
43
|
+
### Run an effect
|
|
32
44
|
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
```ts
|
|
46
|
+
import { Runtime, succeed } from "brass-runtime";
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
const runtime = Runtime.make({});
|
|
49
|
+
const value = await runtime.toPromise(succeed(42));
|
|
50
|
+
```
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
### Recommended HTTP client
|
|
39
53
|
|
|
40
|
-
```
|
|
41
|
-
|
|
54
|
+
```ts
|
|
55
|
+
import { makeDefaultHttpClient, s } from "brass-runtime/http";
|
|
56
|
+
|
|
57
|
+
const User = s.object({
|
|
58
|
+
id: s.number({ int: true }),
|
|
59
|
+
name: s.string({ minLength: 1 }),
|
|
60
|
+
role: s.enum(["admin", "user"] as const).optional(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const http = makeDefaultHttpClient({
|
|
64
|
+
baseUrl: "https://api.example.com",
|
|
65
|
+
headers: { accept: "application/json" },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const user = await http.getJson("/users/1", { schema: User }).unsafeRunPromise();
|
|
69
|
+
|
|
70
|
+
console.log(user.body.name);
|
|
71
|
+
console.log(http.stats());
|
|
72
|
+
console.log(http.compression?.stats());
|
|
42
73
|
```
|
|
43
74
|
|
|
44
|
-
|
|
75
|
+
`makeDefaultHttpClient` is the batteries-included entrypoint: timeout,
|
|
76
|
+
deduplication, priority scheduling, retry, adaptive concurrency, safe-method
|
|
77
|
+
response cache, decompression, stats, `cancelAll`, and JSON/text helpers. Use
|
|
78
|
+
`preset: "balanced"` to skip the default cache, or `preset: "minimal"` for a
|
|
79
|
+
cheap wire client with the same helper API.
|
|
45
80
|
|
|
46
|
-
|
|
81
|
+
The HTTP stack is meant to replace the usual `fetch` wrapper plus Zod/Valibot
|
|
82
|
+
glue: schemas are dependency-free, responses and request bodies are validated in
|
|
83
|
+
the same effect, config validation fails at construction time, and the client
|
|
84
|
+
still owns cancellation, retries, compression, observability, and adaptive
|
|
85
|
+
limits as one pipeline.
|
|
47
86
|
|
|
48
|
-
|
|
87
|
+
The default adaptive limiter uses the `aggressive` preset: warmup sample floor,
|
|
88
|
+
P5 baseline, error-rate signal, priority-aware queueing, jittered probes,
|
|
89
|
+
proportional headroom, capped decreases, and TTL-evicted per-key state.
|
|
90
|
+
Call `shutdown()` for explicit cleanup.
|
|
91
|
+
|
|
92
|
+
The same schema DSL is available outside HTTP:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { Schema } from "brass-runtime/schema";
|
|
96
|
+
|
|
97
|
+
const Config = Schema.object({
|
|
98
|
+
port: Schema.int({ min: 1 }),
|
|
99
|
+
callbackUrl: Schema.url(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const config = Config.parse({ port: 3000, callbackUrl: "https://example.com/cb" });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Schemas can validate request bodies before the HTTP request is sent:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { Schema } from "brass-runtime/schema";
|
|
109
|
+
|
|
110
|
+
const CreateUser = Schema.object({
|
|
111
|
+
name: Schema.string({ minLength: 1 }),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await http.postJson(
|
|
115
|
+
"/users",
|
|
116
|
+
{ name: "Ada" },
|
|
117
|
+
{ bodySchema: CreateUser, schema: User }
|
|
118
|
+
).unsafeRunPromise();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The same validation machinery checks runtime, HTTP, and observability configs
|
|
122
|
+
at construction time, so invalid values fail with field paths like
|
|
123
|
+
`$.otlp.pipeline.batchSize` instead of surfacing later as ambiguous behavior.
|
|
124
|
+
|
|
125
|
+
### Discoverable HTTP builder
|
|
49
126
|
|
|
50
127
|
```ts
|
|
51
|
-
import {
|
|
128
|
+
import { httpClientBuilder } from "brass-runtime/http";
|
|
129
|
+
|
|
130
|
+
declare const token: string;
|
|
131
|
+
|
|
132
|
+
const http = httpClientBuilder()
|
|
133
|
+
.baseUrl("https://api.example.com")
|
|
134
|
+
.balanced()
|
|
135
|
+
.balancedLimiter({ maxLimit: 128 })
|
|
136
|
+
.header("authorization", `Bearer ${token}`)
|
|
137
|
+
.cache({ ttlSeconds: 30, maxEntries: 512 })
|
|
138
|
+
.retry({ maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 })
|
|
139
|
+
.build();
|
|
140
|
+
```
|
|
52
141
|
|
|
53
|
-
|
|
142
|
+
### HTTP test helpers
|
|
54
143
|
|
|
55
|
-
|
|
56
|
-
|
|
144
|
+
```ts
|
|
145
|
+
import {
|
|
146
|
+
makeJsonHttpResponse,
|
|
147
|
+
makeMockHttpClient,
|
|
148
|
+
runHttpEffect,
|
|
149
|
+
} from "brass-runtime/http/testing";
|
|
150
|
+
|
|
151
|
+
const mock = makeMockHttpClient((req) => makeJsonHttpResponse({ url: req.url }));
|
|
152
|
+
const response = await runHttpEffect(mock({ method: "GET", url: "/users/1" }));
|
|
57
153
|
```
|
|
58
154
|
|
|
59
|
-
|
|
155
|
+
Domain-specific layers still fit in the same config:
|
|
60
156
|
|
|
61
157
|
```ts
|
|
62
|
-
|
|
158
|
+
const http = makeDefaultHttpClient({
|
|
159
|
+
baseUrl: "https://api.example.com",
|
|
160
|
+
batch: {
|
|
161
|
+
windowMs: 50,
|
|
162
|
+
maxBatchSize: 20,
|
|
163
|
+
batchKey: (req) => req.url.startsWith("/graphql") ? "graphql" : "",
|
|
164
|
+
batch: {
|
|
165
|
+
coalesce: (reqs) => ({ method: "POST", url: "/graphql/batch", body: JSON.stringify(reqs) }),
|
|
166
|
+
split: (res, reqs) => JSON.parse(res.bodyText),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
});
|
|
63
170
|
|
|
64
|
-
const
|
|
171
|
+
const response = await http.getJson<User>("/users/1").unsafeRunPromise();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Adaptive concurrency
|
|
65
175
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
176
|
+
```ts
|
|
177
|
+
import { makeAdaptiveLimiterConfig, makeHttp } from "brass-runtime/http";
|
|
178
|
+
|
|
179
|
+
const http = makeHttp({
|
|
180
|
+
adaptiveLimiter: makeAdaptiveLimiterConfig("balanced", {
|
|
181
|
+
maxLimit: 100,
|
|
182
|
+
stateTtlMs: 300_000,
|
|
183
|
+
warmupRequests: 20,
|
|
184
|
+
minSamples: 25,
|
|
185
|
+
decreaseCooldownSamples: 3,
|
|
186
|
+
decreaseThreshold: 0.6,
|
|
187
|
+
maxDecreaseRatio: 0.15,
|
|
188
|
+
historySize: 64,
|
|
189
|
+
onLimitChange: (event) => console.log(`limit: ${event.previousLimit} → ${event.newLimit}`),
|
|
190
|
+
}),
|
|
70
191
|
});
|
|
192
|
+
|
|
193
|
+
console.log(http.adaptiveLimiter?.dump());
|
|
194
|
+
console.log(http.adaptiveLimiter?.history("https://api.example.com"));
|
|
195
|
+
http.shutdown?.();
|
|
71
196
|
```
|
|
72
197
|
|
|
73
|
-
|
|
198
|
+
More end-to-end examples live in [`docs/http-recipes.md`](docs/http-recipes.md).
|
|
74
199
|
|
|
75
|
-
|
|
200
|
+
### Connection pre-warming
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { makePrewarmManager } from "brass-runtime/http";
|
|
76
204
|
|
|
77
|
-
|
|
205
|
+
const prewarm = makePrewarmManager({
|
|
206
|
+
origins: ["https://api.example.com", "https://cdn.example.com"],
|
|
207
|
+
keepAliveDurationMs: 55000,
|
|
208
|
+
autoRefresh: true,
|
|
209
|
+
});
|
|
78
210
|
|
|
79
|
-
|
|
211
|
+
await prewarm.warmAll();
|
|
212
|
+
// Subsequent requests skip TCP+TLS handshake
|
|
213
|
+
```
|
|
80
214
|
|
|
81
|
-
###
|
|
215
|
+
### Response compression
|
|
82
216
|
|
|
83
|
-
|
|
217
|
+
```ts
|
|
218
|
+
import { makeCompressionMiddleware, makeDefaultHttpClient } from "brass-runtime/http";
|
|
84
219
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
220
|
+
const { middleware, stats } = makeCompressionMiddleware({ encodings: ["br", "gzip"] });
|
|
221
|
+
const baseClient = makeDefaultHttpClient({
|
|
222
|
+
baseUrl: "https://api.example.com",
|
|
223
|
+
compression: false,
|
|
224
|
+
});
|
|
225
|
+
const client = baseClient.with(middleware);
|
|
226
|
+
// Responses are transparently decompressed (gzip, brotli, deflate)
|
|
227
|
+
```
|
|
89
228
|
|
|
90
|
-
|
|
229
|
+
### Production observability
|
|
91
230
|
|
|
92
|
-
|
|
231
|
+
```ts
|
|
232
|
+
import { Runtime, asyncSucceed } from "brass-runtime/core";
|
|
233
|
+
import {
|
|
234
|
+
makeObservability,
|
|
235
|
+
runObservedHttpServerEffect,
|
|
236
|
+
withHttpObservability,
|
|
237
|
+
} from "brass-runtime/observability";
|
|
238
|
+
import { makeDefaultHttpClient } from "brass-runtime/http";
|
|
239
|
+
|
|
240
|
+
const obs = makeObservability({
|
|
241
|
+
serviceName: "api",
|
|
242
|
+
logs: { minLevel: "info" },
|
|
243
|
+
sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
|
|
244
|
+
redaction: {},
|
|
245
|
+
cardinality: { maxValuesPerLabel: 100 },
|
|
246
|
+
otlp: {
|
|
247
|
+
metricsUrl: "http://collector:4318/v1/metrics",
|
|
248
|
+
tracesUrl: "http://collector:4318/v1/traces",
|
|
249
|
+
logsUrl: "http://collector:4318/v1/logs",
|
|
250
|
+
},
|
|
251
|
+
flushIntervalMs: 10_000,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const runtime = new Runtime({ env: obs.env, hooks: obs.hooks });
|
|
255
|
+
const client = makeDefaultHttpClient({
|
|
256
|
+
baseUrl: "https://api.example.com",
|
|
257
|
+
middleware: [withHttpObservability(obs)],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await runObservedHttpServerEffect(
|
|
261
|
+
obs,
|
|
262
|
+
{ method: "GET", route: "/users/:id" },
|
|
263
|
+
asyncSucceed("ok")
|
|
264
|
+
);
|
|
265
|
+
await runtime.toPromise(client.getText("/health"));
|
|
266
|
+
await obs.shutdown();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
HTTP client observability automatically reads adaptive limiter diagnostics when
|
|
270
|
+
the wrapped client owns a limiter, exposing gauges for current limit, queue
|
|
271
|
+
depth, utilization, error rate, request/completion rate, rejection rate, and
|
|
272
|
+
state count.
|
|
273
|
+
|
|
274
|
+
### Structured concurrency
|
|
93
275
|
|
|
94
276
|
```ts
|
|
95
|
-
import { Runtime,
|
|
96
|
-
import { httpClient } from "brass-runtime/http";
|
|
277
|
+
import { Runtime, asyncSucceed, withScope } from "brass-runtime";
|
|
97
278
|
|
|
98
|
-
|
|
279
|
+
const runtime = Runtime.make({});
|
|
99
280
|
|
|
100
|
-
|
|
281
|
+
await runtime.toPromise(
|
|
282
|
+
withScope(runtime, (scope) => {
|
|
283
|
+
scope.fork(asyncSucceed("child"));
|
|
284
|
+
// scope close interrupts children + runs finalizers
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
```
|
|
101
288
|
|
|
102
|
-
|
|
289
|
+
### Streams
|
|
103
290
|
|
|
104
|
-
|
|
105
|
-
|
|
291
|
+
```ts
|
|
292
|
+
import { Runtime, collectStream, fromArray, mapP, via } from "brass-runtime";
|
|
293
|
+
|
|
294
|
+
const runtime = Runtime.make({});
|
|
295
|
+
const numbers = fromArray([1, 2, 3, 4, 5]);
|
|
296
|
+
const doubled = via(numbers, mapP((n: number) => n * 2));
|
|
297
|
+
const result = await runtime.toPromise(collectStream(doubled));
|
|
298
|
+
// [2, 4, 6, 8, 10]
|
|
106
299
|
```
|
|
107
300
|
|
|
108
301
|
---
|
|
109
302
|
|
|
110
|
-
|
|
303
|
+
## Package exports
|
|
304
|
+
|
|
305
|
+
| Import | Purpose |
|
|
306
|
+
|--------|---------|
|
|
307
|
+
| `brass-runtime` | Core runtime: effects, fibers, scheduler, streams, layers |
|
|
308
|
+
| `brass-runtime/core` | Stable core surface (preferred for new code) |
|
|
309
|
+
| `brass-runtime/http` | Default HTTP client factory, lifecycle middleware, compression, batching, prewarm, adaptive limiter |
|
|
310
|
+
| `brass-runtime/http/testing` | Dependency-free mock clients, mock fetch, response factories, and effect runner helpers |
|
|
311
|
+
| `brass-runtime/schema` | Dependency-free runtime schema DSL with type inference |
|
|
312
|
+
| `brass-runtime/observability` | Prometheus/OTLP exporters, logs, spans, trace propagation, request adapters |
|
|
313
|
+
| `brass-runtime/agent` | Brass Agent core (experimental) |
|
|
111
314
|
|
|
112
|
-
|
|
315
|
+
CLI: `brass-agent`
|
|
113
316
|
|
|
114
|
-
|
|
317
|
+
---
|
|
115
318
|
|
|
116
|
-
|
|
117
|
-
- [Declarative optimized planning roadmap](./docs/agent-declarative-optimized-planning.md)
|
|
118
|
-
- [Brass Agent CLI](./docs/agent-cli.md)
|
|
119
|
-
- [Project intelligence](./docs/agent-project-intelligence.md)
|
|
120
|
-
- [Global usage and workspace discovery](./docs/agent-global-usage.md)
|
|
121
|
-
- [VS Code local install](./docs/agent-vscode-install.md)
|
|
122
|
-
- [VS Code auto-discovery](./docs/agent-vscode-auto-discovery.md)
|
|
123
|
-
- [VS Code model setup](./docs/agent-vscode-model-setup.md)
|
|
124
|
-
- [VS Code chat layout / focus mode](./docs/agent-vscode-chat-layout.md)
|
|
319
|
+
## HTTP middleware pipeline
|
|
125
320
|
|
|
126
|
-
|
|
127
|
-
npm run agent:vscode:install
|
|
128
|
-
# then open any repo in VS Code and use Brass Agent -> Chat
|
|
321
|
+
The lifecycle client composes middleware in this order (innermost to outermost):
|
|
129
322
|
|
|
130
|
-
npm run build
|
|
131
|
-
npm run agent:link
|
|
132
|
-
brass-agent --where
|
|
133
|
-
brass-agent --doctor
|
|
134
|
-
brass-agent --init
|
|
135
|
-
brass-agent --preset inspect
|
|
136
323
|
```
|
|
324
|
+
Wire → Priority → Retry → Cache → Batch → Dedup
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Adaptive limiting lives in the wire client, before lifecycle middleware.
|
|
328
|
+
`makeDefaultHttpClient` can then wrap the lifecycle stack with response
|
|
329
|
+
compression and caller middleware; caller middleware is outermost.
|
|
330
|
+
|
|
331
|
+
Each layer is independently optional. Set to `false` or omit to disable.
|
|
332
|
+
|
|
333
|
+
| Layer | What it does |
|
|
334
|
+
|-------|-------------|
|
|
335
|
+
| **Adaptive Limiter** | Gradient-based dynamic concurrency control per origin |
|
|
336
|
+
| **Priority** | Priority queue for request scheduling (0-9 levels) |
|
|
337
|
+
| **Retry** | Exponential backoff with circuit breaker awareness |
|
|
338
|
+
| **Cache** | LRU + TTL + stale-while-revalidate |
|
|
339
|
+
| **Batch** | Time-window request coalescing with split/distribute |
|
|
340
|
+
| **Dedup** | Ref-counted in-flight request deduplication |
|
|
341
|
+
| **Compression** | Transparent gzip/br/deflate decompression |
|
|
342
|
+
| **Prewarm** | Proactive TCP+TLS connection establishment |
|
|
343
|
+
|
|
344
|
+
All layers emit lifecycle events, track stats, and support cancellation.
|
|
345
|
+
|
|
346
|
+
The recommended `makeDefaultHttpClient` factory wires the default preset
|
|
347
|
+
for you and accepts extra middleware, so observability can be attached with
|
|
348
|
+
`middleware: [withHttpObservability(obs)]` without coupling HTTP to exporters.
|
|
137
349
|
|
|
138
350
|
---
|
|
139
351
|
|
|
140
|
-
|
|
352
|
+
## WASM engine
|
|
141
353
|
|
|
142
|
-
|
|
354
|
+
Optional Rust/WASM-backed components for strict execution:
|
|
143
355
|
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
- Bounded
|
|
147
|
-
-
|
|
356
|
+
- Fiber engine state machine
|
|
357
|
+
- Scheduler queue
|
|
358
|
+
- Bounded queues
|
|
359
|
+
- Permit pool
|
|
360
|
+
- Retry planner
|
|
148
361
|
|
|
149
|
-
|
|
362
|
+
```bash
|
|
363
|
+
npm run build:wasm # requires wasm-pack
|
|
364
|
+
```
|
|
150
365
|
|
|
151
|
-
|
|
152
|
-
|
|
366
|
+
The WASM engine never silently falls back to TypeScript — if you request WASM and it's unavailable, it fails explicitly.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Brass Agent (experimental)
|
|
371
|
+
|
|
372
|
+
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.
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npm run agent:vscode:install # VS Code extension
|
|
376
|
+
brass-agent --doctor # check setup
|
|
377
|
+
brass-agent --init # initialize workspace
|
|
378
|
+
brass-agent --preset inspect # run inspection
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
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)
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Testing
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
npm test # vitest suite
|
|
389
|
+
npm run test:types # TypeScript type checking
|
|
390
|
+
npm run test:coverage # coverage with baseline gate
|
|
391
|
+
npm run benchmark # runtime, HTTP lifecycle, and 100k local HTTP concurrency
|
|
392
|
+
npm run benchmark -- http-concurrent # HTTP compare mode variants
|
|
393
|
+
node --expose-gc --import tsx src/benchmarks/runner.ts http-concurrent # HTTP memory/limiter diagnostics
|
|
394
|
+
npm run benchmark:adaptive
|
|
395
|
+
npm run benchmark:adaptive:soak
|
|
396
|
+
npm run benchmark:http:budget
|
|
397
|
+
npm run benchmark:http:soak
|
|
398
|
+
npm run benchmark:observability
|
|
399
|
+
npm run benchmark:observability:budget
|
|
400
|
+
npm run smoke:observability:collector # requires local OTEL collector
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Property-based tests use `fast-check` with 100+ iterations per property. Each HTTP middleware has dedicated property tests verifying correctness invariants.
|
|
153
404
|
|
|
154
405
|
---
|
|
155
406
|
|
|
@@ -159,79 +410,100 @@ Examples:
|
|
|
159
410
|
- [Architecture](./docs/ARCHITECTURE.md)
|
|
160
411
|
- [Cancellation & Interruption](./docs/cancellation.md)
|
|
161
412
|
- [Observability: Hooks & Tracing](./docs/observability.md)
|
|
413
|
+
- [Observability framework examples](./docs/observability-framework-examples.md)
|
|
414
|
+
- [Observability collector smoke](./docs/observability-collector-smoke.md)
|
|
162
415
|
- [HTTP module](./docs/http.md)
|
|
163
|
-
- [
|
|
164
|
-
- [
|
|
165
|
-
- [
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## What’s new (recent changes)
|
|
170
|
-
|
|
171
|
-
- Stream buffering with backpressure (`buffer`)
|
|
172
|
-
- Abortable async integration (`fromPromiseAbortable`)
|
|
173
|
-
- Fiber-safe `toPromise` for examples & DX
|
|
174
|
-
- HTTP client module built on top of the runtime
|
|
416
|
+
- [Production readiness](./docs/production-readiness.md)
|
|
417
|
+
- [Streams guide](./docs/guides/streams.md)
|
|
418
|
+
- [Testing guide](./docs/guides/testing.md)
|
|
419
|
+
- [WASM engine](./docs/wasm-fiber-engine.md)
|
|
175
420
|
|
|
176
421
|
---
|
|
177
422
|
|
|
178
|
-
## Features
|
|
423
|
+
## Features
|
|
179
424
|
|
|
180
425
|
### Runtime (core)
|
|
181
426
|
|
|
182
|
-
- [x] Sync
|
|
183
|
-
- [x]
|
|
184
|
-
- [x] Cooperative
|
|
427
|
+
- [x] Sync effect values via `ZIO<R, E, A>` aliases
|
|
428
|
+
- [x] Algebraic async: `Async<R, E, A>`
|
|
429
|
+
- [x] Cooperative scheduler (observable, testable)
|
|
185
430
|
- [x] Fibers with interruption & finalizers
|
|
186
|
-
- [x] Structured
|
|
187
|
-
- [x]
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
- [x]
|
|
195
|
-
- [x]
|
|
196
|
-
- [x] Pipelines
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
- [
|
|
431
|
+
- [x] Structured scopes & resource safety
|
|
432
|
+
- [x] Layers, semaphores, circuit breakers
|
|
433
|
+
- [x] Metrics, tracing, runtime hooks
|
|
434
|
+
- [x] Worker pools
|
|
435
|
+
- [x] WASM engine (optional)
|
|
436
|
+
|
|
437
|
+
### Streams
|
|
438
|
+
|
|
439
|
+
- [x] Pull-based streams with backpressure
|
|
440
|
+
- [x] Bounded buffers, queues, hubs
|
|
441
|
+
- [x] Pipelines with fusion optimization
|
|
442
|
+
- [x] Stream merge, zip, broadcast
|
|
443
|
+
- [x] Chunks & batch operators
|
|
444
|
+
|
|
445
|
+
### HTTP
|
|
446
|
+
|
|
447
|
+
- [x] Lazy, cancelable HTTP client
|
|
448
|
+
- [x] Schema-validated JSON helpers
|
|
449
|
+
- [x] Discoverable builder API
|
|
450
|
+
- [x] Test helper subpath
|
|
451
|
+
- [x] Lifecycle client with middleware composition
|
|
452
|
+
- [x] Response cache (LRU + TTL + SWR)
|
|
453
|
+
- [x] Request deduplication (ref-counted)
|
|
454
|
+
- [x] Priority scheduling
|
|
455
|
+
- [x] Retry with exponential backoff
|
|
456
|
+
- [x] Response compression (gzip, br, deflate)
|
|
457
|
+
- [x] Request batching (time-window coalesce/split)
|
|
458
|
+
- [x] Connection pre-warming (probes, auto-refresh)
|
|
459
|
+
- [x] Adaptive concurrency (gradient-based)
|
|
460
|
+
- [x] Adaptive limiter presets, diagnostics, observability gauges, and soak benchmark
|
|
461
|
+
- [x] Circuit breaker
|
|
462
|
+
- [x] Tracing & validation
|
|
463
|
+
|
|
464
|
+
### Schema
|
|
465
|
+
|
|
466
|
+
- [x] Dependency-free schema DSL
|
|
467
|
+
- [x] Type inference via `InferSchema`
|
|
468
|
+
- [x] Object, array, record, union, enum, literal, custom schemas
|
|
469
|
+
- [x] Optional, nullable, refine, transform
|
|
470
|
+
- [x] Path-rich validation issues
|
|
471
|
+
|
|
472
|
+
### Observability
|
|
473
|
+
|
|
474
|
+
- [x] Runtime metrics sink and Prometheus exporter
|
|
475
|
+
- [x] OTLP JSON/HTTP exporters for metrics, traces, and logs
|
|
476
|
+
- [x] Structured logging with context propagation and redaction
|
|
477
|
+
- [x] W3C trace-context extract/inject helpers
|
|
478
|
+
- [x] Client and server HTTP observability helpers
|
|
479
|
+
- [x] Adaptive limiter metrics on HTTP client spans and Prometheus gauges
|
|
480
|
+
- [x] Sampling, force-sample-on-error, and bounded trace retention
|
|
481
|
+
- [x] Bounded exporter queues with retry, timeout, drop policy, and single-flight flush
|
|
482
|
+
- [x] Fetch/Node/Express/Fastify/Nest-style examples and collector smoke script
|
|
203
483
|
|
|
204
484
|
---
|
|
205
485
|
|
|
206
486
|
## Design notes
|
|
207
487
|
|
|
208
|
-
- **No hidden Promises
|
|
209
|
-
- **Deterministic execution
|
|
210
|
-
- **Resource safety is structural
|
|
211
|
-
- **
|
|
488
|
+
- **No hidden Promises** — async is always modeled explicitly via `Async`
|
|
489
|
+
- **Deterministic execution** — scheduler is observable and testable
|
|
490
|
+
- **Resource safety is structural** — scopes guarantee cleanup
|
|
491
|
+
- **Middleware composes via functions** — `(next: HttpClientFn) => HttpClientFn`
|
|
492
|
+
- **Cancellation propagates** — ref-counted through the entire middleware stack
|
|
493
|
+
- **Stats are frozen snapshots** — no mutable state leaks to consumers
|
|
212
494
|
|
|
213
495
|
---
|
|
214
496
|
|
|
215
497
|
## Contributing
|
|
216
498
|
|
|
217
|
-
- Runtime invariants matter — avoid sneaking Promises into semantics
|
|
218
|
-
- Prefer libraries on top of the runtime over changes in
|
|
219
|
-
-
|
|
499
|
+
- Runtime invariants matter — avoid sneaking Promises into core semantics
|
|
500
|
+
- Prefer libraries on top of the runtime over changes in core
|
|
501
|
+
- Add property tests when an invariant is broad
|
|
502
|
+
- Keep tests close to the changed module
|
|
503
|
+
- Small, focused PRs are welcome
|
|
220
504
|
|
|
221
505
|
---
|
|
222
506
|
|
|
223
507
|
## License
|
|
224
508
|
|
|
225
|
-
MIT
|
|
226
|
-
|
|
227
|
-
## Brass Agent local smoke tests
|
|
228
|
-
|
|
229
|
-
Run local smoke tests without CI or a real LLM provider:
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
npm run agent:test:local
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
This builds the project and runs a fake-LLM smoke test against the `brass-agent` CLI.
|
|
236
|
-
|
|
237
|
-
See also: [Agent language and workspace setup UX](docs/agent-language-workspace-ux.md).
|
|
509
|
+
MIT © 2025
|