brass-runtime 1.16.1 → 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 (39) hide show
  1. package/README.md +4 -5
  2. package/dist/{chunk-MT3OWDPC.mjs → chunk-4P2HHGAX.mjs} +28 -0
  3. package/dist/{chunk-HLWLMW2F.mjs → chunk-6RY2FFN4.mjs} +2 -2
  4. package/dist/{chunk-BABBZK4Y.js → chunk-7X3K5RMS.js} +2 -2
  5. package/dist/{chunk-VN44DYYT.cjs → chunk-7ZPEZ57L.cjs} +4 -4
  6. package/dist/{chunk-CIZFIMK5.js → chunk-BKK77SBA.js} +28 -0
  7. package/dist/{chunk-76YMRMH2.cjs → chunk-F6XWZQY4.cjs} +25 -25
  8. package/dist/{chunk-DNFO2EIZ.mjs → chunk-SK7UZRNI.mjs} +1 -1
  9. package/dist/{chunk-AVNQLJ5V.js → chunk-VWIPB6I5.js} +1 -1
  10. package/dist/{chunk-ENKODRU3.cjs → chunk-WBGRHGBP.cjs} +29 -1
  11. package/dist/http/index.cjs +4 -4
  12. package/dist/http/index.d.ts +1 -1
  13. package/dist/http/index.js +1 -1
  14. package/dist/http/index.mjs +1 -1
  15. package/dist/observability/index.cjs +5 -3
  16. package/dist/observability/index.d.ts +2 -2
  17. package/dist/observability/index.js +4 -2
  18. package/dist/observability/index.mjs +4 -2
  19. package/dist/perf/cli.cjs +18 -18
  20. package/dist/perf/cli.js +3 -3
  21. package/dist/perf/cli.mjs +3 -3
  22. package/dist/perf/index.cjs +5 -5
  23. package/dist/perf/index.js +3 -3
  24. package/dist/perf/index.mjs +3 -3
  25. package/dist/{server-GJPg8ZSG.d.ts → server-D6JZ15_e.d.ts} +12 -1
  26. package/docs/README.md +2 -0
  27. package/docs/ai/PUBLIC_API.md +3 -0
  28. package/docs/framework-integrations.md +38 -0
  29. package/docs/frameworks/angular.md +153 -0
  30. package/docs/frameworks/express.md +125 -0
  31. package/docs/frameworks/fastify.md +124 -0
  32. package/docs/frameworks/nestjs.md +282 -0
  33. package/docs/frameworks/nextjs.md +147 -0
  34. package/docs/frameworks/react.md +139 -0
  35. package/docs/frameworks/vanilla.md +224 -0
  36. package/docs/nestjs.md +6 -0
  37. package/docs/observability-framework-examples.md +12 -0
  38. package/docs/observability.md +107 -0
  39. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ # Vanilla integration
