brass-runtime 1.16.0 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +283 -18
- package/dist/agent/cli/main.cjs +38 -38
- package/dist/agent/cli/main.js +6 -6
- package/dist/agent/cli/main.mjs +6 -6
- package/dist/agent/index.cjs +7 -7
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +6 -6
- package/dist/agent/index.mjs +6 -6
- package/dist/chunk-2HQTDLHF.mjs +683 -0
- package/dist/chunk-36I3M4UC.mjs +370 -0
- package/dist/{chunk-QY5FKYEQ.js → chunk-3AYM6WPJ.js} +570 -51
- package/dist/chunk-3LOYJFRR.cjs +300 -0
- package/dist/chunk-3Y2RIUMM.js +300 -0
- package/dist/{chunk-N6VHMOWB.cjs → chunk-4ROBZFL6.cjs} +128 -128
- package/dist/{chunk-NC5SDRYE.js → chunk-52OB2ROS.js} +4 -4
- package/dist/{chunk-JX3LZQJH.cjs → chunk-52PPNNI4.cjs} +82 -20
- package/dist/{chunk-5YOQOXEQ.cjs → chunk-5EC274J5.cjs} +676 -293
- package/dist/chunk-5QC7LRZ3.js +229 -0
- package/dist/{chunk-7TL2LHQJ.js → chunk-5VRJNBLZ.mjs} +524 -141
- 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-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
- package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
- package/dist/chunk-AGR5B2BC.cjs +683 -0
- package/dist/chunk-AVNQLJ5V.js +777 -0
- package/dist/chunk-B33ICAKP.js +313 -0
- package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
- package/dist/chunk-BABBZK4Y.js +2024 -0
- package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
- package/dist/{chunk-F5EUMJL7.mjs → chunk-CIZFIMK5.js} +55 -5
- package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
- package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
- package/dist/chunk-DNFO2EIZ.mjs +777 -0
- package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
- package/dist/{chunk-JNFRRJYH.cjs → chunk-ENKODRU3.cjs} +242 -192
- package/dist/chunk-EOC4UHBS.mjs +229 -0
- package/dist/{chunk-7LVI2GIN.js → chunk-FH2X7BVP.js} +507 -72
- package/dist/{chunk-OOGJ73B6.js → chunk-FHQGHPMO.mjs} +20 -10
- package/dist/{chunk-WQ5QNU5R.cjs → chunk-GLE2WY7Z.cjs} +652 -217
- package/dist/{chunk-G6IQOE4P.mjs → chunk-GYM3LLGS.mjs} +507 -72
- package/dist/chunk-HLWLMW2F.mjs +2024 -0
- package/dist/{chunk-TVN5I4U6.cjs → chunk-JF5WGYJJ.cjs} +25 -24
- package/dist/{chunk-CY33PGEX.mjs → chunk-KH4SYAOS.mjs} +570 -51
- package/dist/chunk-KN32XNTH.mjs +313 -0
- package/dist/chunk-KQLYONSE.cjs +2871 -0
- package/dist/{chunk-7HUOJA4W.cjs → chunk-KZJQ723N.cjs} +90 -80
- package/dist/{chunk-CCKHV5BT.mjs → chunk-L2SYFEBS.js} +5 -4
- package/dist/{chunk-IJT6RRQ5.cjs → chunk-L6VB5N7Q.cjs} +20 -9
- package/dist/{chunk-ZGLD4TVZ.mjs → chunk-MBEJI5HF.mjs} +4 -4
- package/dist/{chunk-PRWCB3QL.mjs → chunk-MIIYDLGM.js} +524 -141
- package/dist/{chunk-H55LI6WY.js → chunk-MOO4L7F4.mjs} +15 -4
- package/dist/{chunk-7XOPAB5Q.js → chunk-MT3OWDPC.mjs} +55 -5
- package/dist/chunk-MVGUEJ5Z.cjs +370 -0
- package/dist/chunk-PD4EJTQC.cjs +229 -0
- package/dist/chunk-PWC3RBQE.mjs +300 -0
- package/dist/{chunk-MWXMNYJS.cjs → chunk-Q2I37RP3.cjs} +643 -124
- package/dist/{chunk-VFIUZG7J.mjs → chunk-RKGKFN2A.js} +79 -17
- package/dist/{chunk-NYL4D7SK.cjs → chunk-SA6HUJVI.cjs} +5 -5
- package/dist/{chunk-K2T3DV26.mjs → chunk-TRM4JUZQ.js} +15 -4
- package/dist/chunk-UB4B6OFY.js +370 -0
- package/dist/{chunk-G3XGCZDQ.js → chunk-UCUBNWM2.js} +1 -1
- package/dist/chunk-VN44DYYT.cjs +2024 -0
- package/dist/{client-CtFmoDvM.d.ts → client-CZHU674n.d.ts} +211 -36
- package/dist/core/index.cjs +135 -9
- package/dist/core/index.d.ts +238 -33
- package/dist/core/index.js +155 -29
- package/dist/core/index.mjs +155 -29
- package/dist/{effect-CGNl5Rqp.d.ts → effect-DIUHZ9IN.d.ts} +89 -1
- package/dist/effectRunner-CFLC32IK.cjs +8 -0
- package/dist/{effectRunner-A4CHJXJI.js → effectRunner-L4S7IPT3.js} +2 -2
- package/dist/{effectRunner-OPUF6QRN.mjs → effectRunner-NNGG75QA.mjs} +2 -2
- package/dist/http/index.cjs +324 -2986
- package/dist/http/index.d.ts +54 -68
- package/dist/http/index.js +238 -2900
- package/dist/http/index.mjs +238 -2900
- package/dist/http/testing.cjs +14 -12
- package/dist/http/testing.d.ts +5 -4
- package/dist/http/testing.js +10 -8
- package/dist/http/testing.mjs +10 -8
- package/dist/index.cjs +423 -255
- package/dist/index.d.ts +87 -69
- package/dist/index.js +301 -133
- package/dist/index.mjs +301 -133
- package/dist/observability/index.cjs +16 -531
- package/dist/observability/index.d.ts +81 -8
- package/dist/observability/index.js +23 -538
- package/dist/observability/index.mjs +23 -538
- 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 +6 -2
- package/dist/schema/index.d.ts +3 -1
- package/dist/schema/index.js +5 -1
- package/dist/schema/index.mjs +5 -1
- package/dist/{server-C8hDXA74.d.ts → server-GJPg8ZSG.d.ts} +4 -3
- package/dist/{stream-dvSs0QS5.d.ts → stream-B4oK9JFP.d.ts} +1 -1
- package/dist/{tracer-B5tRH9H7.d.ts → tracer-Hwt1cl7h.d.ts} +13 -54
- package/dist/{tracing-Dt9S_6V8.d.ts → tracing-DqbTKGcf.d.ts} +1 -1
- 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 +22 -2
- package/dist/chunk-45F7OKGT.cjs +0 -104
- package/dist/chunk-7V4KY4RL.mjs +0 -104
- package/dist/chunk-DJQ7OMMB.cjs +0 -144
- package/dist/chunk-GOV47PPB.mjs +0 -552
- package/dist/chunk-JF4XXPZ5.cjs +0 -552
- package/dist/chunk-KCPT2D6G.js +0 -552
- package/dist/chunk-NOYZIMUJ.mjs +0 -144
- package/dist/chunk-PNVFW245.js +0 -144
- package/dist/chunk-ROJC3NBJ.js +0 -104
- package/dist/effectRunner-3ZHAD3LE.cjs +0 -8
- package/dist/schedule-Fque9Abz.d.ts +0 -70
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# HTTP Recipes
|
|
2
|
+
|
|
3
|
+
Practical recipes for the `brass-runtime/http` stack.
|
|
4
|
+
|
|
5
|
+
## Typed API Client
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { makeDefaultHttpClient, s, type InferSchema } from "brass-runtime/http";
|
|
9
|
+
|
|
10
|
+
const User = s.object({
|
|
11
|
+
id: s.int(),
|
|
12
|
+
email: s.email(),
|
|
13
|
+
name: s.nonEmptyString(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const CreateUser = s.object({
|
|
17
|
+
email: s.email(),
|
|
18
|
+
name: s.nonEmptyString(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const http = makeDefaultHttpClient({
|
|
22
|
+
baseUrl: "https://api.example.com",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const usersApi = {
|
|
26
|
+
get: (id: number) =>
|
|
27
|
+
http.getJson(`/users/${id}`, { schema: User }),
|
|
28
|
+
|
|
29
|
+
create: (body: InferSchema<typeof CreateUser>) =>
|
|
30
|
+
http.postJson("/users", body, {
|
|
31
|
+
bodySchema: CreateUser,
|
|
32
|
+
schema: User,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`bodySchema` validates before the request is sent; `schema` validates the
|
|
38
|
+
response after JSON parsing.
|
|
39
|
+
|
|
40
|
+
## Axios Or Internal Transport
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { makeDefaultHttpClient, promiseHttpTransport } from "brass-runtime/http";
|
|
44
|
+
|
|
45
|
+
const transport = promiseHttpTransport()
|
|
46
|
+
.requestConfig(({ request, url }) => ({
|
|
47
|
+
url: url.toString(),
|
|
48
|
+
method: request.method,
|
|
49
|
+
headers: request.headers,
|
|
50
|
+
data: request.body,
|
|
51
|
+
responseType: "json",
|
|
52
|
+
}))
|
|
53
|
+
.send((config) => axiosInstance.request(config))
|
|
54
|
+
.json();
|
|
55
|
+
|
|
56
|
+
const http = makeDefaultHttpClient({
|
|
57
|
+
baseUrl: "https://api.example.com",
|
|
58
|
+
transport,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await http.getJson("/users", {
|
|
62
|
+
policy: {
|
|
63
|
+
dedupKey: "users:list",
|
|
64
|
+
priority: 1,
|
|
65
|
+
poolKey: "users-api",
|
|
66
|
+
},
|
|
67
|
+
}).unsafeRunPromise();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`requestConfig(...).send(...)` injects the runtime `AbortSignal` into object
|
|
71
|
+
configs automatically. `.json()` infers Axios/Fetch-shaped responses. Use
|
|
72
|
+
`.json((res) => res.payload, (res) => ({ status: res.code }))` when the
|
|
73
|
+
external client uses a different response shape.
|
|
74
|
+
|
|
75
|
+
## Named Policy Presets
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import {
|
|
79
|
+
defineHttpPolicyPresets,
|
|
80
|
+
httpPolicy,
|
|
81
|
+
makeDefaultHttpClient,
|
|
82
|
+
} from "brass-runtime/http";
|
|
83
|
+
|
|
84
|
+
const policies = defineHttpPolicyPresets({
|
|
85
|
+
readModel: {
|
|
86
|
+
lane: "read-model",
|
|
87
|
+
poolKey: "users-api",
|
|
88
|
+
priority: 2,
|
|
89
|
+
retry: { maxRetries: 2, baseDelayMs: 50 },
|
|
90
|
+
},
|
|
91
|
+
writes: httpPolicy.lane("write-path", {
|
|
92
|
+
poolKey: "users-api",
|
|
93
|
+
priority: 1,
|
|
94
|
+
retry: false,
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const http = makeDefaultHttpClient({
|
|
99
|
+
baseUrl: "https://api.example.com",
|
|
100
|
+
policyPresets: policies,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await http.getJson("/users/1", {
|
|
104
|
+
policy: "readModel",
|
|
105
|
+
}).unsafeRunPromise();
|
|
106
|
+
|
|
107
|
+
await http.postJson("/users", { name: "Ada" }, {
|
|
108
|
+
policy: { preset: "writes", dedupKey: "users:create" },
|
|
109
|
+
}).unsafeRunPromise();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`policy: "readModel"` is shorthand for a named lane.
|
|
113
|
+
`policy: { preset: "writes", ...overrides }` starts from the preset and lets
|
|
114
|
+
the request override fields such as `dedupKey`, `priority`, or `retry`.
|
|
115
|
+
|
|
116
|
+
## Mock HTTP In Tests
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { describe, expect, it } from "vitest";
|
|
120
|
+
import { makeJsonHttpResponse, makeMockHttpClient, runHttpEffect } from "brass-runtime/http/testing";
|
|
121
|
+
|
|
122
|
+
describe("usersApi", () => {
|
|
123
|
+
it("reads typed JSON", async () => {
|
|
124
|
+
const http = makeMockHttpClient(() =>
|
|
125
|
+
makeJsonHttpResponse({
|
|
126
|
+
id: 1,
|
|
127
|
+
email: "ada@example.com",
|
|
128
|
+
name: "Ada",
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const res = await runHttpEffect(http({
|
|
133
|
+
method: "GET",
|
|
134
|
+
url: "https://api.example.com/users/1",
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
expect(JSON.parse(res.bodyText).name).toBe("Ada");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Retry + Adaptive Limiter + Circuit Breaker
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { makeAdaptiveLimiterConfig, makeHttp, withCircuitBreaker } from "brass-runtime/http";
|
|
146
|
+
|
|
147
|
+
const http = makeHttp({
|
|
148
|
+
baseUrl: "https://api.example.com",
|
|
149
|
+
adaptiveLimiter: makeAdaptiveLimiterConfig("balanced", {
|
|
150
|
+
maxLimit: 256,
|
|
151
|
+
warmupRequests: 25,
|
|
152
|
+
rejectionBackoffMs: 100,
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const protectedHttp = http.with(withCircuitBreaker({
|
|
157
|
+
failureThreshold: 5,
|
|
158
|
+
resetTimeoutMs: 30_000,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
const res = await protectedHttp({ method: "GET", url: "/health" }).unsafeRunPromise();
|
|
162
|
+
|
|
163
|
+
console.log(res.status);
|
|
164
|
+
console.log(http.adaptiveLimiter?.dump());
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
When the breaker opens, it can notify the adaptive limiter so the affected key
|
|
168
|
+
falls back to `minLimit` immediately.
|
|
169
|
+
|
|
170
|
+
## Default Client With Limiter Diagnostics
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { toPromise } from "brass-runtime";
|
|
174
|
+
import { makeDefaultHttpClient } from "brass-runtime/http";
|
|
175
|
+
import { makeObservability, withHttpObservability } from "brass-runtime/observability";
|
|
176
|
+
|
|
177
|
+
const obs = makeObservability({ serviceName: "users-api" });
|
|
178
|
+
|
|
179
|
+
const http = makeDefaultHttpClient({
|
|
180
|
+
baseUrl: "https://api.example.com",
|
|
181
|
+
adaptiveLimiter: { preset: "conservative", maxLimit: 80 },
|
|
182
|
+
middleware: [
|
|
183
|
+
withHttpObservability({
|
|
184
|
+
metrics: obs.metrics,
|
|
185
|
+
route: "/users/:id",
|
|
186
|
+
adaptiveLimiter: { includeKeyLabel: false },
|
|
187
|
+
policy: { labelKeys: ["preset", "lane", "poolKey"] },
|
|
188
|
+
}),
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await http.getJson("/users/1", {
|
|
193
|
+
schema: User,
|
|
194
|
+
policy: { preset: "readModel", lane: "read-model", poolKey: "users-api" },
|
|
195
|
+
}).unsafeRunPromise();
|
|
196
|
+
|
|
197
|
+
console.log(obs.metrics.snapshot());
|
|
198
|
+
console.log(http.wire.adaptiveLimiter?.dump());
|
|
199
|
+
await toPromise(http.shutdown(), {});
|
|
200
|
+
await obs.shutdown();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`withHttpObservability` records adaptive limiter gauges when the wrapped client
|
|
204
|
+
owns a limiter. Keep `includeKeyLabel` disabled unless your limiter keys are
|
|
205
|
+
low-cardinality and stable.
|
|
206
|
+
Request policy is always available in logs and span attributes. Add
|
|
207
|
+
`policy.labelKeys` only for stable, low-cardinality policy fields you want in
|
|
208
|
+
Prometheus labels.
|
|
209
|
+
|
|
210
|
+
## Transport Error Mapping
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import {
|
|
214
|
+
formatHttpError,
|
|
215
|
+
isRetryableHttpError,
|
|
216
|
+
toHttpError,
|
|
217
|
+
} from "brass-runtime/http";
|
|
218
|
+
|
|
219
|
+
const mapped = toHttpError(axiosError);
|
|
220
|
+
|
|
221
|
+
if (isRetryableHttpError(mapped)) {
|
|
222
|
+
console.warn("transient HTTP failure", formatHttpError(mapped));
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`toHttpError` understands already-tagged `HttpError`s, `AbortError`,
|
|
227
|
+
common timeout codes such as `ECONNABORTED`, and Axios-like
|
|
228
|
+
`response.status` / `response.statusText`. Promise transports use it by
|
|
229
|
+
default.
|
|
230
|
+
|
|
231
|
+
## Observability + Schema Errors
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import {
|
|
235
|
+
formatHttpError,
|
|
236
|
+
isValidationError,
|
|
237
|
+
makeDefaultHttpClient,
|
|
238
|
+
s,
|
|
239
|
+
} from "brass-runtime/http";
|
|
240
|
+
|
|
241
|
+
const Payload = s.object({ ok: s.boolean() });
|
|
242
|
+
const http = makeDefaultHttpClient({ baseUrl: "https://api.example.com" });
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
await http.getJson("/payload", { schema: Payload }).unsafeRunPromise();
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (isValidationError(error)) {
|
|
248
|
+
console.error(error.phase, error.issues);
|
|
249
|
+
}
|
|
250
|
+
console.error(formatHttpError(error));
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Config Validation On Startup
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { toPromise } from "brass-runtime";
|
|
258
|
+
import { makeDefaultHttpClient } from "brass-runtime/http";
|
|
259
|
+
import { ConfigValidationError } from "brass-runtime/schema";
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const http = makeDefaultHttpClient({
|
|
263
|
+
preset: "production",
|
|
264
|
+
adaptiveLimiter: {
|
|
265
|
+
minLimit: 4,
|
|
266
|
+
probeJitterRatio: 0.2,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
await toPromise(http.shutdown(), {});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error instanceof ConfigValidationError) {
|
|
273
|
+
console.error(error.configName, error.issues);
|
|
274
|
+
}
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Production Adoption Story
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { toPromise } from "brass-runtime";
|
|
283
|
+
import {
|
|
284
|
+
defineHttpPolicyPresets,
|
|
285
|
+
formatHttpError,
|
|
286
|
+
isRetryableHttpError,
|
|
287
|
+
makeDefaultHttpClient,
|
|
288
|
+
promiseHttpTransport,
|
|
289
|
+
s,
|
|
290
|
+
} from "brass-runtime/http";
|
|
291
|
+
import { makeObservability, withHttpObservability } from "brass-runtime/observability";
|
|
292
|
+
|
|
293
|
+
const User = s.object({
|
|
294
|
+
id: s.int(),
|
|
295
|
+
email: s.email(),
|
|
296
|
+
name: s.nonEmptyString(),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const transport = promiseHttpTransport()
|
|
300
|
+
.requestConfig(({ request, url }) => ({
|
|
301
|
+
url: url.toString(),
|
|
302
|
+
method: request.method,
|
|
303
|
+
headers: request.headers,
|
|
304
|
+
data: request.body,
|
|
305
|
+
responseType: "json",
|
|
306
|
+
}))
|
|
307
|
+
.send((config) => axiosInstance.request(config))
|
|
308
|
+
.json();
|
|
309
|
+
|
|
310
|
+
const policies = defineHttpPolicyPresets({
|
|
311
|
+
readModel: {
|
|
312
|
+
lane: "read-model",
|
|
313
|
+
poolKey: "users-api",
|
|
314
|
+
priority: 2,
|
|
315
|
+
retry: { maxRetries: 2, baseDelayMs: 50 },
|
|
316
|
+
},
|
|
317
|
+
writes: {
|
|
318
|
+
lane: "write-path",
|
|
319
|
+
poolKey: "users-api",
|
|
320
|
+
priority: 1,
|
|
321
|
+
retry: false,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const obs = makeObservability({
|
|
326
|
+
serviceName: "users-bff",
|
|
327
|
+
logs: { minLevel: "info" },
|
|
328
|
+
cardinality: { maxValuesPerLabel: 100 },
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const http = makeDefaultHttpClient({
|
|
332
|
+
preset: "production",
|
|
333
|
+
baseUrl: "https://api.example.com",
|
|
334
|
+
transport,
|
|
335
|
+
policyPresets: policies,
|
|
336
|
+
middleware: [
|
|
337
|
+
withHttpObservability({
|
|
338
|
+
metrics: obs.metrics,
|
|
339
|
+
route: "/users/:id",
|
|
340
|
+
policy: { labelKeys: ["preset", "lane", "poolKey"] },
|
|
341
|
+
}),
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const user = await http.getJson("/users/1", {
|
|
347
|
+
schema: User,
|
|
348
|
+
policy: { preset: "readModel", dedupKey: "users:1" },
|
|
349
|
+
}).unsafeRunPromise();
|
|
350
|
+
|
|
351
|
+
console.log(user.body.email);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (isRetryableHttpError(error)) {
|
|
354
|
+
console.warn("transient upstream failure", formatHttpError(error));
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
} finally {
|
|
358
|
+
await toPromise(http.shutdown(), {});
|
|
359
|
+
await obs.shutdown();
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`preset: "production"` is the explicit name for the full default stack:
|
|
364
|
+
timeout, priority, retry, dedup, adaptive limiter, safe-method response cache,
|
|
365
|
+
response compression, stats, and shutdown. `preset: "default"` is the same
|
|
366
|
+
stack kept for compatibility.
|
|
367
|
+
|
|
368
|
+
Construction-time validation catches invalid setup before traffic starts:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
makeDefaultHttpClient({
|
|
372
|
+
preset: "production",
|
|
373
|
+
policyPresets: {
|
|
374
|
+
readModel: { priority: 99 },
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
// throws ConfigValidationError at path: policyPresets.readModel.priority
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Operational Checklist
|
|
381
|
+
|
|
382
|
+
- Use `preset: "production"` for the full managed HTTP stack.
|
|
383
|
+
- Define `policyPresets` for stable business intent: read paths, writes,
|
|
384
|
+
partner APIs, search, or backoffice calls.
|
|
385
|
+
- Set `poolKey` per downstream isolation boundary, not per URL.
|
|
386
|
+
- Keep `dedupKey` only for safe/idempotent reads where coalescing is correct.
|
|
387
|
+
- Use `retry: false` for non-idempotent writes unless the operation has an
|
|
388
|
+
idempotency key.
|
|
389
|
+
- Keep metric labels low-cardinality: usually `preset`, `lane`, and `poolKey`.
|
|
390
|
+
- Leave `dedupKey` out of labels unless the key space is tiny and bounded.
|
|
391
|
+
- Watch `brass_http_client_requests_total`,
|
|
392
|
+
`brass_http_client_duration_ms`, `brass_http_client_in_flight`, and
|
|
393
|
+
`brass_http_adaptive_limiter_*`.
|
|
394
|
+
- Use `HTTP_OBSERVABILITY_CONTRACT` for dashboard names instead of copying
|
|
395
|
+
strings by hand.
|
|
396
|
+
- Call `shutdown()` on the HTTP client and observability setup during process
|
|
397
|
+
shutdown.
|
|
398
|
+
- Before release, run `npm run test:types`, focused HTTP/observability tests,
|
|
399
|
+
`npm run build`, and `npm run validate:cjs`.
|