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.
- package/CHANGELOG.md +17 -0
- package/README.md +287 -23
- 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-7XOPAB5Q.js → chunk-4P2HHGAX.mjs} +83 -5
- 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-6RY2FFN4.mjs +2024 -0
- package/dist/chunk-74ZTY6CP.js +2871 -0
- package/dist/chunk-7CMJS3QE.mjs +2871 -0
- package/dist/{chunk-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
- package/dist/chunk-7X3K5RMS.js +2024 -0
- package/dist/chunk-7ZPEZ57L.cjs +2024 -0
- package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
- package/dist/chunk-AGR5B2BC.cjs +683 -0
- package/dist/chunk-B33ICAKP.js +313 -0
- package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
- package/dist/{chunk-F5EUMJL7.mjs → chunk-BKK77SBA.js} +83 -5
- package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
- package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
- package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
- package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
- package/dist/chunk-EOC4UHBS.mjs +229 -0
- package/dist/chunk-F6XWZQY4.cjs +777 -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-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-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-SK7UZRNI.mjs +777 -0
- 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-VWIPB6I5.js +777 -0
- package/dist/{chunk-JNFRRJYH.cjs → chunk-WBGRHGBP.cjs} +270 -192
- 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 +18 -531
- package/dist/observability/index.d.ts +81 -8
- package/dist/observability/index.js +25 -538
- package/dist/observability/index.mjs +25 -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-D6JZ15_e.d.ts} +16 -4
- 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 +65 -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 +339 -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/framework-integrations.md +38 -0
- package/docs/frameworks/angular.md +153 -0
- package/docs/frameworks/express.md +125 -0
- package/docs/frameworks/fastify.md +124 -0
- package/docs/frameworks/nestjs.md +282 -0
- package/docs/frameworks/nextjs.md +147 -0
- package/docs/frameworks/react.md +139 -0
- package/docs/frameworks/vanilla.md +224 -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/nestjs.md +6 -0
- package/docs/observability-collector-smoke.md +31 -0
- package/docs/observability-framework-examples.md +110 -0
- package/docs/observability.md +649 -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,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
|
+
|