2
+
3
+ Use this when an app does not have a framework-level DI or lifecycle system.
4
+ The same shape works for plain browser TypeScript, Node HTTP handlers, CLIs,
5
+ and small services.
6
+
7
+ ## Browser
8
+
9
+ Browser apps should send telemetry to a same-origin proxy. Do not ship Grafana
10
+ Cloud or collector credentials to the client.
11
+
12
+ ```ts
13
+ // brass.browser.ts
14
+ import { Runtime } from "brass-runtime/core";
15
+ import {
16
+ defineHttpPolicyPresets,
17
+ makeDefaultHttpClient,
18
+ } from "brass-runtime/http";
19
+ import {
20
+ makeObservability,
21
+ makeOtlpOptions,
22
+ withHttpObservability,
23
+ } from "brass-runtime/observability";
24
+
25
+ const policyPresets = defineHttpPolicyPresets({
26
+ readModel: {
27
+ lane: "read-model",
28
+ priority: 3,
29
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
30
+ },
31
+ command: {
32
+ lane: "command",
33
+ priority: 1,
34
+ retry: false,
35
+ },
36
+ });
37
+
38
+ export const brass = (() => {
39
+ const observability = makeObservability({
40
+ serviceName: "shop-web",
41
+ resource: { "deployment.environment": "browser" },
42
+ logs: false,
43
+ sampling: { ratio: 0.1, respectRemoteSampled: true, forceSampleOnError: true },
44
+ redaction: {},
45
+ cardinality: { maxValuesPerLabel: 100 },
46
+ otlp: makeOtlpOptions({
47
+ endpoint: "/api/otel",
48
+ timeoutMs: 10_000,
49
+ retry: { attempts: 2, initialDelayMs: 100, maxDelayMs: 1_000 },
50
+ pipeline: { maxQueueSize: 2_000, batchSize: 128, dropPolicy: "drop-oldest" },
51
+ }),
52
+ flushIntervalMs: 15_000,
53
+ autoStart: true,
54
+ });
55
+
56
+ const runtime = new Runtime({
57
+ env: observability.env,
58
+ hooks: observability.hooks,
59
+ });
60
+
61
+ const http = makeDefaultHttpClient({
62
+ baseUrl: "/api",
63
+ preset: "balanced",
64
+ timeoutMs: 5_000,
65
+ policyPresets,
66
+ middleware: [withHttpObservability(observability)],
67
+ });
68
+
69
+ return {
70
+ observability,
71
+ runtime,
72
+ http,
73
+ shutdown: async () => {
74
+ await http.shutdown();
75
+ await observability.shutdown();
76
+ },
77
+ };
78
+ })();
79
+ ```
80
+
81
+ Use it from ordinary browser code:
82
+
83
+ ```ts
84
+ type User = {
85
+ readonly id: string;
86
+ readonly name: string;
87
+ };
88
+
89
+ export async function loadCurrentUser(): Promise<User> {
90
+ const response = await brass.runtime.toPromise(
91
+ brass.http.getJson<User>("/users/me", {
92
+ policy: "readModel",
93
+ timeoutMs: 2_000,
94
+ }),
95
+ );
96
+
97
+ return response.body;
98
+ }
99
+
100
+ window.addEventListener("pagehide", () => {
101
+ void brass.shutdown();
102
+ });
103
+ ```
104
+
105
+ ## Node
106
+
107
+ On the server, Brass can send directly to a collector because credentials stay
108
+ inside the trusted process.
109
+
110
+ ```ts
111
+ // brass.node.ts
112
+ import { Runtime } from "brass-runtime/core";
113
+ import {
114
+ defineHttpPolicyPresets,
115
+ makeDefaultHttpClient,
116
+ } from "brass-runtime/http";
117
+ import {
118
+ makeObservability,
119
+ makeOtlpOptions,
120
+ withHttpObservability,
121
+ } from "brass-runtime/observability";
122
+
123
+ const policyPresets = defineHttpPolicyPresets({
124
+ readModel: {
125
+ lane: "read-model",
126
+ priority: 3,
127
+ retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
128
+ },
129
+ command: {
130
+ lane: "command",
131
+ priority: 1,
132
+ retry: false,
133
+ },
134
+ });
135
+
136
+ export const brass = (() => {
137
+ const observability = makeObservability({
138
+ serviceName: process.env.OTEL_SERVICE_NAME ?? "vanilla-node",
139
+ serviceVersion: process.env.OTEL_SERVICE_VERSION,
140
+ resource: {
141
+ "deployment.environment": process.env.NODE_ENV ?? "development",
142
+ },
143
+ logs: { minLevel: "info" },
144
+ sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
145
+ redaction: {},
146
+ cardinality: { maxValuesPerLabel: 100 },
147
+ otlp: makeOtlpOptions({
148
+ endpoint: process.env.GRAFANA_OTLP_ENDPOINT ?? "http://grafana-alloy:4318",
149
+ headers: process.env.GRAFANA_OTLP_AUTHORIZATION
150
+ ? { Authorization: process.env.GRAFANA_OTLP_AUTHORIZATION }
151
+ : undefined,
152
+ timeoutMs: 10_000,
153
+ retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
154
+ pipeline: {
155
+ maxQueueSize: 10_000,
156
+ batchSize: 512,
157
+ dropPolicy: "drop-oldest",
158
+ shutdownTimeoutMs: 10_000,
159
+ },
160
+ }),
161
+ flushIntervalMs: 10_000,
162
+ autoStart: true,
163
+ });
164
+
165
+ const runtime = new Runtime({
166
+ env: observability.env,
167
+ hooks: observability.hooks,
168
+ });
169
+
170
+ const http = makeDefaultHttpClient({
171
+ baseUrl: process.env.API_BASE_URL ?? "https://api.internal",
172
+ preset: "production",
173
+ timeoutMs: 5_000,
174
+ policyPresets,
175
+ middleware: [withHttpObservability(observability)],
176
+ });
177
+
178
+ return {
179
+ observability,
180
+ runtime,
181
+ http,
182
+ shutdown: async () => {
183
+ await http.shutdown();
184
+ await observability.shutdown();
185
+ },
186
+ };
187
+ })();
188
+ ```
189
+
190
+ Use the Node request adapter when you receive `IncomingMessage`-like requests:
191
+
192
+ ```ts
193
+ import { createServer } from "node:http";
194
+ import {
195
+ makeNodeRequestObservabilityContext,
196
+ } from "brass-runtime/observability";
197
+ import { brass } from "./brass.node";
198
+
199
+ const server = createServer(async (req, res) => {
200
+ const ctx = makeNodeRequestObservabilityContext(brass.observability, req, {
201
+ route: req.url?.startsWith("/users/") ? "/users/:id" : req.url,
202
+ });
203
+
204
+ const response = await ctx.run(
205
+ ctx.withRequestSpan(
206
+ brass.http.getJson("/users/42", {
207
+ policy: "readModel",
208
+ timeoutMs: 2_000,
209
+ }),
210
+ ),
211
+ );
212
+
213
+ res.setHeader("content-type", "application/json");
214
+ res.end(JSON.stringify(response.body));
215
+ });
216
+
217
+ process.once("SIGTERM", async () => {
218
+ await new Promise<void>((resolve) => server.close(() => resolve()));
219
+ await brass.shutdown();
220
+ });
221
+
222
+ server.listen(process.env.PORT ?? 3000);
223
+ ```
224
+
package/docs/nestjs.md ADDED
@@ -0,0 +1,6 @@
1
+ # NestJS integration
2
+
3
+ The NestJS recipe now lives at [`docs/frameworks/nestjs.md`](./frameworks/nestjs.md).
4
+
5
+ This file is kept as a compatibility pointer for older links.
6
+
@@ -7,6 +7,10 @@ flush OTLP metrics/traces.
7
7
  They use optional framework dependencies so the runtime package does not force
