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,38 @@
1
+ # Framework integrations
2
+
3
+ These recipes show how to wire Brass into common TypeScript application
4
+ frameworks without adding framework-specific dependencies to `brass-runtime`.
5
+
6
+ The common shape is:
7
+
8
+ - create one `Observability` instance near the application boundary;
9
+ - create one `Runtime` when framework handlers need to run effects;
10
+ - create one `makeDefaultHttpClient(...)` with `withHttpObservability(...)`;
11
+ - keep collector/vendor config in the application;
12
+ - shut down HTTP and observability queues from the host lifecycle when possible.
13
+
14
+ For browser apps, never expose Grafana Cloud tokens or collector secrets. Send
15
+ browser telemetry to a same-origin proxy such as `/api/otel`, then forward to
16
+ Grafana, Alloy, AppDynamics, or OpenTelemetry Collector from a trusted server.
17
+
18
+ ## Recipes
19
+
20
+ | Framework | Recipe | Covers |
21
+ |-----------|--------|--------|
22
+ | Vanilla | [`docs/frameworks/vanilla.md`](./frameworks/vanilla.md) | Browser and Node setup without a framework |
23
+ | React | [`docs/frameworks/react.md`](./frameworks/react.md) | Context provider, hook, component usage |
24
+ | Next.js | [`docs/frameworks/nextjs.md`](./frameworks/nextjs.md) | App Router, server singleton, OTLP proxy |
25
+ | Angular | [`docs/frameworks/angular.md`](./frameworks/angular.md) | InjectionToken providers and services |
26
+ | Express | [`docs/frameworks/express.md`](./frameworks/express.md) | Request spans, `/metrics`, shutdown |
27
+ | Fastify | [`docs/frameworks/fastify.md`](./frameworks/fastify.md) | Request adapter, `/metrics`, shutdown |
28
+ | NestJS | [`docs/frameworks/nestjs.md`](./frameworks/nestjs.md) | Module providers, DI tokens, shutdown hooks |
29
+
30
+ Runnable dependency-optional examples live in:
31
+
32
+ - `src/examples/observabilityExpress.ts`
33
+ - `src/examples/observabilityFastify.ts`
34
+ - `src/examples/observabilityNest.ts`
35
+
36
+ See also [`docs/observability-framework-examples.md`](./observability-framework-examples.md)
37
+ for commands that run those examples locally.
38
+
@@ -0,0 +1,153 @@
1
+ # Angular integration
2
+
3
+ Angular apps should expose Brass through `InjectionToken`s. Browser telemetry
4
+ should go to a same-origin proxy such as `/api/otel`; collector credentials
5
+ belong on the server side.
6
+
7
+ ## Providers
8
+
9
+ ```ts
10
+ // brass.providers.ts
11
+ import { inject, InjectionToken, type Provider } from "@angular/core";
12
+ import { Runtime } from "brass-runtime/core";
13
+ import {
14
+ defineHttpPolicyPresets,
15
+ makeDefaultHttpClient,
16
+ } from "brass-runtime/http";
17
+ import {
18
+ makeObservability,
19
+ makeOtlpOptions,
20
+ withHttpObservability,
21
+ } from "brass-runtime/observability";
22
+
23
+ const policyPresets = defineHttpPolicyPresets({
24
+ readModel: {
25
+ lane: "read-model",
26
+ priority: 3,
27
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
28
+ },
29
+ command: {
30
+ lane: "command",
31
+ priority: 1,
32
+ retry: false,
33
+ },
34
+ });
35
+
36
+ function makeAngularBrass() {
37
+ const observability = makeObservability({
38
+ serviceName: "shop-angular",
39
+ resource: { "deployment.environment": "browser" },
40
+ logs: false,
41
+ sampling: { ratio: 0.1, respectRemoteSampled: true, forceSampleOnError: true },
42
+ redaction: {},
43
+ cardinality: { maxValuesPerLabel: 100 },
44
+ otlp: makeOtlpOptions({
45
+ endpoint: "/api/otel",
46
+ timeoutMs: 10_000,
47
+ retry: { attempts: 2, initialDelayMs: 100, maxDelayMs: 1_000 },
48
+ pipeline: { maxQueueSize: 2_000, batchSize: 128, dropPolicy: "drop-oldest" },
49
+ }),
50
+ flushIntervalMs: 15_000,
51
+ autoStart: true,
52
+ });
53
+
54
+ const runtime = new Runtime({
55
+ env: observability.env,
56
+ hooks: observability.hooks,
57
+ });
58
+
59
+ const http = makeDefaultHttpClient({
60
+ baseUrl: "/api",
61
+ preset: "balanced",
62
+ timeoutMs: 5_000,
63
+ policyPresets,
64
+ middleware: [withHttpObservability(observability)],
65
+ });
66
+
67
+ return {
68
+ observability,
69
+ runtime,
70
+ http,
71
+ shutdown: async () => {
72
+ await http.shutdown();
73
+ await observability.shutdown();
74
+ },
75
+ };
76
+ }
77
+
78
+ export const BRASS = new InjectionToken<ReturnType<typeof makeAngularBrass>>("BRASS");
79
+
80
+ export function provideBrass(): Provider[] {
81
+ return [
82
+ {
83
+ provide: BRASS,
84
+ useFactory: () => makeAngularBrass(),
85
+ },
86
+ ];
87
+ }
88
+
89
+ export function injectBrass() {
90
+ return inject(BRASS);
91
+ }
92
+ ```
93
+
94
+ Register the provider:
95
+
96
+ ```ts
97
+ // app.config.ts
98
+ import { type ApplicationConfig } from "@angular/core";
99
+ import { provideBrass } from "./brass.providers";
100
+
101
+ export const appConfig: ApplicationConfig = {
102
+ providers: [
103
+ ...provideBrass(),
104
+ ],
105
+ };
106
+ ```
107
+
108
+ ## Service Usage
109
+
110
+ ```ts
111
+ // users.service.ts
112
+ import { Injectable } from "@angular/core";
113
+ import { injectBrass } from "./brass.providers";
114
+
115
+ type User = {
116
+ readonly id: string;
117
+ readonly name: string;
118
+ };
119
+
120
+ @Injectable({ providedIn: "root" })
121
+ export class UsersService {
122
+ private readonly brass = injectBrass();
123
+
124
+ getUser(id: string): Promise<User> {
125
+ return this.brass.runtime
126
+ .toPromise(
127
+ this.brass.http.getJson<User>(`/users/${id}`, {
128
+ policy: "readModel",
129
+ timeoutMs: 2_000,
130
+ }),
131
+ )
132
+ .then((response) => response.body);
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## Shutdown
138
+
139
+ Browser apps often do not have a reliable shutdown hook, but you can flush on
140
+ page lifecycle events:
141
+
142
+ ```ts
143
+ import { injectBrass } from "./brass.providers";
144
+
145
+ export function installBrassBrowserShutdown() {
146
+ const brass = injectBrass();
147
+
148
+ window.addEventListener("pagehide", () => {
149
+ void brass.shutdown();
150
+ });
151
+ }
152
+ ```
153
+
@@ -0,0 +1,125 @@
1
+ # Express integration
2
+
3
+ Express can wire Brass directly at process startup: one observability instance,
4
+ one runtime, one HTTP client, and a shutdown handler.
5
+
6
+ ## App Setup
7
+
8
+ ```ts
9
+ import express from "express";
10
+ import { asyncFlatMap } from "brass-runtime/core";
11
+ import {
12
+ defineHttpPolicyPresets,
13
+ makeDefaultHttpClient,
14
+ } from "brass-runtime/http";
15
+ import {
16
+ logEffect,
17
+ makeExpressRequestObservabilityContext,
18
+ makeObservability,
19
+ makeOtlpOptions,
20
+ withHttpObservability,
21
+ } from "brass-runtime/observability";
22
+
23
+ const policyPresets = defineHttpPolicyPresets({
24
+ readModel: {
25
+ lane: "read-model",
26
+ priority: 3,
27
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
28
+ },
29
+ command: {
30
+ lane: "command",
31
+ priority: 1,
32
+ retry: false,
33
+ },
34
+ });
35
+
36
+ const observability = makeObservability({
37
+ serviceName: process.env.OTEL_SERVICE_NAME ?? "shop-express",
38
+ serviceVersion: process.env.OTEL_SERVICE_VERSION,
39
+ resource: {
40
+ "deployment.environment": process.env.NODE_ENV ?? "development",
41
+ },
42
+ logs: { minLevel: "info" },
43
+ sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
44
+ redaction: {},
45
+ cardinality: { maxValuesPerLabel: 100 },
46
+ otlp: makeOtlpOptions({
47
+ endpoint: process.env.GRAFANA_OTLP_ENDPOINT ?? "http://grafana-alloy:4318",
48
+ headers: process.env.GRAFANA_OTLP_AUTHORIZATION
49
+ ? { Authorization: process.env.GRAFANA_OTLP_AUTHORIZATION }
50
+ : undefined,
51
+ timeoutMs: 10_000,
52
+ retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
53
+ pipeline: {
54
+ maxQueueSize: 10_000,
55
+ batchSize: 512,
56
+ dropPolicy: "drop-oldest",
57
+ shutdownTimeoutMs: 10_000,
58
+ },
59
+ }),
60
+ flushIntervalMs: 10_000,
61
+ autoStart: true,
62
+ });
63
+
64
+ const http = makeDefaultHttpClient({
65
+ baseUrl: process.env.USERS_API_BASE_URL ?? "https://users-api.internal",
66
+ preset: "production",
67
+ timeoutMs: 5_000,
68
+ policyPresets,
69
+ middleware: [withHttpObservability(observability)],
70
+ });
71
+
72
+ const app = express();
73
+ ```
74
+
75
+ ## Routes
76
+
77
+ ```ts
78
+ app.get("/users/:id", async (req, res, next) => {
79
+ const ctx = makeExpressRequestObservabilityContext(observability, req, {
80
+ route: "/users/:id",
81
+ });
82
+
83
+ try {
84
+ const response = await ctx.run(
85
+ ctx.withRequestSpan(
86
+ asyncFlatMap(
87
+ logEffect("info", "users.lookup", {
88
+ userId: req.params.id,
89
+ authorization: req.headers.authorization,
90
+ }),
91
+ () =>
92
+ http.getJson(`/users/${req.params.id}`, {
93
+ policy: "readModel",
94
+ timeoutMs: 2_000,
95
+ }),
96
+ ),
97
+ ),
98
+ );
99
+
100
+ res.json(response.body);
101
+ } catch (error) {
102
+ next(error);
103
+ }
104
+ });
105
+
106
+ app.get("/metrics", (_req, res) => {
107
+ res
108
+ .type(observability.prometheus.contentType)
109
+ .send(observability.prometheus.export());
110
+ });
111
+ ```
112
+
113
+ ## Shutdown
114
+
115
+ ```ts
116
+ const server = app.listen(process.env.PORT ?? 3000);
117
+
118
+ process.once("SIGTERM", async () => {
119
+ await new Promise<void>((resolve) => server.close(() => resolve()));
120
+ await http.shutdown();
121
+ await observability.shutdown();
122
+ });
123
+ ```
124
+
125
+ Runnable repo example: `src/examples/observabilityExpress.ts`.
@@ -0,0 +1,124 @@
1
+ # Fastify integration
2
+
3
+ Fastify has its own request shape, so use
4
+ `makeFastifyRequestObservabilityContext` for inbound spans and a shared Brass
5
+ HTTP client for downstream calls.
6
+
7
+ ## App Setup
8
+
9
+ ```ts
10
+ import Fastify from "fastify";
11
+ import { asyncFlatMap } from "brass-runtime/core";
12
+ import {
13
+ defineHttpPolicyPresets,
14
+ makeDefaultHttpClient,
15
+ } from "brass-runtime/http";
16
+ import {
17
+ logEffect,
18
+ makeFastifyRequestObservabilityContext,
19
+ makeObservability,
20
+ makeOtlpOptions,
21
+ withHttpObservability,
22
+ } from "brass-runtime/observability";
23
+
24
+ const policyPresets = defineHttpPolicyPresets({
25
+ readModel: {
26
+ lane: "read-model",
27
+ priority: 3,
28
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
29
+ },
30
+ command: {
31
+ lane: "command",
32
+ priority: 1,
33
+ retry: false,
34
+ },
35
+ });
36
+
37
+ const observability = makeObservability({
38
+ serviceName: process.env.OTEL_SERVICE_NAME ?? "shop-fastify",
39
+ serviceVersion: process.env.OTEL_SERVICE_VERSION,
40
+ resource: {
41
+ "deployment.environment": process.env.NODE_ENV ?? "development",
42
+ },
43
+ logs: { minLevel: "info" },
44
+ sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
45
+ redaction: {},
46
+ cardinality: { maxValuesPerLabel: 100 },
47
+ otlp: makeOtlpOptions({
48
+ endpoint: process.env.GRAFANA_OTLP_ENDPOINT ?? "http://grafana-alloy:4318",
49
+ headers: process.env.GRAFANA_OTLP_AUTHORIZATION
50
+ ? { Authorization: process.env.GRAFANA_OTLP_AUTHORIZATION }
51
+ : undefined,
52
+ timeoutMs: 10_000,
53
+ retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
54
+ pipeline: {
55
+ maxQueueSize: 10_000,
56
+ batchSize: 512,
57
+ dropPolicy: "drop-oldest",
58
+ shutdownTimeoutMs: 10_000,
59
+ },
60
+ }),
61
+ flushIntervalMs: 10_000,
62
+ autoStart: true,
63
+ });
64
+
65
+ const http = makeDefaultHttpClient({
66
+ baseUrl: process.env.USERS_API_BASE_URL ?? "https://users-api.internal",
67
+ preset: "production",
68
+ timeoutMs: 5_000,
69
+ policyPresets,
70
+ middleware: [withHttpObservability(observability)],
71
+ });
72
+
73
+ const app = Fastify({ logger: true });
74
+ ```
75
+
76
+ ## Routes
77
+
78
+ ```ts
79
+ app.get("/users/:id", async (request, reply) => {
80
+ const params = request.params as { id: string };
81
+ const ctx = makeFastifyRequestObservabilityContext(observability, request, {
82
+ route: "/users/:id",
83
+ });
84
+
85
+ const response = await ctx.run(
86
+ ctx.withRequestSpan(
87
+ asyncFlatMap(
88
+ logEffect("info", "users.lookup", {
89
+ userId: params.id,
90
+ authorization: request.headers.authorization,
91
+ }),
92
+ () =>
93
+ http.getJson(`/users/${params.id}`, {
94
+ policy: "readModel",
95
+ timeoutMs: 2_000,
96
+ }),
97
+ ),
98
+ ),
99
+ );
100
+
101
+ return reply.send(response.body);
102
+ });
103
+
104
+ app.get("/metrics", async (_request, reply) => {
105
+ return reply
106
+ .type(observability.prometheus.contentType)
107
+ .send(observability.prometheus.export());
108
+ });
109
+ ```
110
+
111
+ ## Shutdown
112
+
113
+ ```ts
114
+ await app.listen({ port: Number(process.env.PORT ?? 3000), host: "0.0.0.0" });
115
+
116
+ process.once("SIGTERM", async () => {
117
+ await app.close();
118
+ await http.shutdown();
119
+ await observability.shutdown();
120
+ });
121
+ ```
122
+
123
+ Runnable repo example: `src/examples/observabilityFastify.ts`.
124
+