brass-runtime 1.14.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 +410 -135
- package/dist/agent/cli/main.cjs +49 -43
- package/dist/agent/cli/main.js +11 -5
- package/dist/agent/cli/main.mjs +11 -5
- package/dist/agent/index.cjs +8 -3
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +7 -2
- package/dist/agent/index.mjs +7 -2
- package/dist/{chunk-BMRF4FN6.js → chunk-2WC63LJK.mjs} +68 -242
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-45F7OKGT.cjs +104 -0
- package/dist/chunk-5YOQOXEQ.cjs +2491 -0
- package/dist/chunk-7HUOJA4W.cjs +493 -0
- package/dist/{chunk-4N2JEK4H.mjs → chunk-7LVI2GIN.js} +252 -495
- 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-CY33PGEX.mjs +1110 -0
- package/dist/chunk-DJQ7OMMB.cjs +144 -0
- package/dist/chunk-F5EUMJL7.mjs +2143 -0
- package/dist/chunk-FM4W4QPL.js +193 -0
- package/dist/chunk-G3XGCZDQ.js +131 -0
- package/dist/{chunk-JT7D6M5H.js → chunk-G6IQOE4P.mjs} +252 -495
- package/dist/chunk-GOV47PPB.mjs +552 -0
- package/dist/chunk-H55LI6WY.js +93 -0
- package/dist/chunk-IJT6RRQ5.cjs +93 -0
- package/dist/chunk-J3H54ZRV.mjs +131 -0
- 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-XTMZTVIT.cjs → chunk-N6VHMOWB.cjs} +140 -134
- package/dist/{chunk-WJESVBWN.js → chunk-NC5SDRYE.js} +16 -10
- package/dist/chunk-NOYZIMUJ.mjs +144 -0
- package/dist/chunk-NYL4D7SK.cjs +131 -0
- package/dist/chunk-OBGZSXTJ.cjs +10 -0
- package/dist/{chunk-UWMMYKVK.mjs → chunk-OOGJ73B6.js} +68 -242
- package/dist/chunk-PNVFW245.js +144 -0
- package/dist/chunk-PRWCB3QL.mjs +2491 -0
- package/dist/chunk-QY5FKYEQ.js +1110 -0
- 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-BKBFSOGT.cjs → chunk-WQ5QNU5R.cjs} +460 -703
- package/dist/chunk-XDZOO4L5.js +354 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/{chunk-MQF7HZ7Y.mjs → chunk-ZGLD4TVZ.mjs} +16 -10
- package/dist/client-CtFmoDvM.d.ts +645 -0
- package/dist/core/index.cjs +284 -0
- package/dist/core/index.d.ts +567 -0
- package/dist/core/index.js +284 -0
- package/dist/core/index.mjs +284 -0
- package/dist/{effect-DM56H743.d.ts → effect-CGNl5Rqp.d.ts} +118 -11
- 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 +4130 -890
- package/dist/http/index.d.ts +2289 -219
- package/dist/http/index.js +4116 -876
- package/dist/http/index.mjs +4116 -876
- 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 +305 -1168
- package/dist/index.d.ts +9 -701
- package/dist/index.js +176 -1039
- package/dist/index.mjs +176 -1039
- 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-dvSs0QS5.d.ts +74 -0
- package/dist/tracer-B5tRH9H7.d.ts +230 -0
- package/dist/tracing-Dt9S_6V8.d.ts +148 -0
- package/package.json +37 -3
- package/dist/chunk-SKVY72E5.cjs +0 -667
- package/dist/stream-Oqe6WeLE.d.ts +0 -173
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,220 +34,476 @@ 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
|
-
|
|
45
|
+
```ts
|
|
46
|
+
import { Runtime, succeed } from "brass-runtime";
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
const runtime = Runtime.make({});
|
|
49
|
+
const value = await runtime.toPromise(succeed(42));
|
|
50
|
+
```
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
### Recommended HTTP client
|
|
53
|
+
|
|
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());
|
|
39
73
|
```
|
|
40
74
|
|
|
41
|
-
|
|
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.
|
|
42
80
|
|
|
43
|
-
|
|
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.
|
|
44
86
|
|
|
45
|
-
|
|
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:
|
|
46
93
|
|
|
47
94
|
```ts
|
|
48
|
-
import {
|
|
95
|
+
import { Schema } from "brass-runtime/schema";
|
|
49
96
|
|
|
50
|
-
const
|
|
97
|
+
const Config = Schema.object({
|
|
98
|
+
port: Schema.int({ min: 1 }),
|
|
99
|
+
callbackUrl: Schema.url(),
|
|
100
|
+
});
|
|
51
101
|
|
|
52
|
-
const
|
|
53
|
-
console.log(value); // 123
|
|
102
|
+
const config = Config.parse({ port: 3000, callbackUrl: "https://example.com/cb" });
|
|
54
103
|
```
|
|
55
104
|
|
|
56
|
-
|
|
105
|
+
Schemas can validate request bodies before the HTTP request is sent:
|
|
57
106
|
|
|
58
107
|
```ts
|
|
59
|
-
import {
|
|
108
|
+
import { Schema } from "brass-runtime/schema";
|
|
109
|
+
|
|
110
|
+
const CreateUser = Schema.object({
|
|
111
|
+
name: Schema.string({ minLength: 1 }),
|
|
112
|
+
});
|
|
60
113
|
|
|
61
|
-
|
|
114
|
+
await http.postJson(
|
|
115
|
+
"/users",
|
|
116
|
+
{ name: "Ada" },
|
|
117
|
+
{ bodySchema: CreateUser, schema: User }
|
|
118
|
+
).unsafeRunPromise();
|
|
119
|
+
```
|
|
62
120
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
126
|
+
|
|
127
|
+
```ts
|
|
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
|
+
```
|
|
141
|
+
|
|
142
|
+
### HTTP test helpers
|
|
143
|
+
|
|
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" }));
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Domain-specific layers still fit in the same config:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
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
|
+
},
|
|
67
169
|
});
|
|
170
|
+
|
|
171
|
+
const response = await http.getJson<User>("/users/1").unsafeRunPromise();
|
|
68
172
|
```
|
|
69
173
|
|
|
70
|
-
|
|
174
|
+
### Adaptive concurrency
|
|
71
175
|
|
|
72
|
-
|
|
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
|
+
}),
|
|
191
|
+
});
|
|
73
192
|
|
|
74
|
-
|
|
193
|
+
console.log(http.adaptiveLimiter?.dump());
|
|
194
|
+
console.log(http.adaptiveLimiter?.history("https://api.example.com"));
|
|
195
|
+
http.shutdown?.();
|
|
196
|
+
```
|
|
75
197
|
|
|
76
|
-
|
|
198
|
+
More end-to-end examples live in [`docs/http-recipes.md`](docs/http-recipes.md).
|
|
77
199
|
|
|
78
|
-
###
|
|
200
|
+
### Connection pre-warming
|
|
79
201
|
|
|
80
|
-
|
|
202
|
+
```ts
|
|
203
|
+
import { makePrewarmManager } from "brass-runtime/http";
|
|
81
204
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
205
|
+
const prewarm = makePrewarmManager({
|
|
206
|
+
origins: ["https://api.example.com", "https://cdn.example.com"],
|
|
207
|
+
keepAliveDurationMs: 55000,
|
|
208
|
+
autoRefresh: true,
|
|
209
|
+
});
|
|
86
210
|
|
|
87
|
-
|
|
211
|
+
await prewarm.warmAll();
|
|
212
|
+
// Subsequent requests skip TCP+TLS handshake
|
|
213
|
+
```
|
|
88
214
|
|
|
89
|
-
|
|
215
|
+
### Response compression
|
|
90
216
|
|
|
91
217
|
```ts
|
|
92
|
-
import {
|
|
93
|
-
import { httpClientStream } from "brass-runtime/http";
|
|
218
|
+
import { makeCompressionMiddleware, makeDefaultHttpClient } from "brass-runtime/http";
|
|
94
219
|
|
|
95
|
-
|
|
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
|
+
```
|
|
96
228
|
|
|
97
|
-
|
|
229
|
+
### Production observability
|
|
98
230
|
|
|
99
|
-
|
|
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
|
+
});
|
|
100
259
|
|
|
101
|
-
|
|
102
|
-
|
|
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();
|
|
103
267
|
```
|
|
104
268
|
|
|
105
|
-
|
|
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.
|
|
106
273
|
|
|
107
|
-
###
|
|
274
|
+
### Structured concurrency
|
|
108
275
|
|
|
109
|
-
|
|
276
|
+
```ts
|
|
277
|
+
import { Runtime, asyncSucceed, withScope } from "brass-runtime";
|
|
110
278
|
|
|
111
|
-
|
|
279
|
+
const runtime = Runtime.make({});
|
|
112
280
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- [VS Code model setup](./docs/agent-vscode-model-setup.md)
|
|
121
|
-
- [VS Code chat layout / focus mode](./docs/agent-vscode-chat-layout.md)
|
|
281
|
+
await runtime.toPromise(
|
|
282
|
+
withScope(runtime, (scope) => {
|
|
283
|
+
scope.fork(asyncSucceed("child"));
|
|
284
|
+
// scope close interrupts children + runs finalizers
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
```
|
|
122
288
|
|
|
123
|
-
|
|
124
|
-
npm run agent:vscode:install
|
|
125
|
-
# then open any repo in VS Code and use Brass Agent -> Chat
|
|
289
|
+
### Streams
|
|
126
290
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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]
|
|
133
299
|
```
|
|
134
300
|
|
|
135
301
|
---
|
|
136
302
|
|
|
137
|
-
|
|
303
|
+
## Package exports
|
|
138
304
|
|
|
139
|
-
|
|
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) |
|
|
140
314
|
|
|
141
|
-
|
|
142
|
-
- `Pull` semantics
|
|
143
|
-
- Bounded buffers
|
|
144
|
-
- Deterministic resource cleanup
|
|
315
|
+
CLI: `brass-agent`
|
|
145
316
|
|
|
146
|
-
|
|
317
|
+
---
|
|
147
318
|
|
|
148
|
-
|
|
149
|
-
- `src/examples/mergeStreamSync.ts`
|
|
319
|
+
## HTTP middleware pipeline
|
|
150
320
|
|
|
151
|
-
|
|
321
|
+
The lifecycle client composes middleware in this order (innermost to outermost):
|
|
152
322
|
|
|
153
|
-
|
|
323
|
+
```
|
|
324
|
+
Wire → Priority → Retry → Cache → Batch → Dedup
|
|
325
|
+
```
|
|
154
326
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
163
349
|
|
|
164
350
|
---
|
|
165
351
|
|
|
166
|
-
##
|
|
352
|
+
## WASM engine
|
|
353
|
+
|
|
354
|
+
Optional Rust/WASM-backed components for strict execution:
|
|
355
|
+
|
|
356
|
+
- Fiber engine state machine
|
|
357
|
+
- Scheduler queue
|
|
358
|
+
- Bounded queues
|
|
359
|
+
- Permit pool
|
|
360
|
+
- Retry planner
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
npm run build:wasm # requires wasm-pack
|
|
364
|
+
```
|
|
167
365
|
|
|
168
|
-
|
|
169
|
-
- Abortable async integration (`fromPromiseAbortable`)
|
|
170
|
-
- Fiber-safe `toPromise` for examples & DX
|
|
171
|
-
- HTTP client module built on top of the runtime
|
|
366
|
+
The WASM engine never silently falls back to TypeScript — if you request WASM and it's unavailable, it fails explicitly.
|
|
172
367
|
|
|
173
368
|
---
|
|
174
369
|
|
|
175
|
-
##
|
|
370
|
+
## Brass Agent (experimental)
|
|
176
371
|
|
|
177
|
-
|
|
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.
|
|
178
373
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
|
|
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
|
+
```
|
|
185
380
|
|
|
186
|
-
|
|
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)
|
|
187
382
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
- [x] Stream merge / zip
|
|
192
|
-
- [x] Hubs / Broadcast
|
|
193
|
-
- [x] Pipelines (`ZPipeline`-style)
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Testing
|
|
194
386
|
|
|
195
|
-
|
|
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
|
+
```
|
|
196
402
|
|
|
197
|
-
-
|
|
198
|
-
- [ ] Retry / timeout middleware
|
|
199
|
-
- [ ] Logging / metrics layers
|
|
403
|
+
Property-based tests use `fast-check` with 100+ iterations per property. Each HTTP middleware has dedicated property tests verifying correctness invariants.
|
|
200
404
|
|
|
201
405
|
---
|
|
202
406
|
|
|
203
|
-
##
|
|
407
|
+
## Docs
|
|
204
408
|
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
409
|
+
- [Getting Started](./docs/getting-started.md)
|
|
410
|
+
- [Architecture](./docs/ARCHITECTURE.md)
|
|
411
|
+
- [Cancellation & Interruption](./docs/cancellation.md)
|
|
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)
|
|
415
|
+
- [HTTP module](./docs/http.md)
|
|
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)
|
|
209
420
|
|
|
210
421
|
---
|
|
211
422
|
|
|
212
|
-
##
|
|
423
|
+
## Features
|
|
213
424
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
425
|
+
### Runtime (core)
|
|
426
|
+
|
|
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)
|
|
430
|
+
- [x] Fibers with interruption & finalizers
|
|
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
|
|
217
483
|
|
|
218
484
|
---
|
|
219
485
|
|
|
220
|
-
##
|
|
486
|
+
## Design notes
|
|
221
487
|
|
|
222
|
-
|
|
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
|
|
223
494
|
|
|
224
|
-
|
|
495
|
+
---
|
|
225
496
|
|
|
226
|
-
|
|
497
|
+
## Contributing
|
|
227
498
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
231
504
|
|
|
232
|
-
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## License
|
|
233
508
|
|
|
234
|
-
|
|
509
|
+
MIT © 2025
|