8
8
  Express, Fastify, or Nest into normal installs.
9
9
 
10
+ For production-style integration recipes across React, Next.js, Angular,
11
+ Express, Fastify, and Nest, see
12
+ [`docs/framework-integrations.md`](./framework-integrations.md).
13
+
10
14
  ## Express
11
15
 
12
16
  ```bash
@@ -61,9 +65,17 @@ curl http://localhost:3002/metrics
61
65
 
62
66
  Source: `src/examples/observabilityNest.ts`.
63
67
 
68
+ For a production-style Nest module with DI tokens, Grafana/OTLP configuration,
69
+ an observed Brass HTTP client, and shutdown wiring, see
70
+ [`docs/frameworks/nestjs.md`](./frameworks/nestjs.md).
71
+
64
72
  ## What the examples demonstrate
65
73
 
66
74
  - `makeObservabilityFromEnv(process.env)` for deployment-style setup.
75
+ - `makeOtlpOptions(...)` for collector endpoint configuration without adding
76
+ vendor-specific code to Brass.
77
+ - `withHttpObservability(...)` around the Brass HTTP client for outbound
78
+ metrics, spans, logs, policy context, and trace propagation.
67
79
  - Framework-specific request adapters:
68
80
  - `makeExpressRequestObservabilityContext`
69
81
  - `makeFastifyRequestObservabilityContext`
@@ -327,6 +327,109 @@ collector does not create overlapping exports.
327
327
  Finished spans are pruned after successful export and can also be bounded with
328
328
  `traces.maxFinishedSpans` / `traces.maxSpanAgeMs`.
329
329
 
330
+ ### Vendor-neutral collector recipes
331
+
332
+ Brass intentionally does not know about Grafana Cloud, AppDynamics, or any
333
+ OpenTelemetry SDK implementation. The runtime only needs OTLP HTTP endpoint
334
+ URLs, headers, and optional export tuning. Keep vendor naming in application
335
+ code by writing small helpers that call the backend-neutral `makeOtlpOptions`.
336
+
337
+ ```ts
338
+ import {
339
+ makeObservability,
340
+ makeOtlpOptions,
341
+ type ObservabilityOtlpOptions,
342
+ } from "brass-runtime/observability";
343
+
344
+ function productionOtlp(input: {
345
+ readonly endpoint: string;
346
+ readonly headers?: Record<string, string>;
347
+ }): ObservabilityOtlpOptions {
348
+ return makeOtlpOptions({
349
+ endpoint: input.endpoint,
350
+ headers: input.headers,
351
+ timeoutMs: 10_000,
352
+ retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
353
+ pipeline: {
354
+ maxQueueSize: 10_000,
355
+ batchSize: 512,
356
+ dropPolicy: "drop-oldest",
357
+ shutdownTimeoutMs: 10_000,
358
+ },
359
+ });
360
+ }
361
+ ```
362
+
363
+ Grafana Cloud can be configured as a direct OTLP endpoint or through
364
+ Grafana Alloy/OpenTelemetry Collector. The helper stays in your app and only
365
+ returns Brass OTLP config:
366
+
367
+ ```ts
368
+ function grafanaCloudCollector(input: {
369
+ readonly endpoint: string;
370
+ readonly authorization?: string;
371
+ }): ObservabilityOtlpOptions {
372
+ return productionOtlp({
373
+ endpoint: input.endpoint,
374
+ headers: input.authorization
375
+ ? { Authorization: input.authorization }
376
+ : undefined,
377
+ });
378
+ }
379
+
380
+ const observability = makeObservability({
381
+ serviceName: "shopping-ms",
382
+ serviceVersion: "1.2.3",
383
+ resource: {
384
+ "service.namespace": "shopping",
385
+ "deployment.environment": "production",
386
+ },
387
+ otlp: grafanaCloudCollector({
388
+ endpoint: process.env.GRAFANA_OTLP_ENDPOINT!,
389
+ authorization: process.env.GRAFANA_OTLP_AUTHORIZATION,
390
+ }),
391
+ });
392
+ ```
393
+
394
+ For AppDynamics, prefer sending Brass telemetry to the AppDynamics/OpenTelemetry
395
+ Collector deployed next to the service. Authentication and vendor-specific
396
+ exporters stay in the collector config:
397
+
398
+ ```ts
399
+ function appDynamicsCollector(input: {
400
+ readonly endpoint: string;
401
+ }): ObservabilityOtlpOptions {
402
+ return productionOtlp({
403
+ endpoint: input.endpoint,
404
+ });
405
+ }
406
+
407
+ const observability = makeObservability({
408
+ serviceName: "car-rental-ms",
409
+ serviceVersion: "1.2.3",
410
+ resource: {
411
+ "service.namespace": "car-rental",
412
+ "deployment.environment": "production",
413
+ },
414
+ otlp: appDynamicsCollector({
415
+ endpoint: process.env.APPD_OTEL_COLLECTOR_ENDPOINT ?? "http://appd-otel-collector:4318",
416
+ }),
417
+ });
418
+ ```
419
+
420
+ Then attach the same observability instance to HTTP without changing the
421
+ collector helpers:
422
+
423
+ ```ts
424
+ import { makeDefaultHttpClient } from "brass-runtime/http";
425
+ import { withHttpObservability } from "brass-runtime/observability";
426
+
427
+ const http = makeDefaultHttpClient({
428
+ baseUrl: "https://api.example.com",
429
+ middleware: [withHttpObservability(observability)],
430
+ });
431
+ ```
432
+
330
433
  Sampling can be configured globally, by ratio, or with rules:
331
434
 
332
435
  ```ts
@@ -423,6 +526,10 @@ const router = makeHttpRouter([
423
526
 
424
527
  Runnable framework examples live in
425
528
  [`docs/observability-framework-examples.md`](./observability-framework-examples.md).
529
+ Production-style framework integration recipes live in
530
+ [`docs/framework-integrations.md`](./framework-integrations.md).
531
+ For a NestJS module recipe with Grafana/OTLP, DI tokens, HTTP client
532
+ observability, and shutdown wiring, see [`docs/frameworks/nestjs.md`](./frameworks/nestjs.md).
426
533
 
427
534
  Collector smoke and performance budget helpers:
428
535
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brass-runtime",
3
- "version": "1.16.1",
3
+ "version": "1.17.0",
4
4
  "description": "Effect runtime utilities for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "Augusto Vivaldelli",