brass-runtime 1.16.0 → 1.17.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.
Files changed (219) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +287 -23
  3. package/dist/agent/cli/main.cjs +38 -38
  4. package/dist/agent/cli/main.js +6 -6
  5. package/dist/agent/cli/main.mjs +6 -6
  6. package/dist/agent/index.cjs +7 -7
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.js +6 -6
  9. package/dist/agent/index.mjs +6 -6
  10. package/dist/chunk-2HQTDLHF.mjs +683 -0
  11. package/dist/chunk-36I3M4UC.mjs +370 -0
  12. package/dist/{chunk-QY5FKYEQ.js → chunk-3AYM6WPJ.js} +570 -51
  13. package/dist/chunk-3LOYJFRR.cjs +300 -0
  14. package/dist/chunk-3Y2RIUMM.js +300 -0
  15. package/dist/{chunk-7XOPAB5Q.js → chunk-4P2HHGAX.mjs} +83 -5
  16. package/dist/{chunk-N6VHMOWB.cjs → chunk-4ROBZFL6.cjs} +128 -128
  17. package/dist/{chunk-NC5SDRYE.js → chunk-52OB2ROS.js} +4 -4
  18. package/dist/{chunk-JX3LZQJH.cjs → chunk-52PPNNI4.cjs} +82 -20
  19. package/dist/{chunk-5YOQOXEQ.cjs → chunk-5EC274J5.cjs} +676 -293
  20. package/dist/chunk-5QC7LRZ3.js +229 -0
  21. package/dist/{chunk-7TL2LHQJ.js → chunk-5VRJNBLZ.mjs} +524 -141
  22. package/dist/chunk-62AZW6UT.cjs +313 -0
  23. package/dist/chunk-6IXXWIUM.js +683 -0
  24. package/dist/chunk-6RY2FFN4.mjs +2024 -0
  25. package/dist/chunk-74ZTY6CP.js +2871 -0
  26. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  27. package/dist/{chunk-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
  28. package/dist/chunk-7X3K5RMS.js +2024 -0
  29. package/dist/chunk-7ZPEZ57L.cjs +2024 -0
  30. package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
  31. package/dist/chunk-AGR5B2BC.cjs +683 -0
  32. package/dist/chunk-B33ICAKP.js +313 -0
  33. package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
  34. package/dist/{chunk-F5EUMJL7.mjs → chunk-BKK77SBA.js} +83 -5
  35. package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
  36. package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
  37. package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
  38. package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
  39. package/dist/chunk-EOC4UHBS.mjs +229 -0
  40. package/dist/chunk-F6XWZQY4.cjs +777 -0
  41. package/dist/{chunk-7LVI2GIN.js → chunk-FH2X7BVP.js} +507 -72
  42. package/dist/{chunk-OOGJ73B6.js → chunk-FHQGHPMO.mjs} +20 -10
  43. package/dist/{chunk-WQ5QNU5R.cjs → chunk-GLE2WY7Z.cjs} +652 -217
  44. package/dist/{chunk-G6IQOE4P.mjs → chunk-GYM3LLGS.mjs} +507 -72
  45. package/dist/{chunk-TVN5I4U6.cjs → chunk-JF5WGYJJ.cjs} +25 -24
  46. package/dist/{chunk-CY33PGEX.mjs → chunk-KH4SYAOS.mjs} +570 -51
  47. package/dist/chunk-KN32XNTH.mjs +313 -0
  48. package/dist/chunk-KQLYONSE.cjs +2871 -0
  49. package/dist/{chunk-7HUOJA4W.cjs → chunk-KZJQ723N.cjs} +90 -80
  50. package/dist/{chunk-CCKHV5BT.mjs → chunk-L2SYFEBS.js} +5 -4
  51. package/dist/{chunk-IJT6RRQ5.cjs → chunk-L6VB5N7Q.cjs} +20 -9
  52. package/dist/{chunk-ZGLD4TVZ.mjs → chunk-MBEJI5HF.mjs} +4 -4
  53. package/dist/{chunk-PRWCB3QL.mjs → chunk-MIIYDLGM.js} +524 -141
  54. package/dist/{chunk-H55LI6WY.js → chunk-MOO4L7F4.mjs} +15 -4
  55. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  56. package/dist/chunk-PD4EJTQC.cjs +229 -0
  57. package/dist/chunk-PWC3RBQE.mjs +300 -0
  58. package/dist/{chunk-MWXMNYJS.cjs → chunk-Q2I37RP3.cjs} +643 -124
  59. package/dist/{chunk-VFIUZG7J.mjs → chunk-RKGKFN2A.js} +79 -17
  60. package/dist/{chunk-NYL4D7SK.cjs → chunk-SA6HUJVI.cjs} +5 -5
  61. package/dist/chunk-SK7UZRNI.mjs +777 -0
  62. package/dist/{chunk-K2T3DV26.mjs → chunk-TRM4JUZQ.js} +15 -4
  63. package/dist/chunk-UB4B6OFY.js +370 -0
  64. package/dist/{chunk-G3XGCZDQ.js → chunk-UCUBNWM2.js} +1 -1
  65. package/dist/chunk-VWIPB6I5.js +777 -0
  66. package/dist/{chunk-JNFRRJYH.cjs → chunk-WBGRHGBP.cjs} +270 -192
  67. package/dist/{client-CtFmoDvM.d.ts → client-CZHU674n.d.ts} +211 -36
  68. package/dist/core/index.cjs +135 -9
  69. package/dist/core/index.d.ts +238 -33
  70. package/dist/core/index.js +155 -29
  71. package/dist/core/index.mjs +155 -29
  72. package/dist/{effect-CGNl5Rqp.d.ts → effect-DIUHZ9IN.d.ts} +89 -1
  73. package/dist/effectRunner-CFLC32IK.cjs +8 -0
  74. package/dist/{effectRunner-A4CHJXJI.js → effectRunner-L4S7IPT3.js} +2 -2
  75. package/dist/{effectRunner-OPUF6QRN.mjs → effectRunner-NNGG75QA.mjs} +2 -2
  76. package/dist/http/index.cjs +324 -2986
  77. package/dist/http/index.d.ts +54 -68
  78. package/dist/http/index.js +238 -2900
  79. package/dist/http/index.mjs +238 -2900
  80. package/dist/http/testing.cjs +14 -12
  81. package/dist/http/testing.d.ts +5 -4
  82. package/dist/http/testing.js +10 -8
  83. package/dist/http/testing.mjs +10 -8
  84. package/dist/index.cjs +423 -255
  85. package/dist/index.d.ts +87 -69
  86. package/dist/index.js +301 -133
  87. package/dist/index.mjs +301 -133
  88. package/dist/observability/index.cjs +18 -531
  89. package/dist/observability/index.d.ts +81 -8
  90. package/dist/observability/index.js +25 -538
  91. package/dist/observability/index.mjs +25 -538
  92. package/dist/perf/cli.cjs +401 -0
  93. package/dist/perf/cli.d.ts +1 -0
  94. package/dist/perf/cli.js +401 -0
  95. package/dist/perf/cli.mjs +401 -0
  96. package/dist/perf/index.cjs +141 -0
  97. package/dist/perf/index.d.ts +483 -0
  98. package/dist/perf/index.js +141 -0
  99. package/dist/perf/index.mjs +141 -0
  100. package/dist/schedule-CK3Ml_7p.d.ts +259 -0
  101. package/dist/schema/index.cjs +6 -2
  102. package/dist/schema/index.d.ts +3 -1
  103. package/dist/schema/index.js +5 -1
  104. package/dist/schema/index.mjs +5 -1
  105. package/dist/{server-C8hDXA74.d.ts → server-D6JZ15_e.d.ts} +16 -4
  106. package/dist/{stream-dvSs0QS5.d.ts → stream-B4oK9JFP.d.ts} +1 -1
  107. package/dist/{tracer-B5tRH9H7.d.ts → tracer-Hwt1cl7h.d.ts} +13 -54
  108. package/dist/{tracing-Dt9S_6V8.d.ts → tracing-DqbTKGcf.d.ts} +1 -1
  109. package/docs/ARCHITECTURE.md +292 -0
  110. package/docs/README.md +65 -0
  111. package/docs/adr/0001-ai-context-pack.md +32 -0
  112. package/docs/agent-apply-mode.md +104 -0
  113. package/docs/agent-approvals.md +110 -0
  114. package/docs/agent-batch.md +185 -0
  115. package/docs/agent-boundaries.md +112 -0
  116. package/docs/agent-chat-sessions.md +160 -0
  117. package/docs/agent-ci.md +17 -0
  118. package/docs/agent-cli.md +405 -0
  119. package/docs/agent-config.md +480 -0
  120. package/docs/agent-context-discovery.md +159 -0
  121. package/docs/agent-copilot-like-dx.md +126 -0
  122. package/docs/agent-declarative-optimized-planning.md +138 -0
  123. package/docs/agent-dx.md +224 -0
  124. package/docs/agent-env-files.md +126 -0
  125. package/docs/agent-follow-up-context.md +43 -0
  126. package/docs/agent-global-usage.md +180 -0
  127. package/docs/agent-init.md +109 -0
  128. package/docs/agent-install-and-configure.md +516 -0
  129. package/docs/agent-language-workspace-ux.md +99 -0
  130. package/docs/agent-llm-adapters.md +123 -0
  131. package/docs/agent-local-install.md +190 -0
  132. package/docs/agent-local-tests.md +51 -0
  133. package/docs/agent-observability.md +155 -0
  134. package/docs/agent-patch-quality-loop.md +162 -0
  135. package/docs/agent-presets.md +22 -0
  136. package/docs/agent-project-commands.md +237 -0
  137. package/docs/agent-project-intelligence.md +156 -0
  138. package/docs/agent-redaction.md +18 -0
  139. package/docs/agent-release-readiness.md +76 -0
  140. package/docs/agent-rollback-safety.md +162 -0
  141. package/docs/agent-rollback.md +23 -0
  142. package/docs/agent-run-artifacts.md +16 -0
  143. package/docs/agent-vscode-auto-discovery.md +137 -0
  144. package/docs/agent-vscode-batch-runner.md +100 -0
  145. package/docs/agent-vscode-chat-layout.md +90 -0
  146. package/docs/agent-vscode-clean-install.md +147 -0
  147. package/docs/agent-vscode-code-actions.md +70 -0
  148. package/docs/agent-vscode-diff-preview.md +45 -0
  149. package/docs/agent-vscode-inline-assist.md +56 -0
  150. package/docs/agent-vscode-install.md +186 -0
  151. package/docs/agent-vscode-model-setup.md +97 -0
  152. package/docs/agent-vscode-patch-preview.md +92 -0
  153. package/docs/agent-vscode-problems.md +79 -0
  154. package/docs/agent-vscode-project-dashboard.md +106 -0
  155. package/docs/agent-vscode-run-history.md +92 -0
  156. package/docs/agent-vscode-ux.md +73 -0
  157. package/docs/ai/INVARIANTS.md +84 -0
  158. package/docs/ai/PROJECT_MAP.md +338 -0
  159. package/docs/ai/PUBLIC_API.md +339 -0
  160. package/docs/ai/VALIDATION_MATRIX.md +67 -0
  161. package/docs/api-polish.md +37 -0
  162. package/docs/cancellation.md +162 -0
  163. package/docs/coverage.md +46 -0
  164. package/docs/framework-integrations.md +38 -0
  165. package/docs/frameworks/angular.md +153 -0
  166. package/docs/frameworks/express.md +125 -0
  167. package/docs/frameworks/fastify.md +124 -0
  168. package/docs/frameworks/nestjs.md +282 -0
  169. package/docs/frameworks/nextjs.md +147 -0
  170. package/docs/frameworks/react.md +139 -0
  171. package/docs/frameworks/vanilla.md +224 -0
  172. package/docs/getting-started.md +159 -0
  173. package/docs/guides/README.md +40 -0
  174. package/docs/guides/circuit-breaker.md +89 -0
  175. package/docs/guides/error-handling.md +91 -0
  176. package/docs/guides/getting-started.md +107 -0
  177. package/docs/guides/layers.md +189 -0
  178. package/docs/guides/metrics.md +101 -0
  179. package/docs/guides/resource-management.md +141 -0
  180. package/docs/guides/retry.md +215 -0
  181. package/docs/guides/semaphore.md +66 -0
  182. package/docs/guides/streams.md +117 -0
  183. package/docs/guides/supervisors.md +98 -0
  184. package/docs/guides/testing.md +162 -0
  185. package/docs/guides/tracing.md +71 -0
  186. package/docs/http-recipes.md +399 -0
  187. package/docs/http.md +749 -0
  188. package/docs/modules.md +285 -0
  189. package/docs/nestjs.md +6 -0
  190. package/docs/observability-collector-smoke.md +31 -0
  191. package/docs/observability-framework-examples.md +110 -0
  192. package/docs/observability.md +649 -0
  193. package/docs/otel-collector-smoke.yaml +27 -0
  194. package/docs/performance-profiler.md +199 -0
  195. package/docs/production-readiness.md +73 -0
  196. package/docs/recipes/README.md +12 -0
  197. package/docs/recipes/http-server.md +45 -0
  198. package/docs/recipes/layers.md +44 -0
  199. package/docs/recipes/performance.md +47 -0
  200. package/docs/recipes/runtime.md +41 -0
  201. package/docs/recipes/testing.md +41 -0
  202. package/docs/release.md +53 -0
  203. package/docs/wasm-bounded-queues.md +44 -0
  204. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  205. package/docs/wasm-fiber-engine.md +117 -0
  206. package/docs/wasm-scheduler-state-machine.md +122 -0
  207. package/docs/wasm-stream-chunks.md +54 -0
  208. package/package.json +22 -2
  209. package/dist/chunk-45F7OKGT.cjs +0 -104
  210. package/dist/chunk-7V4KY4RL.mjs +0 -104
  211. package/dist/chunk-DJQ7OMMB.cjs +0 -144
  212. package/dist/chunk-GOV47PPB.mjs +0 -552
  213. package/dist/chunk-JF4XXPZ5.cjs +0 -552
  214. package/dist/chunk-KCPT2D6G.js +0 -552
  215. package/dist/chunk-NOYZIMUJ.mjs +0 -144
  216. package/dist/chunk-PNVFW245.js +0 -144
  217. package/dist/chunk-ROJC3NBJ.js +0 -104
  218. package/dist/effectRunner-3ZHAD3LE.cjs +0 -8
  219. 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`.