brass-runtime 1.15.0 → 1.16.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 (93) hide show
  1. package/README.md +409 -137
  2. package/dist/agent/cli/main.cjs +40 -35
  3. package/dist/agent/cli/main.js +9 -4
  4. package/dist/agent/cli/main.mjs +9 -4
  5. package/dist/agent/index.cjs +8 -4
  6. package/dist/agent/index.d.ts +1 -1
  7. package/dist/agent/index.js +7 -3
  8. package/dist/agent/index.mjs +7 -3
  9. package/dist/{chunk-PPUXIH5R.js → chunk-2WC63LJK.mjs} +11 -7
  10. package/dist/chunk-3RG5ZIWI.js +10 -0
  11. package/dist/chunk-45F7OKGT.cjs +104 -0
  12. package/dist/chunk-5YOQOXEQ.cjs +2491 -0
  13. package/dist/{chunk-STVLQ3XD.cjs → chunk-7HUOJA4W.cjs} +78 -74
  14. package/dist/{chunk-BMH5AV44.js → chunk-7LVI2GIN.js} +251 -370
  15. package/dist/chunk-7TL2LHQJ.js +2491 -0
  16. package/dist/chunk-7V4KY4RL.mjs +104 -0
  17. package/dist/chunk-7XOPAB5Q.js +2143 -0
  18. package/dist/chunk-CCKHV5BT.mjs +193 -0
  19. package/dist/{chunk-AR22SXML.js → chunk-CY33PGEX.mjs} +488 -421
  20. package/dist/chunk-DJQ7OMMB.cjs +144 -0
  21. package/dist/chunk-F5EUMJL7.mjs +2143 -0
  22. package/dist/chunk-FM4W4QPL.js +193 -0
  23. package/dist/{chunk-TO7IKXYT.js → chunk-G3XGCZDQ.js} +1 -1
  24. package/dist/{chunk-BDF4AMWX.mjs → chunk-G6IQOE4P.mjs} +251 -370
  25. package/dist/chunk-GOV47PPB.mjs +552 -0
  26. package/dist/chunk-H55LI6WY.js +93 -0
  27. package/dist/chunk-IJT6RRQ5.cjs +93 -0
  28. package/dist/{chunk-ELOOF35R.mjs → chunk-J3H54ZRV.mjs} +1 -1
  29. package/dist/chunk-JF4XXPZ5.cjs +552 -0
  30. package/dist/chunk-JNFRRJYH.cjs +2143 -0
  31. package/dist/chunk-JX3LZQJH.cjs +354 -0
  32. package/dist/chunk-K2T3DV26.mjs +93 -0
  33. package/dist/chunk-KCPT2D6G.js +552 -0
  34. package/dist/chunk-MWXMNYJS.cjs +1110 -0
  35. package/dist/{chunk-VEZNF5GZ.cjs → chunk-N6VHMOWB.cjs} +130 -126
  36. package/dist/{chunk-3QMOKAS5.js → chunk-NC5SDRYE.js} +9 -5
  37. package/dist/chunk-NOYZIMUJ.mjs +144 -0
  38. package/dist/{chunk-R3R2FVLG.cjs → chunk-NYL4D7SK.cjs} +5 -5
  39. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  40. package/dist/{chunk-4NHES7VK.mjs → chunk-OOGJ73B6.js} +11 -7
  41. package/dist/chunk-PNVFW245.js +144 -0
  42. package/dist/chunk-PRWCB3QL.mjs +2491 -0
  43. package/dist/{chunk-JFPU5GQI.mjs → chunk-QY5FKYEQ.js} +488 -421
  44. package/dist/chunk-ROJC3NBJ.js +104 -0
  45. package/dist/chunk-SPUEME2B.cjs +343 -0
  46. package/dist/chunk-TDVMADDN.js +343 -0
  47. package/dist/chunk-TVN5I4U6.cjs +193 -0
  48. package/dist/chunk-U5KWK3PX.mjs +343 -0
  49. package/dist/chunk-VFIUZG7J.mjs +354 -0
  50. package/dist/{chunk-TGIFUAK4.cjs → chunk-WQ5QNU5R.cjs} +459 -578
  51. package/dist/chunk-XDZOO4L5.js +354 -0
  52. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  53. package/dist/{chunk-K6M7MDZ4.mjs → chunk-ZGLD4TVZ.mjs} +9 -5
  54. package/dist/client-CtFmoDvM.d.ts +645 -0
  55. package/dist/core/index.cjs +72 -4
  56. package/dist/core/index.d.ts +92 -198
  57. package/dist/core/index.js +106 -38
  58. package/dist/core/index.mjs +106 -38
  59. package/dist/{effect-CMOQKX8y.d.ts → effect-CGNl5Rqp.d.ts} +107 -1
  60. package/dist/effectRunner-3ZHAD3LE.cjs +8 -0
  61. package/dist/effectRunner-A4CHJXJI.js +8 -0
  62. package/dist/effectRunner-OPUF6QRN.mjs +8 -0
  63. package/dist/http/index.cjs +2189 -1271
  64. package/dist/http/index.d.ts +830 -270
  65. package/dist/http/index.js +2008 -1090
  66. package/dist/http/index.mjs +2008 -1090
  67. package/dist/http/testing.cjs +159 -0
  68. package/dist/http/testing.d.ts +42 -0
  69. package/dist/http/testing.js +159 -0
  70. package/dist/http/testing.mjs +159 -0
  71. package/dist/index.cjs +246 -178
  72. package/dist/index.d.ts +9 -35
  73. package/dist/index.js +120 -52
  74. package/dist/index.mjs +120 -52
  75. package/dist/observability/index.cjs +677 -0
  76. package/dist/observability/index.d.ts +79 -0
  77. package/dist/observability/index.js +677 -0
  78. package/dist/observability/index.mjs +677 -0
  79. package/dist/schedule-Fque9Abz.d.ts +70 -0
  80. package/dist/schema/index.cjs +25 -0
  81. package/dist/schema/index.d.ts +177 -0
  82. package/dist/schema/index.js +25 -0
  83. package/dist/schema/index.mjs +25 -0
  84. package/dist/server-C8hDXA74.d.ts +674 -0
  85. package/dist/{stream-FQm9h4Mg.d.ts → stream-dvSs0QS5.d.ts} +1 -1
  86. package/dist/tracer-B5tRH9H7.d.ts +230 -0
  87. package/dist/tracing-Dt9S_6V8.d.ts +148 -0
  88. package/package.json +27 -1
  89. package/dist/chunk-BDYEENHT.js +0 -224
  90. package/dist/chunk-MS34J5LY.cjs +0 -224
  91. package/dist/chunk-UMAZLXAB.mjs +0 -224
  92. package/dist/chunk-XPZNXSVN.cjs +0 -1043
  93. package/dist/tracing-DNT9jEbr.d.ts +0 -106
@@ -0,0 +1,2143 @@
1
+ import {
2
+ EventBus,
3
+ InMemoryTracer,
4
+ makeMetrics
5
+ } from "./chunk-VFIUZG7J.mjs";
6
+ import {
7
+ Runtime,
8
+ ctxExtend,
9
+ ctxToObject,
10
+ emptyContext,
11
+ getCurrentFiber
12
+ } from "./chunk-G6IQOE4P.mjs";
13
+ import {
14
+ asyncFail,
15
+ asyncFlatMap,
16
+ asyncFold,
17
+ asyncSucceed,
18
+ asyncSync
19
+ } from "./chunk-NOYZIMUJ.mjs";
20
+ import {
21
+ Schema,
22
+ parseConfig
23
+ } from "./chunk-U5KWK3PX.mjs";
24
+
25
+ // src/observability/health.ts
26
+ function snapshotRuntimeHealth(options = {}) {
27
+ const checkedAtMs = options.clock?.() ?? Date.now();
28
+ const checks = runChecks(options.checks);
29
+ const circuitBreakers = collectCircuitBreakers(options.circuitBreakers);
30
+ const adaptiveLimiters = collectAdaptiveLimiters(options.adaptiveLimiters);
31
+ const fibers = options.registry ? fiberSummary(options.registry) : void 0;
32
+ const scopes = options.registry ? scopeSummary(options.registry) : void 0;
33
+ const metrics = options.metrics ? metricsSummary(options.metrics) : void 0;
34
+ const runtime = options.runtime ? runtimeSummary(options.runtime) : void 0;
35
+ const status = aggregateStatus([
36
+ ...Object.values(checks).map((check) => check.status),
37
+ ...Object.values(circuitBreakers).map((breaker) => breaker.status),
38
+ ...Object.values(adaptiveLimiters).map((limiter) => limiter.status),
39
+ ...runtimeSchedulerDropped(runtime) > 0 ? ["degraded"] : []
40
+ ]);
41
+ return {
42
+ status,
43
+ ready: status === "ok" || status === "degraded" && options.readiness?.failOnDegraded !== true,
44
+ checkedAt: new Date(checkedAtMs).toISOString(),
45
+ ...fibers ? { fibers } : {},
46
+ ...scopes ? { scopes } : {},
47
+ ...runtime ? { runtime } : {},
48
+ ...metrics ? { metrics } : {},
49
+ circuitBreakers,
50
+ adaptiveLimiters,
51
+ checks
52
+ };
53
+ }
54
+ function makeRuntimeHealth(options = {}) {
55
+ return asyncSync(() => snapshotRuntimeHealth(options));
56
+ }
57
+ var runtimeHealth = makeRuntimeHealth;
58
+ function readiness(options = {}) {
59
+ return asyncSync(() => snapshotRuntimeHealth(options).ready);
60
+ }
61
+ function healthToHttpResponse(report) {
62
+ return {
63
+ status: report.ready ? 200 : 503,
64
+ headers: { "content-type": "application/json" },
65
+ body: JSON.stringify(report)
66
+ };
67
+ }
68
+ function runChecks(checks) {
69
+ const out = {};
70
+ for (const [name, check] of Object.entries(checks ?? {})) {
71
+ try {
72
+ out[name] = normalizeHealthCheckResult(check());
73
+ } catch (error) {
74
+ out[name] = {
75
+ status: "down",
76
+ message: error instanceof Error ? error.message : String(error)
77
+ };
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ function collectCircuitBreakers(breakers) {
83
+ const out = {};
84
+ for (const [name, breaker] of Object.entries(breakers ?? {})) {
85
+ const stats = breaker.stats();
86
+ out[name] = {
87
+ ...stats,
88
+ status: stats.state === "open" ? "down" : stats.state === "half-open" ? "degraded" : "ok"
89
+ };
90
+ }
91
+ return out;
92
+ }
93
+ function collectAdaptiveLimiters(limiters) {
94
+ const out = {};
95
+ for (const [name, limiter] of Object.entries(limiters ?? {})) {
96
+ const stats = limiter.stats();
97
+ out[name] = {
98
+ ...stats,
99
+ status: adaptiveLimiterStatus(stats)
100
+ };
101
+ }
102
+ return out;
103
+ }
104
+ function adaptiveLimiterStatus(stats) {
105
+ const rejectionRate = stats.rejectionRate ?? 0;
106
+ const errorRate = stats.errorRate ?? 0;
107
+ if (rejectionRate >= 0.5) return "down";
108
+ if (rejectionRate > 0 || stats.queueDepth > stats.limit || errorRate >= 0.25) return "degraded";
109
+ return "ok";
110
+ }
111
+ function fiberSummary(registry) {
112
+ const fibers = Array.from(registry.fibers.values());
113
+ return {
114
+ active: fibers.filter((fiber) => fiber.status === "Running").length,
115
+ suspended: fibers.filter((fiber) => fiber.runState === "Suspended").length,
116
+ done: fibers.filter((fiber) => fiber.runState === "Done").length
117
+ };
118
+ }
119
+ function scopeSummary(registry) {
120
+ const scopes = Array.from(registry.scopes.values());
121
+ return {
122
+ open: scopes.filter((scope) => scope.closedAt === void 0).length,
123
+ closed: scopes.filter((scope) => scope.closedAt !== void 0).length
124
+ };
125
+ }
126
+ function metricsSummary(metrics) {
127
+ const snapshot = metrics.snapshot();
128
+ const gaugeValue = (name) => snapshot.gauges.find((gauge) => gauge.name === name)?.value;
129
+ return {
130
+ counters: snapshot.counters.length,
131
+ gauges: snapshot.gauges.length,
132
+ histograms: snapshot.histograms.length,
133
+ activeFibers: gaugeValue("brass_runtime_fibers_active"),
134
+ activeScopes: gaugeValue("brass_runtime_scopes_active"),
135
+ activeSpans: gaugeValue("brass_runtime_spans_active")
136
+ };
137
+ }
138
+ function runtimeSummary(runtime) {
139
+ const stats = runtime.stats?.();
140
+ const scheduler = runtime.scheduler?.stats?.();
141
+ return {
142
+ engine: stats?.engine,
143
+ fiberStats: stats?.data,
144
+ scheduler
145
+ };
146
+ }
147
+ function runtimeSchedulerDropped(runtime) {
148
+ const data = runtime?.scheduler?.data;
149
+ return typeof data?.droppedTasks === "number" ? data.droppedTasks : 0;
150
+ }
151
+ function aggregateStatus(statuses) {
152
+ if (statuses.includes("down")) return "down";
153
+ if (statuses.includes("degraded")) return "degraded";
154
+ return "ok";
155
+ }
156
+ function normalizeHealthCheckResult(result) {
157
+ return {
158
+ status: result.status,
159
+ ...result.message ? { message: result.message } : {},
160
+ ...result.details ? { details: result.details } : {}
161
+ };
162
+ }
163
+
164
+ // src/observability/redaction.ts
165
+ var DEFAULT_REPLACEMENT = "[REDACTED]";
166
+ var DEFAULT_MAX_DEPTH = 6;
167
+ var DEFAULT_MAX_STRING_LENGTH = 8192;
168
+ var DEFAULT_KEY_PATTERNS = [
169
+ "authorization",
170
+ "cookie",
171
+ "set-cookie",
172
+ "password",
173
+ "passwd",
174
+ "secret",
175
+ "token",
176
+ "access_token",
177
+ "refresh_token",
178
+ "api_key",
179
+ "apikey",
180
+ "private_key",
181
+ /credential/i
182
+ ];
183
+ var DEFAULT_HEADER_PATTERNS = [
184
+ "authorization",
185
+ "cookie",
186
+ "set-cookie",
187
+ "x-api-key",
188
+ "proxy-authorization"
189
+ ];
190
+ function makeObservabilityRedactor(config) {
191
+ if (isRedactor(config)) return config;
192
+ const options = config === false ? { keys: [], headers: [], redactUrlQuery: false } : config ?? {};
193
+ const replacement = options.replacement ?? DEFAULT_REPLACEMENT;
194
+ const keys = options.keys ?? DEFAULT_KEY_PATTERNS;
195
+ const headers = options.headers ?? DEFAULT_HEADER_PATTERNS;
196
+ const maxDepth = Math.max(1, Math.floor(options.maxDepth ?? DEFAULT_MAX_DEPTH));
197
+ const maxStringLength = Math.max(0, Math.floor(options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH));
198
+ const redactUrlQuery = options.redactUrlQuery ?? true;
199
+ const redactByKey = (key, value, depth) => {
200
+ if (matchesAny(key, keys)) return replacement;
201
+ return redactValue(value, depth);
202
+ };
203
+ const redactValue = (value, depth) => {
204
+ if (value == null || typeof value === "number" || typeof value === "boolean") return value;
205
+ if (typeof value === "string") return maxStringLength > 0 && value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;
206
+ if (depth >= maxDepth) return "[MaxDepth]";
207
+ if (Array.isArray(value)) return value.map((item) => redactValue(item, depth + 1));
208
+ if (value instanceof Error) {
209
+ return {
210
+ name: value.name,
211
+ message: value.message
212
+ };
213
+ }
214
+ if (typeof value === "object") {
215
+ const out = {};
216
+ for (const [key, item] of Object.entries(value)) {
217
+ out[key] = redactByKey(key, item, depth + 1);
218
+ }
219
+ return out;
220
+ }
221
+ return String(value);
222
+ };
223
+ return {
224
+ value: (value) => redactValue(value, 0),
225
+ fields: (fields) => redactRecord(fields, redactByKey),
226
+ attributes: (fields) => redactRecord(fields, redactByKey),
227
+ headers: (input) => {
228
+ const out = {};
229
+ for (const [key, value] of Object.entries(input)) {
230
+ out[key] = matchesAny(key, headers) ? replacement : value;
231
+ }
232
+ return out;
233
+ },
234
+ url: (url) => redactUrl(url, redactUrlQuery)
235
+ };
236
+ }
237
+ function redactRecord(fields, redactByKey) {
238
+ const out = {};
239
+ for (const [key, value] of Object.entries(fields)) {
240
+ out[key] = redactByKey(key, value, 0);
241
+ }
242
+ return out;
243
+ }
244
+ function redactUrl(url, redactQuery) {
245
+ if (!redactQuery) return url;
246
+ try {
247
+ const parsed = new URL(url);
248
+ if (parsed.search) parsed.search = "?[REDACTED]";
249
+ return parsed.toString();
250
+ } catch {
251
+ const queryIndex = url.indexOf("?");
252
+ if (queryIndex < 0) return url;
253
+ return `${url.slice(0, queryIndex)}?[REDACTED]`;
254
+ }
255
+ }
256
+ function matchesAny(key, patterns) {
257
+ const lower = key.toLowerCase();
258
+ return patterns.some(
259
+ (pattern) => typeof pattern === "string" ? lower === pattern.toLowerCase() : pattern.test(key)
260
+ );
261
+ }
262
+ function isRedactor(value) {
263
+ return typeof value === "object" && value !== null && typeof value.fields === "function" && typeof value.attributes === "function";
264
+ }
265
+
266
+ // src/observability/traceContext.ts
267
+ var TRACEPARENT_HEADER = "traceparent";
268
+ var TRACESTATE_HEADER = "tracestate";
269
+ var BAGGAGE_HEADER = "baggage";
270
+ var HEX = /^[0-9a-f]+$/;
271
+ var BAGGAGE_KEY = /^[!#$%&'*+\-.^_`|~0-9a-zA-Z]+$/;
272
+ function parseTraceparent(value) {
273
+ if (!value) return void 0;
274
+ const parts = value.trim().split("-");
275
+ if (parts.length < 4) return void 0;
276
+ const [version, traceIdRaw, spanIdRaw, flagsRaw] = parts;
277
+ if (!isFixedHex(version, 2) || version.toLowerCase() === "ff") return void 0;
278
+ if (version.toLowerCase() === "00" && parts.length !== 4) return void 0;
279
+ if (!isFixedHex(traceIdRaw, 32) || isAllZero(traceIdRaw)) return void 0;
280
+ if (!isFixedHex(spanIdRaw, 16) || isAllZero(spanIdRaw)) return void 0;
281
+ if (!isFixedHex(flagsRaw, 2)) return void 0;
282
+ const flags = Number.parseInt(flagsRaw, 16);
283
+ return {
284
+ traceId: traceIdRaw.toLowerCase(),
285
+ spanId: spanIdRaw.toLowerCase(),
286
+ sampled: (flags & 1) === 1
287
+ };
288
+ }
289
+ function formatTraceparent(trace) {
290
+ return `00-${normalizeTraceId(trace.traceId)}-${normalizeSpanId(trace.spanId)}-${trace.sampled === false ? "00" : "01"}`;
291
+ }
292
+ function extractTraceContext(headers) {
293
+ const parsed = parseTraceparent(readHeader(headers, TRACEPARENT_HEADER));
294
+ if (!parsed) return void 0;
295
+ const traceState = readHeader(headers, TRACESTATE_HEADER);
296
+ const baggage = extractBaggage(headers);
297
+ return {
298
+ ...parsed,
299
+ ...traceState ? { traceState } : {},
300
+ ...baggage ? { baggage } : {}
301
+ };
302
+ }
303
+ function injectTraceContext(headers, trace, options = {}) {
304
+ const out = { ...headers ?? {} };
305
+ const hasTraceparent = hasHeader(out, TRACEPARENT_HEADER);
306
+ const shouldInjectTraceContext = options.overwrite || !hasTraceparent;
307
+ if (!shouldInjectTraceContext) return out;
308
+ if (shouldInjectTraceContext) {
309
+ setHeader(out, TRACEPARENT_HEADER, formatTraceparent(trace));
310
+ }
311
+ if (trace.traceState && (options.overwrite || !hasHeader(out, TRACESTATE_HEADER))) {
312
+ setHeader(out, TRACESTATE_HEADER, trace.traceState);
313
+ }
314
+ if (trace.baggage && Object.keys(trace.baggage).length > 0 && (options.overwrite || !hasHeader(out, BAGGAGE_HEADER))) {
315
+ setHeader(out, BAGGAGE_HEADER, formatBaggage(trace.baggage));
316
+ }
317
+ return out;
318
+ }
319
+ function parseBaggage(value) {
320
+ if (!value) return void 0;
321
+ const entries = {};
322
+ for (const rawMember of value.split(",")) {
323
+ const member = rawMember.trim();
324
+ if (!member) continue;
325
+ const [pair] = member.split(";", 1);
326
+ const eq = pair.indexOf("=");
327
+ if (eq <= 0) continue;
328
+ const key = pair.slice(0, eq).trim();
329
+ const rawValue = pair.slice(eq + 1).trim();
330
+ if (!BAGGAGE_KEY.test(key)) continue;
331
+ entries[key] = decodeBaggageValue(rawValue);
332
+ }
333
+ return Object.keys(entries).length > 0 ? entries : void 0;
334
+ }
335
+ function formatBaggage(baggage) {
336
+ if (!baggage) return "";
337
+ return Object.entries(baggage).filter(([key, value]) => BAGGAGE_KEY.test(key) && value !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${encodeBaggageValue(value)}`).join(",");
338
+ }
339
+ function extractBaggage(headers) {
340
+ return parseBaggage(readHeader(headers, BAGGAGE_HEADER));
341
+ }
342
+ function injectBaggage(headers, baggage, options = {}) {
343
+ const out = { ...headers ?? {} };
344
+ if (!baggage || Object.keys(baggage).length === 0) return out;
345
+ if (!options.overwrite && hasHeader(out, BAGGAGE_HEADER)) return out;
346
+ setHeader(out, BAGGAGE_HEADER, formatBaggage(baggage));
347
+ return out;
348
+ }
349
+ function normalizeTraceId(id) {
350
+ return normalizeHexId(id, 32);
351
+ }
352
+ function normalizeSpanId(id) {
353
+ return normalizeHexId(id, 16);
354
+ }
355
+ function readHeader(headers, name) {
356
+ if (!headers) return void 0;
357
+ const getter = headers.get;
358
+ if (typeof getter === "function") {
359
+ return getter.call(headers, name) ?? void 0;
360
+ }
361
+ if (isIterableHeaders(headers)) {
362
+ for (const [key, value] of headers) {
363
+ if (key.toLowerCase() === name) return value;
364
+ }
365
+ return void 0;
366
+ }
367
+ const record = headers;
368
+ for (const [key, value] of Object.entries(record)) {
369
+ if (key.toLowerCase() !== name) continue;
370
+ if (typeof value === "string") return value;
371
+ if (Array.isArray(value)) return value[0];
372
+ return void 0;
373
+ }
374
+ return void 0;
375
+ }
376
+ function isIterableHeaders(value) {
377
+ return typeof value?.[Symbol.iterator] === "function";
378
+ }
379
+ function hasHeader(headers, name) {
380
+ const lower = name.toLowerCase();
381
+ return Object.keys(headers).some((key) => key.toLowerCase() === lower);
382
+ }
383
+ function setHeader(headers, name, value) {
384
+ const lower = name.toLowerCase();
385
+ const existing = Object.keys(headers).find((key) => key.toLowerCase() === lower);
386
+ headers[existing ?? name] = value;
387
+ }
388
+ function isFixedHex(value, length) {
389
+ return typeof value === "string" && value.length === length && HEX.test(value.toLowerCase());
390
+ }
391
+ function isAllZero(value) {
392
+ for (let i = 0; i < value.length; i++) {
393
+ if (value[i] !== "0") return false;
394
+ }
395
+ return true;
396
+ }
397
+ function normalizeHexId(id, length) {
398
+ const hex = String(id).toLowerCase().replace(/[^0-9a-f]/g, "");
399
+ if (hex.length >= length) return avoidAllZero(hex.slice(0, length));
400
+ if (hex.length > 0) return avoidAllZero(hex.padStart(length, "0"));
401
+ return stableHex(String(id), length);
402
+ }
403
+ function decodeBaggageValue(value) {
404
+ try {
405
+ return decodeURIComponent(value);
406
+ } catch {
407
+ return value;
408
+ }
409
+ }
410
+ function encodeBaggageValue(value) {
411
+ return encodeURIComponent(String(value));
412
+ }
413
+ function avoidAllZero(hex) {
414
+ return isAllZero(hex) ? `${hex.slice(0, -1)}1` : hex;
415
+ }
416
+ function stableHex(input, length) {
417
+ let hash = 2166136261;
418
+ let out = "";
419
+ while (out.length < length) {
420
+ for (let i = 0; i < input.length; i++) {
421
+ hash ^= input.charCodeAt(i);
422
+ hash = Math.imul(hash, 16777619);
423
+ }
424
+ out += (hash >>> 0).toString(16).padStart(8, "0");
425
+ input = `${input}:${out.length}`;
426
+ }
427
+ return avoidAllZero(out.slice(0, length));
428
+ }
429
+
430
+ // src/observability/metrics.ts
431
+ var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
432
+ var OTLP_JSON_CONTENT_TYPE = "application/json";
433
+ var DEFAULT_DURATION_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4];
434
+ function makePrometheusMetricsExporter(registry, options = {}) {
435
+ return {
436
+ contentType: PROMETHEUS_CONTENT_TYPE,
437
+ export: () => formatPrometheusMetrics(registry.snapshot(), options)
438
+ };
439
+ }
440
+ function formatPrometheusMetrics(snapshot, options = {}) {
441
+ const lines = [];
442
+ const emittedHeaders = /* @__PURE__ */ new Set();
443
+ const now = Math.trunc(options.now?.() ?? Date.now());
444
+ const metricName = (name) => sanitizeMetricName(`${options.prefix ?? ""}${name}`);
445
+ const emitHeader = (name, type) => {
446
+ const safeName = metricName(name);
447
+ const key = `${safeName}:${type}`;
448
+ if (emittedHeaders.has(key)) return;
449
+ emittedHeaders.add(key);
450
+ if (options.help?.[name]) lines.push(`# HELP ${safeName} ${escapeHelp(options.help[name])}`);
451
+ lines.push(`# TYPE ${safeName} ${type}`);
452
+ };
453
+ const emitSample = (name, labels, value, exemplar) => {
454
+ const exemplarSuffix = options.includeExemplars && exemplar ? formatExemplar(exemplar) : "";
455
+ const suffix = exemplarSuffix || !options.includeTimestamp ? exemplarSuffix : ` ${now}`;
456
+ lines.push(`${metricName(name)}${formatLabels(labels)} ${formatNumber(value)}${suffix}`);
457
+ };
458
+ for (const counter of snapshot.counters) {
459
+ emitHeader(counter.name, "counter");
460
+ emitSample(counter.name, counter.labels, counter.value);
461
+ }
462
+ for (const gauge of snapshot.gauges) {
463
+ emitHeader(gauge.name, "gauge");
464
+ emitSample(gauge.name, gauge.labels, gauge.value);
465
+ }
466
+ for (const histogram of snapshot.histograms) {
467
+ emitHeader(histogram.name, "histogram");
468
+ const buckets = histogram.buckets;
469
+ let cumulative = 0;
470
+ for (let i = 0; i < buckets.boundaries.length; i++) {
471
+ cumulative += buckets.counts[i] ?? 0;
472
+ emitSample(`${histogram.name}_bucket`, { ...histogram.labels, le: String(buckets.boundaries[i]) }, cumulative, buckets.exemplars?.[i]);
473
+ }
474
+ cumulative += buckets.counts[buckets.boundaries.length] ?? 0;
475
+ emitSample(`${histogram.name}_bucket`, { ...histogram.labels, le: "+Inf" }, cumulative, buckets.exemplars?.[buckets.boundaries.length]);
476
+ emitSample(`${histogram.name}_sum`, histogram.labels, buckets.sum);
477
+ emitSample(`${histogram.name}_count`, histogram.labels, buckets.count);
478
+ }
479
+ return lines.length === 0 ? "" : `${lines.join("\n")}
480
+ `;
481
+ }
482
+ function metricsSnapshotToOtlp(snapshot, options = {}) {
483
+ const timeUnixNano = unixNanoFromMs(options.now?.() ?? Date.now());
484
+ const scope = { name: options.scopeName ?? "brass-runtime" };
485
+ if (options.scopeVersion) scope.version = options.scopeVersion;
486
+ const metrics = [];
487
+ for (const counter of snapshot.counters) {
488
+ metrics.push({
489
+ name: counter.name,
490
+ sum: {
491
+ aggregationTemporality: 2,
492
+ isMonotonic: true,
493
+ dataPoints: [numberDataPoint(counter.labels, counter.value, timeUnixNano)]
494
+ }
495
+ });
496
+ }
497
+ for (const gauge of snapshot.gauges) {
498
+ metrics.push({
499
+ name: gauge.name,
500
+ gauge: {
501
+ dataPoints: [numberDataPoint(gauge.labels, gauge.value, timeUnixNano)]
502
+ }
503
+ });
504
+ }
505
+ for (const histogram of snapshot.histograms) {
506
+ const buckets = histogram.buckets;
507
+ metrics.push({
508
+ name: histogram.name,
509
+ histogram: {
510
+ aggregationTemporality: 2,
511
+ dataPoints: [{
512
+ attributes: toOtlpAttributes(histogram.labels),
513
+ timeUnixNano,
514
+ count: String(buckets.count),
515
+ sum: finiteOrZero(buckets.sum),
516
+ min: finiteOrZero(buckets.min),
517
+ max: finiteOrZero(buckets.max),
518
+ explicitBounds: buckets.boundaries,
519
+ bucketCounts: buckets.counts.map((count) => String(count)),
520
+ ...buckets.exemplars ? { exemplars: buckets.exemplars.filter(isMetricExemplar).map(exemplarToOtlp) } : {}
521
+ }]
522
+ }
523
+ });
524
+ }
525
+ return {
526
+ resourceMetrics: [{
527
+ resource: { attributes: toOtlpAttributes(options.resource ?? {}) },
528
+ scopeMetrics: [{ scope, metrics }]
529
+ }]
530
+ };
531
+ }
532
+ function makeOtlpHttpMetricsExporter(registry, options) {
533
+ return {
534
+ export: async () => {
535
+ const body = JSON.stringify(metricsSnapshotToOtlp(registry.snapshot(), options));
536
+ const response = await postOtlpJson(options, body);
537
+ return { status: response.status, body };
538
+ }
539
+ };
540
+ }
541
+ function makeRuntimeMetricsSink(metrics, options = {}) {
542
+ const clock = options.clock ?? Date.now;
543
+ const durationBuckets = [...options.durationBuckets ?? DEFAULT_DURATION_BUCKETS];
544
+ const includeEventTotals = options.includeEventTotals ?? true;
545
+ const includeSpanNameLabel = options.includeSpanNameLabel ?? false;
546
+ const fiberStarts = /* @__PURE__ */ new Map();
547
+ const scopeStarts = /* @__PURE__ */ new Map();
548
+ const spanStarts = /* @__PURE__ */ new Map();
549
+ const activeFibers = metrics.gauge("brass_runtime_fibers_active");
550
+ const activeScopes = metrics.gauge("brass_runtime_scopes_active");
551
+ const activeSpans = metrics.gauge("brass_runtime_spans_active");
552
+ return {
553
+ emit(ev, ctx) {
554
+ try {
555
+ if (includeEventTotals) metrics.counter("brass_runtime_events_total", { type: ev.type }).increment();
556
+ recordRuntimeMetricEvent(ev, ctx, clock(), durationBuckets, {
557
+ metrics,
558
+ fiberStarts,
559
+ scopeStarts,
560
+ spanStarts,
561
+ activeFibers,
562
+ activeScopes,
563
+ activeSpans,
564
+ includeSpanNameLabel
565
+ });
566
+ } catch {
567
+ }
568
+ }
569
+ };
570
+ }
571
+ function recordRuntimeMetricEvent(ev, ctx, now, durationBuckets, state) {
572
+ switch (ev.type) {
573
+ case "fiber.start":
574
+ state.fiberStarts.set(ev.fiberId, now);
575
+ state.metrics.counter("brass_runtime_fibers_started_total").increment();
576
+ state.activeFibers.increment();
577
+ return;
578
+ case "fiber.end": {
579
+ state.metrics.counter("brass_runtime_fibers_finished_total", { status: ev.status }).increment();
580
+ decrementIfPositive(state.activeFibers);
581
+ const started = state.fiberStarts.get(ev.fiberId);
582
+ state.fiberStarts.delete(ev.fiberId);
583
+ if (started !== void 0) {
584
+ const durationMs = now - started;
585
+ state.metrics.histogram("brass_runtime_fiber_duration_ms", [...durationBuckets], { status: ev.status }).observe(durationMs, exemplarFromTraceContext(ctx, durationMs, now));
586
+ }
587
+ return;
588
+ }
589
+ case "scope.open":
590
+ state.scopeStarts.set(ev.scopeId, now);
591
+ state.metrics.counter("brass_runtime_scopes_opened_total").increment();
592
+ state.activeScopes.increment();
593
+ return;
594
+ case "scope.close": {
595
+ state.metrics.counter("brass_runtime_scopes_closed_total", { status: ev.status }).increment();
596
+ decrementIfPositive(state.activeScopes);
597
+ const started = state.scopeStarts.get(ev.scopeId);
598
+ state.scopeStarts.delete(ev.scopeId);
599
+ if (started !== void 0) {
600
+ const durationMs = now - started;
601
+ state.metrics.histogram("brass_runtime_scope_duration_ms", [...durationBuckets], { status: ev.status }).observe(durationMs, exemplarFromTraceContext(ctx, durationMs, now));
602
+ }
603
+ return;
604
+ }
605
+ case "span.start":
606
+ if (ctx.spanId) state.spanStarts.set(ctx.spanId, now);
607
+ state.metrics.counter("brass_runtime_spans_started_total", state.includeSpanNameLabel ? { name: ev.name } : {}).increment();
608
+ state.activeSpans.increment();
609
+ return;
610
+ case "span.end": {
611
+ const status = ev.status;
612
+ state.metrics.counter("brass_runtime_spans_finished_total", { status }).increment();
613
+ decrementIfPositive(state.activeSpans);
614
+ if (ctx.spanId) {
615
+ const started = state.spanStarts.get(ctx.spanId);
616
+ state.spanStarts.delete(ctx.spanId);
617
+ if (started !== void 0) {
618
+ const durationMs = now - started;
619
+ state.metrics.histogram("brass_runtime_span_duration_ms", [...durationBuckets], { status }).observe(durationMs, exemplarFromTraceContext(ctx, durationMs, now));
620
+ }
621
+ }
622
+ return;
623
+ }
624
+ case "log":
625
+ state.metrics.counter("brass_runtime_logs_total", { level: ev.level }).increment();
626
+ return;
627
+ }
628
+ }
629
+ function exemplarFromTraceContext(trace, value, timestamp = Date.now(), labels) {
630
+ if (!trace?.traceId || !trace?.spanId) return void 0;
631
+ return {
632
+ value,
633
+ timestamp,
634
+ traceId: normalizeTraceId(trace.traceId),
635
+ spanId: normalizeSpanId(trace.spanId),
636
+ ...labels ? { labels } : {}
637
+ };
638
+ }
639
+ function decrementIfPositive(gauge) {
640
+ if (gauge.value() > 0) gauge.decrement();
641
+ }
642
+ function numberDataPoint(labels, value, timeUnixNano) {
643
+ return {
644
+ attributes: toOtlpAttributes(labels),
645
+ timeUnixNano,
646
+ asDouble: finiteOrZero(value)
647
+ };
648
+ }
649
+ function exemplarToOtlp(exemplar) {
650
+ return {
651
+ timeUnixNano: unixNanoFromMs(exemplar.timestamp),
652
+ asDouble: finiteOrZero(exemplar.value),
653
+ ...exemplar.traceId ? { traceId: normalizeTraceId(exemplar.traceId) } : {},
654
+ ...exemplar.spanId ? { spanId: normalizeSpanId(exemplar.spanId) } : {},
655
+ filteredAttributes: toOtlpAttributes(exemplar.labels ?? {})
656
+ };
657
+ }
658
+ function isMetricExemplar(value) {
659
+ return value !== void 0;
660
+ }
661
+ function toOtlpAttributes(labels) {
662
+ return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => ({
663
+ key,
664
+ value: otlpAnyValue(value)
665
+ }));
666
+ }
667
+ function otlpAnyValue(value) {
668
+ if (typeof value === "boolean") return { boolValue: value };
669
+ if (typeof value === "number") return { doubleValue: finiteOrZero(value) };
670
+ return { stringValue: value };
671
+ }
672
+ async function postOtlpJson(options, body) {
673
+ const fetchImpl = options.fetch ?? globalThis.fetch;
674
+ if (typeof fetchImpl !== "function") {
675
+ throw new Error("No fetch implementation available for OTLP export");
676
+ }
677
+ const response = await fetchImpl(options.url, {
678
+ method: "POST",
679
+ headers: {
680
+ "content-type": OTLP_JSON_CONTENT_TYPE,
681
+ ...options.headers ?? {}
682
+ },
683
+ body
684
+ });
685
+ if (response?.ok === false) {
686
+ let detail = "";
687
+ try {
688
+ detail = response.text ? `: ${await response.text()}` : "";
689
+ } catch {
690
+ detail = "";
691
+ }
692
+ throw new Error(`OTLP export failed with status ${response.status ?? "unknown"}${detail}`);
693
+ }
694
+ return response ?? {};
695
+ }
696
+ function unixNanoFromMs(ms) {
697
+ const wholeMs = Math.max(0, Math.trunc(ms));
698
+ return (BigInt(wholeMs) * 1000000n).toString();
699
+ }
700
+ function formatLabels(labels) {
701
+ const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));
702
+ if (entries.length === 0) return "";
703
+ return `{${entries.map(([key, value]) => `${sanitizeLabelName(key)}="${escapeLabelValue(value)}"`).join(",")}}`;
704
+ }
705
+ function formatExemplar(exemplar) {
706
+ const labels = {
707
+ ...exemplar.labels ?? {},
708
+ ...exemplar.traceId ? { trace_id: normalizeTraceId(exemplar.traceId) } : {},
709
+ ...exemplar.spanId ? { span_id: normalizeSpanId(exemplar.spanId) } : {}
710
+ };
711
+ if (Object.keys(labels).length === 0) return "";
712
+ return ` # ${formatLabels(labels)} ${formatNumber(exemplar.value)} ${formatNumber(exemplar.timestamp / 1e3)}`;
713
+ }
714
+ function sanitizeMetricName(name) {
715
+ const safe = name.replace(/[^a-zA-Z0-9_:]/g, "_");
716
+ return /^[a-zA-Z_:]/.test(safe) ? safe : `_${safe}`;
717
+ }
718
+ function sanitizeLabelName(name) {
719
+ const safe = name.replace(/[^a-zA-Z0-9_]/g, "_");
720
+ return /^[a-zA-Z_]/.test(safe) ? safe : `_${safe}`;
721
+ }
722
+ function escapeLabelValue(value) {
723
+ return String(value).replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
724
+ }
725
+ function escapeHelp(value) {
726
+ return String(value).replace(/\\/g, "\\\\").replace(/\n/g, "\\n");
727
+ }
728
+ function formatNumber(value) {
729
+ return Number.isFinite(value) ? String(value) : "0";
730
+ }
731
+ function finiteOrZero(value) {
732
+ return Number.isFinite(value) ? value : 0;
733
+ }
734
+
735
+ // src/observability/logs.ts
736
+ var LEVEL_WEIGHT = {
737
+ debug: 10,
738
+ info: 20,
739
+ warn: 30,
740
+ error: 40
741
+ };
742
+ function makeStructuredLogSink(options = {}) {
743
+ const minLevel = options.minLevel ?? "debug";
744
+ const clock = options.clock ?? Date.now;
745
+ const write = options.write ?? defaultStructuredLogWriter;
746
+ const redactor = makeObservabilityRedactor(options.redact);
747
+ return {
748
+ emit(ev, ctx) {
749
+ if (ev.type !== "log") return;
750
+ if (LEVEL_WEIGHT[ev.level] < LEVEL_WEIGHT[minLevel]) return;
751
+ const wallTs = clock();
752
+ try {
753
+ write({
754
+ ts: new Date(wallTs).toISOString(),
755
+ wallTs,
756
+ level: ev.level,
757
+ message: ev.message,
758
+ fields: redactor.fields(ev.fields ?? {}),
759
+ fiberId: ctx.fiberId,
760
+ scopeId: ctx.scopeId,
761
+ traceId: ctx.traceId,
762
+ spanId: ctx.spanId,
763
+ parentSpanId: ctx.parentSpanId,
764
+ traceState: ctx.traceState
765
+ });
766
+ } catch {
767
+ }
768
+ }
769
+ };
770
+ }
771
+ function formatStructuredLog(record) {
772
+ return JSON.stringify(record);
773
+ }
774
+ function structuredLogsToOtlp(records, options = {}) {
775
+ const scope = { name: options.scopeName ?? "brass-runtime" };
776
+ if (options.scopeVersion) scope.version = options.scopeVersion;
777
+ return {
778
+ resourceLogs: [{
779
+ resource: { attributes: toOtlpAttributes(options.resource ?? {}) },
780
+ scopeLogs: [{
781
+ scope,
782
+ logRecords: records.map(logRecordToOtlp)
783
+ }]
784
+ }]
785
+ };
786
+ }
787
+ function makeOtlpHttpLogExporter(source, options) {
788
+ return {
789
+ export: async () => {
790
+ const records = typeof source === "function" ? source() : source.exportRecords();
791
+ const body = JSON.stringify(structuredLogsToOtlp(records, options));
792
+ const response = await postOtlpJson(options, body);
793
+ return { status: response.status, body, logCount: records.length };
794
+ }
795
+ };
796
+ }
797
+ function logEffect(level, message, fields = {}) {
798
+ return asyncSync(() => {
799
+ const fiber = getCurrentFiber();
800
+ const runtime = fiber?.runtime;
801
+ if (!runtime) return;
802
+ const inherited = fiber.fiberContext?.log ? ctxToObject(fiber.fiberContext.log) : {};
803
+ runtime.log(level, message, { ...inherited, ...fields });
804
+ });
805
+ }
806
+ function withLogContext(patch, effect) {
807
+ return asyncFlatMap(
808
+ startLogContext(patch),
809
+ (state) => asyncFold(
810
+ effect,
811
+ (error) => asyncFlatMap(endLogContext(state), () => asyncFail(error)),
812
+ (value) => asyncFlatMap(endLogContext(state), () => asyncSucceed(value))
813
+ )
814
+ );
815
+ }
816
+ function startLogContext(patch) {
817
+ return asyncSync(() => {
818
+ const fiber = getCurrentFiber();
819
+ if (!fiber?.fiberContext) return void 0;
820
+ const previous = fiber.fiberContext.log ?? emptyContext;
821
+ const state = { fiber, previous, ended: false };
822
+ fiber.fiberContext = { ...fiber.fiberContext, log: ctxExtend(previous, patch) };
823
+ fiber.addFinalizer?.(() => restoreLogContext(state));
824
+ return state;
825
+ });
826
+ }
827
+ function endLogContext(state) {
828
+ return asyncSync(() => restoreLogContext(state));
829
+ }
830
+ function restoreLogContext(state) {
831
+ if (!state || state.ended) return;
832
+ state.ended = true;
833
+ if (state.fiber?.fiberContext) {
834
+ state.fiber.fiberContext = { ...state.fiber.fiberContext, log: state.previous };
835
+ }
836
+ }
837
+ function defaultStructuredLogWriter(record) {
838
+ const line = formatStructuredLog(record);
839
+ if (record.level === "error") console.error(line);
840
+ else console.log(line);
841
+ }
842
+ function logRecordToOtlp(record) {
843
+ return {
844
+ timeUnixNano: unixNanoFromMs(record.wallTs),
845
+ severityText: record.level.toUpperCase(),
846
+ severityNumber: severityNumber(record.level),
847
+ body: { stringValue: record.message },
848
+ ...record.traceId ? { traceId: record.traceId } : {},
849
+ ...record.spanId ? { spanId: record.spanId } : {},
850
+ attributes: toOtlpAttributes(normalizeLogAttributes({
851
+ ...record.fields,
852
+ ...record.fiberId !== void 0 ? { "brass.fiber_id": record.fiberId } : {},
853
+ ...record.scopeId !== void 0 ? { "brass.scope_id": record.scopeId } : {},
854
+ ...record.parentSpanId ? { "brass.parent_span_id": record.parentSpanId } : {},
855
+ ...record.traceState ? { "w3c.tracestate": record.traceState } : {}
856
+ }))
857
+ };
858
+ }
859
+ function severityNumber(level) {
860
+ switch (level) {
861
+ case "debug":
862
+ return 5;
863
+ case "info":
864
+ return 9;
865
+ case "warn":
866
+ return 13;
867
+ case "error":
868
+ return 17;
869
+ }
870
+ }
871
+ function normalizeLogAttributes(fields) {
872
+ const out = {};
873
+ for (const [key, value] of Object.entries(fields)) {
874
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
875
+ out[key] = value;
876
+ } else if (value != null) {
877
+ out[key] = JSON.stringify(value);
878
+ }
879
+ }
880
+ return out;
881
+ }
882
+
883
+ // src/observability/sampling.ts
884
+ var alwaysOnSampler = {
885
+ shouldSample: () => true
886
+ };
887
+ var alwaysOffSampler = {
888
+ shouldSample: () => false
889
+ };
890
+ function ratioSampler(ratio2) {
891
+ const bounded = clampRatio(ratio2);
892
+ if (bounded >= 1) return alwaysOnSampler;
893
+ if (bounded <= 0) return alwaysOffSampler;
894
+ return {
895
+ shouldSample: (input) => traceRatio(input.traceId) < bounded
896
+ };
897
+ }
898
+ function makeTraceSampler(options = {}) {
899
+ const fallback = options.sampler ?? ratioSampler(options.ratio ?? 1);
900
+ const rules = options.rules ?? [];
901
+ return {
902
+ shouldSample(input) {
903
+ for (const rule of rules) {
904
+ if (!samplingRuleMatches(rule, input)) continue;
905
+ if (rule.sampled !== void 0) return rule.sampled;
906
+ if (rule.ratio !== void 0) return shouldSampleWith(ratioSampler(rule.ratio), input);
907
+ }
908
+ return shouldSampleWith(fallback, input);
909
+ }
910
+ };
911
+ }
912
+ function resolveTraceSampling(config) {
913
+ if (config === false) {
914
+ return { sampler: alwaysOffSampler, respectRemoteSampled: true, forceSampleOnError: false };
915
+ }
916
+ if (typeof config === "number") {
917
+ return { sampler: ratioSampler(config), respectRemoteSampled: true, forceSampleOnError: false };
918
+ }
919
+ if (isTraceSampler(config)) {
920
+ return { sampler: config, respectRemoteSampled: true, forceSampleOnError: false };
921
+ }
922
+ return {
923
+ sampler: makeTraceSampler(config ?? {}),
924
+ respectRemoteSampled: config?.respectRemoteSampled ?? true,
925
+ forceSampleOnError: config?.forceSampleOnError ?? false
926
+ };
927
+ }
928
+ function shouldSampleWith(sampler, input) {
929
+ if (!sampler) return true;
930
+ if (typeof sampler === "function") return sampler(input);
931
+ return sampler.shouldSample(input);
932
+ }
933
+ function samplingRuleMatches(rule, input) {
934
+ if (rule.name && !matchText(rule.name, input.spanName)) return false;
935
+ if (rule.route && !matchText(rule.route, input.attributes?.["http.route"])) return false;
936
+ return true;
937
+ }
938
+ function matchText(pattern, value) {
939
+ if (typeof value !== "string") return false;
940
+ return typeof pattern === "string" ? pattern === value : pattern.test(value);
941
+ }
942
+ function isTraceSampler(value) {
943
+ return typeof value === "function" || typeof value === "object" && value !== null && typeof value.shouldSample === "function";
944
+ }
945
+ function traceRatio(traceId) {
946
+ const normalized = normalizeTraceId(traceId);
947
+ const head = normalized.slice(0, 8);
948
+ const value = Number.parseInt(head, 16);
949
+ if (!Number.isFinite(value)) return 1;
950
+ return value / 4294967295;
951
+ }
952
+ function clampRatio(value) {
953
+ if (!Number.isFinite(value)) return 1;
954
+ return Math.max(0, Math.min(1, value));
955
+ }
956
+
957
+ // src/observability/traces.ts
958
+ function withSpan(name, effect, attributesOrOptions) {
959
+ const options = resolveSpanOptions(attributesOrOptions);
960
+ return asyncFlatMap(
961
+ startSpan(name, options),
962
+ (state) => asyncFold(
963
+ effect,
964
+ (error) => asyncFlatMap(endSpan(state, "failure", error), () => asyncFail(error)),
965
+ (value) => asyncFlatMap(endSpan(state, "success"), () => asyncSucceed(value))
966
+ )
967
+ );
968
+ }
969
+ function spanLink(trace, attributes) {
970
+ return {
971
+ traceId: trace.traceId,
972
+ spanId: trace.spanId,
973
+ ...trace.traceState ? { traceState: trace.traceState } : {},
974
+ ...attributes ? { attributes } : {}
975
+ };
976
+ }
977
+ function currentSpanLink(attributes) {
978
+ const fiber = getCurrentFiber();
979
+ const trace = fiber?.fiberContext?.trace;
980
+ return trace?.traceId && trace?.spanId ? spanLink(trace, attributes) : void 0;
981
+ }
982
+ function withBaggage(baggage, effect) {
983
+ return asyncFlatMap(
984
+ startBaggage(baggage),
985
+ (state) => asyncFold(
986
+ effect,
987
+ (error) => asyncFlatMap(endBaggage(state), () => asyncFail(error)),
988
+ (value) => asyncFlatMap(endBaggage(state), () => asyncSucceed(value))
989
+ )
990
+ );
991
+ }
992
+ function currentBaggage() {
993
+ const fiber = getCurrentFiber();
994
+ const baggage = fiber?.fiberContext?.trace?.baggage;
995
+ return baggage ? { ...baggage } : void 0;
996
+ }
997
+ function spanEvent(name, attributes = {}) {
998
+ return asyncSync(() => {
999
+ const fiber = getCurrentFiber();
1000
+ const trace = fiber?.fiberContext?.trace;
1001
+ if (!fiber?.runtime || !trace || trace.sampled === false) return;
1002
+ fiber.runtime.hooks.emit(
1003
+ { type: "span.event", name, attributes },
1004
+ spanContext(fiber, trace)
1005
+ );
1006
+ });
1007
+ }
1008
+ function spansToOtlp(spans, options = {}) {
1009
+ const scope = { name: options.scopeName ?? "brass-runtime" };
1010
+ if (options.scopeVersion) scope.version = options.scopeVersion;
1011
+ return {
1012
+ resourceSpans: [{
1013
+ resource: { attributes: toOtlpAttributes(options.resource ?? {}) },
1014
+ scopeSpans: [{
1015
+ scope,
1016
+ spans: spans.map(spanToOtlp)
1017
+ }]
1018
+ }]
1019
+ };
1020
+ }
1021
+ function makeOtlpHttpSpanExporter(source, options) {
1022
+ return {
1023
+ export: async () => {
1024
+ const spans = typeof source === "function" ? source() : source.exportFinished();
1025
+ const body = JSON.stringify(spansToOtlp(spans, options));
1026
+ const response = await postOtlpJson(options, body);
1027
+ return { status: response.status, body };
1028
+ }
1029
+ };
1030
+ }
1031
+ function startSpan(name, options) {
1032
+ return asyncSync(() => {
1033
+ const fiber = getCurrentFiber();
1034
+ const runtime = fiber?.runtime;
1035
+ if (!fiber?.fiberContext || !runtime) return void 0;
1036
+ const previousTrace = fiber.fiberContext.trace ?? null;
1037
+ const tracer = resolveTracer(runtime);
1038
+ const traceId = previousTrace?.traceId ?? tracer.newTraceId();
1039
+ const sampled = decideSampling(runtime, {
1040
+ traceId,
1041
+ spanName: name,
1042
+ parentSampled: previousTrace?.sampled,
1043
+ attributes: options.attributes
1044
+ });
1045
+ const trace = {
1046
+ traceId,
1047
+ spanId: tracer.newSpanId(),
1048
+ parentSpanId: previousTrace?.spanId,
1049
+ sampled,
1050
+ traceState: previousTrace?.traceState,
1051
+ ...previousTrace?.baggage ? { baggage: previousTrace.baggage } : {}
1052
+ };
1053
+ const state = {
1054
+ fiber,
1055
+ runtime,
1056
+ previousTrace,
1057
+ trace,
1058
+ name,
1059
+ options,
1060
+ forceSampleOnError: runtime?.env?.brass?.forceSampleOnError === true,
1061
+ startEmitted: sampled !== false,
1062
+ ended: false
1063
+ };
1064
+ fiber.fiberContext = { ...fiber.fiberContext, trace };
1065
+ fiber.addFinalizer?.((exit) => {
1066
+ const status = exit?._tag === "Success" ? "success" : exit?.cause?._tag === "Interrupt" ? "interrupted" : "failure";
1067
+ finishSpan(state, status, exit?._tag === "Failure" ? exit.cause : void 0);
1068
+ });
1069
+ if (state.startEmitted) {
1070
+ runtime.hooks.emit(
1071
+ { type: "span.start", name, attributes: options.attributes, links: options.links.map(normalizeSpanLink) },
1072
+ spanContext(fiber, trace)
1073
+ );
1074
+ }
1075
+ return state;
1076
+ });
1077
+ }
1078
+ function endSpan(state, status, error) {
1079
+ return asyncSync(() => finishSpan(state, status, error));
1080
+ }
1081
+ function finishSpan(state, status, error) {
1082
+ if (!state || state.ended) return;
1083
+ state.ended = true;
1084
+ if (!state.startEmitted && status === "failure" && state.forceSampleOnError) {
1085
+ state.trace.sampled = true;
1086
+ state.startEmitted = true;
1087
+ state.runtime.hooks.emit(
1088
+ { type: "span.start", name: state.name, attributes: state.options.attributes, links: state.options.links.map(normalizeSpanLink) },
1089
+ spanContext(state.fiber, state.trace)
1090
+ );
1091
+ }
1092
+ if (state.startEmitted) {
1093
+ state.runtime.hooks.emit(
1094
+ { type: "span.end", name: state.name, status, error },
1095
+ spanContext(state.fiber, state.trace)
1096
+ );
1097
+ }
1098
+ if (state.fiber?.fiberContext) {
1099
+ state.fiber.fiberContext = { ...state.fiber.fiberContext, trace: state.previousTrace };
1100
+ }
1101
+ }
1102
+ function spanContext(fiber, trace) {
1103
+ return {
1104
+ fiberId: fiber?.id,
1105
+ scopeId: fiber?.scopeId,
1106
+ traceId: trace.traceId,
1107
+ spanId: trace.spanId,
1108
+ parentSpanId: trace.parentSpanId,
1109
+ traceState: trace.traceState,
1110
+ baggage: trace.baggage,
1111
+ sampled: trace.sampled
1112
+ };
1113
+ }
1114
+ function startBaggage(baggage) {
1115
+ return asyncSync(() => {
1116
+ const fiber = getCurrentFiber();
1117
+ if (!fiber?.fiberContext) return void 0;
1118
+ const previousTrace = fiber.fiberContext.trace ?? null;
1119
+ if (!previousTrace) return void 0;
1120
+ const state = { fiber, previousTrace, ended: false };
1121
+ fiber.fiberContext = {
1122
+ ...fiber.fiberContext,
1123
+ trace: {
1124
+ ...previousTrace,
1125
+ baggage: {
1126
+ ...previousTrace.baggage ?? {},
1127
+ ...baggage
1128
+ }
1129
+ }
1130
+ };
1131
+ fiber.addFinalizer?.(() => restoreBaggage(state));
1132
+ return state;
1133
+ });
1134
+ }
1135
+ function endBaggage(state) {
1136
+ return asyncSync(() => restoreBaggage(state));
1137
+ }
1138
+ function restoreBaggage(state) {
1139
+ if (!state || state.ended) return;
1140
+ state.ended = true;
1141
+ if (state.fiber?.fiberContext) {
1142
+ state.fiber.fiberContext = { ...state.fiber.fiberContext, trace: state.previousTrace };
1143
+ }
1144
+ }
1145
+ function decideSampling(runtime, input) {
1146
+ const brass = runtime?.env?.brass;
1147
+ if (input.parentSampled === false && brass?.respectRemoteSampled !== false) return false;
1148
+ return shouldSampleWith(brass?.sampler, input);
1149
+ }
1150
+ function resolveTracer(runtime) {
1151
+ const tracer = runtime?.env?.brass?.tracer;
1152
+ if (tracer?.newTraceId && tracer?.newSpanId) return tracer;
1153
+ return {
1154
+ newTraceId: () => randomRuntimeId("trace"),
1155
+ newSpanId: () => randomRuntimeId("span")
1156
+ };
1157
+ }
1158
+ function spanToOtlp(span) {
1159
+ const status = inferOtlpStatus(span);
1160
+ return {
1161
+ traceId: normalizeTraceId(span.traceId),
1162
+ spanId: normalizeSpanId(span.spanId),
1163
+ ...span.parentSpanId ? { parentSpanId: normalizeSpanId(span.parentSpanId) } : {},
1164
+ ...span.traceState ? { traceState: span.traceState } : {},
1165
+ name: span.name,
1166
+ kind: spanKind(span),
1167
+ startTimeUnixNano: unixNanoFromMs(span.startWallTs),
1168
+ endTimeUnixNano: unixNanoFromMs(span.endWallTs ?? span.startWallTs),
1169
+ attributes: toOtlpAttributes(normalizeAttributes(span.attrs)),
1170
+ events: span.events.map((event) => ({
1171
+ name: event.name,
1172
+ timeUnixNano: unixNanoFromMs(event.wallTs),
1173
+ attributes: toOtlpAttributes(normalizeAttributes(event.attrs))
1174
+ })),
1175
+ links: (span.links ?? []).map((link) => ({
1176
+ traceId: normalizeTraceId(link.traceId),
1177
+ spanId: normalizeSpanId(link.spanId),
1178
+ ...link.traceState ? { traceState: link.traceState } : {},
1179
+ attributes: toOtlpAttributes(normalizeAttributes(link.attributes))
1180
+ })),
1181
+ status
1182
+ };
1183
+ }
1184
+ function spanKind(span) {
1185
+ const kind = span.attrs["span.kind"];
1186
+ if (kind === "server") return 2;
1187
+ if (kind === "client") return 3;
1188
+ if (kind === "producer") return 4;
1189
+ if (kind === "consumer") return 5;
1190
+ if (typeof kind === "number" && Number.isInteger(kind) && kind >= 1 && kind <= 5) return kind;
1191
+ return 1;
1192
+ }
1193
+ function resolveSpanOptions(input) {
1194
+ if (!input) return { attributes: {}, links: [] };
1195
+ if ("attributes" in input || "links" in input) {
1196
+ const options = input;
1197
+ return {
1198
+ attributes: options.attributes ?? {},
1199
+ links: options.links ?? []
1200
+ };
1201
+ }
1202
+ return { attributes: input, links: [] };
1203
+ }
1204
+ function normalizeSpanLink(link) {
1205
+ return {
1206
+ traceId: link.traceId,
1207
+ spanId: link.spanId,
1208
+ ...link.traceState ? { traceState: link.traceState } : {},
1209
+ ...link.attributes ? { attributes: link.attributes } : {}
1210
+ };
1211
+ }
1212
+ function inferOtlpStatus(span) {
1213
+ const end = [...span.events].reverse().find((event) => event.name === "span.end" || event.name === "fiber.end");
1214
+ const status = typeof end?.attrs === "object" && end.attrs != null ? end.attrs.status : void 0;
1215
+ if (status === "failure" || status === "interrupted") {
1216
+ return { code: 2, message: status };
1217
+ }
1218
+ if (status === "success") return { code: 1 };
1219
+ return { code: 0 };
1220
+ }
1221
+ function normalizeAttributes(attrs) {
1222
+ if (!attrs || typeof attrs !== "object") return {};
1223
+ const out = {};
1224
+ for (const [key, value] of Object.entries(attrs)) {
1225
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1226
+ out[key] = value;
1227
+ } else if (value != null) {
1228
+ out[key] = String(value);
1229
+ }
1230
+ }
1231
+ return out;
1232
+ }
1233
+ function randomRuntimeId(prefix) {
1234
+ const cryptoLike = globalThis.crypto;
1235
+ if (typeof cryptoLike?.randomUUID === "function") return cryptoLike.randomUUID();
1236
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1237
+ }
1238
+
1239
+ // src/observability/exportPipeline.ts
1240
+ var DEFAULT_BATCH_SIZE = 512;
1241
+ var DEFAULT_MAX_QUEUE_SIZE = 1e4;
1242
+ var DEFAULT_TIMEOUT_MS = 1e4;
1243
+ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 1e4;
1244
+ function makeExportPipeline(options) {
1245
+ const signal = options.signal;
1246
+ const clock = options.clock ?? Date.now;
1247
+ const batchSize = Math.max(1, Math.floor(options.batchSize ?? DEFAULT_BATCH_SIZE));
1248
+ const maxQueueSize = Math.max(0, Math.floor(options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE));
1249
+ const timeoutMs = Math.max(0, Math.floor(options.timeoutMs ?? DEFAULT_TIMEOUT_MS));
1250
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
1251
+ const queueGauge = options.metrics?.gauge("brass_export_queue_size", { signal });
1252
+ let queue = [];
1253
+ let droppedTotal = 0;
1254
+ const setQueueSize = () => queueGauge?.set(queue.length);
1255
+ const dropItems = (items) => {
1256
+ if (items.length === 0) return;
1257
+ droppedTotal += items.length;
1258
+ options.metrics?.counter("brass_export_dropped_total", { signal }).increment(items.length);
1259
+ options.onDrop?.(items);
1260
+ };
1261
+ const enqueue = (items) => {
1262
+ if (items.length === 0 || maxQueueSize === 0) {
1263
+ dropItems([...items]);
1264
+ setQueueSize();
1265
+ return 0;
1266
+ }
1267
+ const incoming = [...items];
1268
+ const available = maxQueueSize - queue.length;
1269
+ if (incoming.length <= available) {
1270
+ queue.push(...incoming);
1271
+ options.metrics?.counter("brass_export_enqueued_total", { signal }).increment(incoming.length);
1272
+ setQueueSize();
1273
+ return incoming.length;
1274
+ }
1275
+ if (dropPolicy === "drop-newest") {
1276
+ const accepted = incoming.slice(0, Math.max(0, available));
1277
+ const dropped2 = incoming.slice(accepted.length);
1278
+ queue.push(...accepted);
1279
+ options.metrics?.counter("brass_export_enqueued_total", { signal }).increment(accepted.length);
1280
+ dropItems(dropped2);
1281
+ setQueueSize();
1282
+ return accepted.length;
1283
+ }
1284
+ const total = queue.length + incoming.length;
1285
+ const dropCount = Math.max(0, total - maxQueueSize);
1286
+ const dropped = queue.splice(0, Math.min(dropCount, queue.length));
1287
+ const remainingDrop = dropCount - dropped.length;
1288
+ if (remainingDrop > 0) dropped.push(...incoming.splice(0, remainingDrop));
1289
+ queue.push(...incoming);
1290
+ options.metrics?.counter("brass_export_enqueued_total", { signal }).increment(incoming.length);
1291
+ dropItems(dropped);
1292
+ setQueueSize();
1293
+ return incoming.length;
1294
+ };
1295
+ const flush = async (flushOptions = {}) => {
1296
+ const startedAt = clock();
1297
+ const deadlineMs = flushOptions.deadlineMs;
1298
+ const errors = [];
1299
+ let exported = 0;
1300
+ let failed = 0;
1301
+ let batchCount = 0;
1302
+ let attempts = 0;
1303
+ let lastStatus;
1304
+ let lastBody;
1305
+ while (queue.length > 0) {
1306
+ if (deadlineMs !== void 0 && clock() - startedAt >= deadlineMs) break;
1307
+ const batch = queue.splice(0, batchSize);
1308
+ const batchStartedAt = clock();
1309
+ try {
1310
+ const result = await exportWithRetry(
1311
+ () => withTimeout(() => options.exportBatch(batch), timeoutMs),
1312
+ {
1313
+ signal,
1314
+ metrics: options.metrics,
1315
+ retry: options.retry
1316
+ }
1317
+ );
1318
+ exported += batch.length;
1319
+ batchCount++;
1320
+ attempts += result.attempts;
1321
+ lastStatus = result.value.status;
1322
+ lastBody = result.value.body;
1323
+ options.metrics?.counter("brass_export_batches_total", { signal, status: "success" }).increment();
1324
+ options.metrics?.counter("brass_export_items_total", { signal, status: "success" }).increment(batch.length);
1325
+ options.metrics?.histogram("brass_export_batch_duration_ms", void 0, { signal, status: "success" }).observe(clock() - batchStartedAt);
1326
+ } catch (error) {
1327
+ failed += batch.length;
1328
+ errors.push(error);
1329
+ queue = [...batch, ...queue];
1330
+ options.metrics?.counter("brass_export_batches_total", { signal, status: "failure" }).increment();
1331
+ options.metrics?.counter("brass_export_items_total", { signal, status: "failure" }).increment(batch.length);
1332
+ options.metrics?.histogram("brass_export_batch_duration_ms", void 0, { signal, status: "failure" }).observe(clock() - batchStartedAt);
1333
+ break;
1334
+ } finally {
1335
+ setQueueSize();
1336
+ }
1337
+ }
1338
+ options.metrics?.histogram("brass_export_flush_duration_ms", void 0, { signal }).observe(clock() - startedAt);
1339
+ return {
1340
+ exported,
1341
+ dropped: droppedTotal,
1342
+ failed,
1343
+ batchCount,
1344
+ attempts,
1345
+ queueSize: queue.length,
1346
+ status: lastStatus,
1347
+ body: lastBody,
1348
+ errors
1349
+ };
1350
+ };
1351
+ return {
1352
+ enqueue,
1353
+ flush,
1354
+ shutdown: (deadline = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS) => flush({ deadlineMs: deadline }),
1355
+ stats: () => ({ queueSize: queue.length, dropped: droppedTotal })
1356
+ };
1357
+ }
1358
+ async function exportWithRetry(operation, options) {
1359
+ const attempts = Math.max(1, Math.floor(options.retry?.attempts ?? 1));
1360
+ const initialDelayMs = Math.max(0, options.retry?.initialDelayMs ?? 100);
1361
+ const maxDelayMs = Math.max(initialDelayMs, options.retry?.maxDelayMs ?? 2e3);
1362
+ const jitterRatio = Math.max(0, Math.min(1, options.retry?.jitterRatio ?? 0.2));
1363
+ const sleep = options.retry?.sleep ?? defaultSleep;
1364
+ let lastError;
1365
+ for (let attempt = 1; attempt <= attempts; attempt++) {
1366
+ try {
1367
+ return { value: await operation(), attempts: attempt };
1368
+ } catch (error) {
1369
+ lastError = error;
1370
+ if (attempt >= attempts) break;
1371
+ options.metrics?.counter("brass_export_retries_total", { signal: options.signal }).increment();
1372
+ await sleep(jitteredDelay(initialDelayMs, maxDelayMs, jitterRatio, attempt));
1373
+ }
1374
+ }
1375
+ throw lastError;
1376
+ }
1377
+ async function withTimeout(operation, timeoutMs) {
1378
+ if (!timeoutMs || timeoutMs <= 0) return operation();
1379
+ let handle;
1380
+ try {
1381
+ return await Promise.race([
1382
+ operation(),
1383
+ new Promise((_resolve, reject) => {
1384
+ handle = setTimeout(() => reject(new Error(`Observability export timed out after ${timeoutMs}ms`)), timeoutMs);
1385
+ handle.unref?.();
1386
+ })
1387
+ ]);
1388
+ } finally {
1389
+ if (handle) clearTimeout(handle);
1390
+ }
1391
+ }
1392
+ function jitteredDelay(initialDelayMs, maxDelayMs, jitterRatio, attempt) {
1393
+ const base = Math.min(maxDelayMs, initialDelayMs * 2 ** Math.max(0, attempt - 1));
1394
+ if (base <= 0 || jitterRatio <= 0) return base;
1395
+ const spread = base * jitterRatio;
1396
+ return Math.max(0, Math.round(base - spread + Math.random() * spread * 2));
1397
+ }
1398
+ function defaultSleep(ms) {
1399
+ return new Promise((resolve) => {
1400
+ const handle = setTimeout(resolve, ms);
1401
+ handle.unref?.();
1402
+ });
1403
+ }
1404
+
1405
+ // src/observability/cardinality.ts
1406
+ function makeCardinalityLimitedMetrics(registry, options = {}) {
1407
+ const maxValuesPerLabel = Math.max(1, Math.floor(options.maxValuesPerLabel ?? 100));
1408
+ const overflowValue = options.overflowValue ?? "__overflow__";
1409
+ const seen = /* @__PURE__ */ new Map();
1410
+ const limitLabels = (metricName, labels = {}) => {
1411
+ const out = {};
1412
+ for (const [label, value] of Object.entries(labels)) {
1413
+ const key = `${metricName}:${label}`;
1414
+ let values = seen.get(key);
1415
+ if (!values) {
1416
+ values = /* @__PURE__ */ new Set();
1417
+ seen.set(key, values);
1418
+ }
1419
+ if (values.has(value) || values.size < maxValuesPerLabel) {
1420
+ values.add(value);
1421
+ out[label] = value;
1422
+ } else {
1423
+ out[label] = overflowValue;
1424
+ }
1425
+ }
1426
+ return out;
1427
+ };
1428
+ return {
1429
+ counter: (name, labels) => registry.counter(name, limitLabels(name, labels)),
1430
+ gauge: (name, labels) => registry.gauge(name, limitLabels(name, labels)),
1431
+ histogram: (name, boundaries, labels) => registry.histogram(name, boundaries, limitLabels(name, labels)),
1432
+ snapshot: () => registry.snapshot(),
1433
+ reset: () => {
1434
+ seen.clear();
1435
+ registry.reset();
1436
+ }
1437
+ };
1438
+ }
1439
+ function normalizeHttpRoute(path) {
1440
+ if (!path) return void 0;
1441
+ const withoutQuery = path.split("?", 1)[0] ?? path;
1442
+ const normalized = withoutQuery.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi, ":id").replace(/\b\d+\b/g, ":id");
1443
+ return normalized || "/";
1444
+ }
1445
+ function sanitizeHttpTarget(url) {
1446
+ if (!url) return void 0;
1447
+ try {
1448
+ const parsed = new URL(url, "http://local");
1449
+ return `${parsed.pathname}${parsed.search ? "?[REDACTED]" : ""}`;
1450
+ } catch {
1451
+ const [withoutHash] = url.split("#", 1);
1452
+ const queryIndex = withoutHash.indexOf("?");
1453
+ return queryIndex < 0 ? withoutHash : `${withoutHash.slice(0, queryIndex)}?[REDACTED]`;
1454
+ }
1455
+ }
1456
+
1457
+ // src/observability/configValidation.ts
1458
+ var fn = Schema.custom((value) => typeof value === "function", "function");
1459
+ var object = Schema.custom(
1460
+ (value) => typeof value === "object" && value !== null && !Array.isArray(value),
1461
+ "object"
1462
+ );
1463
+ var falseOrObject = Schema.union([Schema.literal(false), object]);
1464
+ var ratio = Schema.number({ min: 0, max: 1 });
1465
+ var retryOptions = Schema.object({
1466
+ attempts: Schema.number({ min: 0, int: true }).optional(),
1467
+ initialDelayMs: Schema.number({ min: 0, int: true }).optional(),
1468
+ maxDelayMs: Schema.number({ min: 0, int: true }).optional(),
1469
+ jitterRatio: Schema.number({ min: 0, max: 1 }).optional(),
1470
+ sleep: fn.optional()
1471
+ }, { unknownKeys: "passthrough" });
1472
+ var pipelineOptions = Schema.object({
1473
+ maxQueueSize: Schema.number({ min: 0, int: true }).optional(),
1474
+ batchSize: Schema.number({ min: 1, int: true }).optional(),
1475
+ timeoutMs: Schema.number({ min: 0, int: true }).optional(),
1476
+ retry: retryOptions.optional(),
1477
+ dropPolicy: Schema.enum(["drop-oldest", "drop-newest"]).optional(),
1478
+ shutdownTimeoutMs: Schema.number({ min: 0, int: true }).optional()
1479
+ }, { unknownKeys: "passthrough" });
1480
+ var otlpOptions = Schema.object({
1481
+ metricsUrl: Schema.string().optional(),
1482
+ tracesUrl: Schema.string().optional(),
1483
+ logsUrl: Schema.string().optional(),
1484
+ headers: Schema.record(Schema.string()).optional(),
1485
+ fetch: fn.optional(),
1486
+ timeoutMs: Schema.number({ min: 1, int: true }).optional(),
1487
+ retry: retryOptions.optional(),
1488
+ pipeline: pipelineOptions.optional()
1489
+ }, { unknownKeys: "passthrough" });
1490
+ var samplingOptions = Schema.union([
1491
+ Schema.literal(false),
1492
+ ratio,
1493
+ fn,
1494
+ Schema.object({
1495
+ ratio: ratio.optional(),
1496
+ rules: Schema.array(object).optional(),
1497
+ sampler: Schema.union([fn, object]).optional(),
1498
+ respectRemoteSampled: Schema.boolean().optional(),
1499
+ forceSampleOnError: Schema.boolean().optional()
1500
+ }, { unknownKeys: "passthrough" })
1501
+ ]);
1502
+ var observabilityOptions = Schema.object({
1503
+ serviceName: Schema.string({ minLength: 1 }).optional(),
1504
+ serviceVersion: Schema.string({ minLength: 1 }).optional(),
1505
+ resource: object.optional(),
1506
+ eventBus: object.optional(),
1507
+ metrics: falseOrObject.optional(),
1508
+ logs: falseOrObject.optional(),
1509
+ traces: falseOrObject.optional(),
1510
+ sampling: samplingOptions.optional(),
1511
+ redaction: falseOrObject.optional(),
1512
+ cardinality: Schema.union([
1513
+ Schema.literal(false),
1514
+ Schema.object({
1515
+ maxValuesPerLabel: Schema.number({ min: 1, int: true }).optional(),
1516
+ overflowValue: Schema.string({ minLength: 1 }).optional()
1517
+ }, { unknownKeys: "passthrough" })
1518
+ ]).optional(),
1519
+ otlp: otlpOptions.optional(),
1520
+ flushIntervalMs: Schema.number({ min: 1, int: true }).optional(),
1521
+ autoStart: Schema.boolean().optional(),
1522
+ traceSeed: object.optional(),
1523
+ childName: fn.optional(),
1524
+ onFlushError: fn.optional()
1525
+ }, { unknownKeys: "passthrough" });
1526
+ function validateObservabilityOptions(options) {
1527
+ parseConfig("ObservabilityOptions", observabilityOptions, options);
1528
+ }
1529
+
1530
+ // src/observability/setup.ts
1531
+ function makeObservability(options = {}) {
1532
+ validateObservabilityOptions(options);
1533
+ const serviceName = options.serviceName ?? "brass-runtime";
1534
+ const eventBus = options.eventBus ?? new EventBus();
1535
+ const rawMetrics = makeMetrics();
1536
+ const metrics = options.cardinality === false ? rawMetrics : makeCardinalityLimitedMetrics(rawMetrics, options.cardinality ?? {});
1537
+ const redactor = makeObservabilityRedactor(options.redaction);
1538
+ const traceOptions = options.traces && typeof options.traces === "object" ? options.traces : {};
1539
+ const tracer = new InMemoryTracer({
1540
+ maxFinishedSpans: traceOptions.maxFinishedSpans ?? 1e4,
1541
+ maxSpanAgeMs: traceOptions.maxSpanAgeMs ?? 10 * 6e4,
1542
+ clock: traceOptions.clock,
1543
+ sanitizeAttributes: redactor.attributes,
1544
+ sanitizeError: redactor.value
1545
+ });
1546
+ const traceIdGenerator = makeRuntimeTraceIdGenerator();
1547
+ const sampling = resolveTraceSampling(options.traces === false ? false : options.sampling);
1548
+ const makeEnv = (traceSeed, baggage) => ({
1549
+ brass: {
1550
+ tracer: traceIdGenerator,
1551
+ ...traceSeed ? { traceSeed: baggage ? { ...traceSeed, baggage: { ...traceSeed.baggage ?? {}, ...baggage } } : traceSeed } : {},
1552
+ ...!traceSeed && baggage ? { baggage } : {},
1553
+ sampler: sampling.sampler,
1554
+ respectRemoteSampled: sampling.respectRemoteSampled,
1555
+ forceSampleOnError: sampling.forceSampleOnError,
1556
+ childName: options.childName
1557
+ }
1558
+ });
1559
+ const env = makeEnv(options.traceSeed, options.traceSeed?.baggage);
1560
+ const envForRequest = (input) => makeEnv(resolveRequestTraceSeed(input) ?? options.traceSeed, resolveRequestBaggage(input));
1561
+ const resource = {
1562
+ "service.name": serviceName,
1563
+ ...options.serviceVersion ? { "service.version": options.serviceVersion } : {},
1564
+ ...options.resource ?? {}
1565
+ };
1566
+ if (options.metrics !== false) {
1567
+ const { prometheus: _prometheus, ...metricsOptions } = options.metrics ?? {};
1568
+ eventBus.subscribeHooks(makeRuntimeMetricsSink(metrics, metricsOptions));
1569
+ }
1570
+ const logPipeline = options.logs !== false && options.otlp?.logsUrl ? makeExportPipeline({
1571
+ signal: "logs",
1572
+ metrics,
1573
+ ...options.otlp.pipeline ?? {},
1574
+ timeoutMs: options.otlp.timeoutMs ?? options.otlp.pipeline?.timeoutMs,
1575
+ retry: options.otlp.retry ?? options.otlp.pipeline?.retry,
1576
+ exportBatch: async (records) => {
1577
+ const body = JSON.stringify(structuredLogsToOtlp(records, {
1578
+ resource,
1579
+ scopeName: serviceName,
1580
+ scopeVersion: options.serviceVersion
1581
+ }));
1582
+ const response = await postOtlpJson({
1583
+ url: options.otlp.logsUrl,
1584
+ headers: options.otlp.headers,
1585
+ fetch: options.otlp.fetch
1586
+ }, body);
1587
+ return { status: response.status, body };
1588
+ }
1589
+ }) : void 0;
1590
+ if (options.logs !== false) {
1591
+ const configuredLogs = options.logs ?? {};
1592
+ const configuredWrite = configuredLogs.write;
1593
+ eventBus.subscribeHooks(makeStructuredLogSink({
1594
+ ...configuredLogs,
1595
+ redact: options.logs && options.logs.redact !== void 0 ? options.logs.redact : redactor,
1596
+ write: (record) => {
1597
+ if (configuredWrite) configuredWrite(record);
1598
+ else defaultStructuredLogWriter(record);
1599
+ logPipeline?.enqueue([record]);
1600
+ }
1601
+ }));
1602
+ }
1603
+ if (options.traces !== false) {
1604
+ eventBus.subscribeHooks(tracer);
1605
+ }
1606
+ const prometheus = makePrometheusMetricsExporter(metrics, options.metrics !== false ? options.metrics?.prometheus : void 0);
1607
+ const otlpMetrics = options.otlp?.metricsUrl ? makeOtlpHttpMetricsExporter(metrics, {
1608
+ url: options.otlp.metricsUrl,
1609
+ headers: options.otlp.headers,
1610
+ fetch: options.otlp.fetch,
1611
+ resource,
1612
+ scopeName: serviceName,
1613
+ scopeVersion: options.serviceVersion
1614
+ }) : void 0;
1615
+ const otlpMetricsExporter = otlpMetrics ? {
1616
+ export: async () => {
1617
+ const result = await exportWithRetry(
1618
+ () => withTimeout(() => otlpMetrics.export(), options.otlp?.timeoutMs ?? 1e4),
1619
+ {
1620
+ signal: "metrics",
1621
+ metrics,
1622
+ retry: options.otlp?.retry
1623
+ }
1624
+ );
1625
+ return result.value;
1626
+ }
1627
+ } : void 0;
1628
+ const exportedSpanIds = /* @__PURE__ */ new Set();
1629
+ const queuedSpanIds = /* @__PURE__ */ new Set();
1630
+ const pendingSpans = () => tracer.exportFinished().filter((span) => !exportedSpanIds.has(span.spanId) && !queuedSpanIds.has(span.spanId));
1631
+ const tracePipeline = options.otlp?.tracesUrl ? makeExportPipeline({
1632
+ signal: "traces",
1633
+ metrics,
1634
+ ...options.otlp.pipeline ?? {},
1635
+ timeoutMs: options.otlp.timeoutMs ?? options.otlp.pipeline?.timeoutMs,
1636
+ retry: options.otlp.retry ?? options.otlp.pipeline?.retry,
1637
+ exportBatch: async (spans) => {
1638
+ const body = JSON.stringify(spansToOtlp(spans, {
1639
+ resource,
1640
+ scopeName: serviceName,
1641
+ scopeVersion: options.serviceVersion
1642
+ }));
1643
+ const response = await postOtlpJson({
1644
+ url: options.otlp.tracesUrl,
1645
+ headers: options.otlp.headers,
1646
+ fetch: options.otlp.fetch
1647
+ }, body);
1648
+ const spanIds = spans.map((span) => span.spanId);
1649
+ for (const spanId of spanIds) {
1650
+ exportedSpanIds.add(spanId);
1651
+ queuedSpanIds.delete(spanId);
1652
+ }
1653
+ tracer.pruneFinished(spanIds);
1654
+ for (const spanId of spanIds) exportedSpanIds.delete(spanId);
1655
+ return { status: response.status, body };
1656
+ },
1657
+ onDrop: (spans) => {
1658
+ const spanIds = spans.map((span) => span.spanId);
1659
+ for (const spanId of spanIds) {
1660
+ queuedSpanIds.delete(spanId);
1661
+ exportedSpanIds.add(spanId);
1662
+ }
1663
+ tracer.pruneFinished(spanIds);
1664
+ for (const spanId of spanIds) exportedSpanIds.delete(spanId);
1665
+ }
1666
+ }) : void 0;
1667
+ const otlpTraces = options.otlp?.tracesUrl ? {
1668
+ pipeline: tracePipeline,
1669
+ export: async () => {
1670
+ const spans = pendingSpans();
1671
+ for (const span of spans) queuedSpanIds.add(span.spanId);
1672
+ tracePipeline.enqueue(spans);
1673
+ if (tracePipeline.stats().queueSize === 0) {
1674
+ return { status: void 0, body: "", spanCount: 0 };
1675
+ }
1676
+ const result = await tracePipeline.flush();
1677
+ if (result.errors.length > 0) throw result.errors[0];
1678
+ return {
1679
+ status: result.status,
1680
+ body: result.body ?? "",
1681
+ spanCount: result.exported
1682
+ };
1683
+ }
1684
+ } : void 0;
1685
+ const otlpLogs = logPipeline ? {
1686
+ pipeline: logPipeline,
1687
+ export: async () => {
1688
+ if (logPipeline.stats().queueSize === 0) {
1689
+ return { status: void 0, body: "", logCount: 0 };
1690
+ }
1691
+ const result = await logPipeline.flush();
1692
+ if (result.errors.length > 0) throw result.errors[0];
1693
+ return {
1694
+ status: result.status,
1695
+ body: result.body ?? "",
1696
+ logCount: result.exported
1697
+ };
1698
+ }
1699
+ } : void 0;
1700
+ let interval;
1701
+ const flushNow = async () => {
1702
+ eventBus.flush();
1703
+ const errors = [];
1704
+ let metricsResult;
1705
+ let tracesResult;
1706
+ let logsResult;
1707
+ if (otlpMetricsExporter) {
1708
+ try {
1709
+ metricsResult = await otlpMetricsExporter.export();
1710
+ } catch (error) {
1711
+ errors.push({ signal: "metrics", error });
1712
+ options.onFlushError?.(error, "metrics");
1713
+ }
1714
+ }
1715
+ if (otlpTraces) {
1716
+ try {
1717
+ tracesResult = await otlpTraces.export();
1718
+ } catch (error) {
1719
+ errors.push({ signal: "traces", error });
1720
+ options.onFlushError?.(error, "traces");
1721
+ }
1722
+ }
1723
+ if (otlpLogs) {
1724
+ try {
1725
+ logsResult = await otlpLogs.export();
1726
+ } catch (error) {
1727
+ errors.push({ signal: "logs", error });
1728
+ options.onFlushError?.(error, "logs");
1729
+ }
1730
+ }
1731
+ return { metrics: metricsResult, traces: tracesResult, logs: logsResult, errors };
1732
+ };
1733
+ let inFlightFlush;
1734
+ const flush = () => {
1735
+ if (inFlightFlush) {
1736
+ metrics.counter("brass_export_flush_singleflight_total").increment();
1737
+ return inFlightFlush;
1738
+ }
1739
+ inFlightFlush = flushNow().finally(() => {
1740
+ inFlightFlush = void 0;
1741
+ });
1742
+ return inFlightFlush;
1743
+ };
1744
+ const start = () => {
1745
+ if (interval || !options.flushIntervalMs || options.flushIntervalMs <= 0) return;
1746
+ interval = setInterval(() => {
1747
+ void flush();
1748
+ }, options.flushIntervalMs);
1749
+ interval.unref?.();
1750
+ };
1751
+ const stop = () => {
1752
+ if (!interval) return;
1753
+ clearInterval(interval);
1754
+ interval = void 0;
1755
+ };
1756
+ const shutdown = async () => {
1757
+ stop();
1758
+ const result = await flush();
1759
+ const drainErrors = [];
1760
+ let traces = result.traces;
1761
+ let logs = result.logs;
1762
+ if (tracePipeline && tracePipeline.stats().queueSize > 0) {
1763
+ const drain = await tracePipeline.shutdown(options.otlp?.pipeline?.shutdownTimeoutMs);
1764
+ for (const error of drain.errors) {
1765
+ drainErrors.push({ signal: "traces", error });
1766
+ options.onFlushError?.(error, "traces");
1767
+ }
1768
+ traces = traces ?? {
1769
+ status: drain.status,
1770
+ body: drain.body ?? "",
1771
+ spanCount: drain.exported
1772
+ };
1773
+ }
1774
+ if (logPipeline && logPipeline.stats().queueSize > 0) {
1775
+ const drain = await logPipeline.shutdown(options.otlp?.pipeline?.shutdownTimeoutMs);
1776
+ for (const error of drain.errors) {
1777
+ drainErrors.push({ signal: "logs", error });
1778
+ options.onFlushError?.(error, "logs");
1779
+ }
1780
+ logs = logs ?? {
1781
+ status: drain.status,
1782
+ body: drain.body ?? "",
1783
+ logCount: drain.exported
1784
+ };
1785
+ }
1786
+ if (drainErrors.length === 0 && traces === result.traces && logs === result.logs) return result;
1787
+ return {
1788
+ ...result,
1789
+ traces,
1790
+ logs,
1791
+ errors: [...result.errors, ...drainErrors]
1792
+ };
1793
+ };
1794
+ if (options.autoStart ?? Boolean(options.flushIntervalMs)) {
1795
+ start();
1796
+ }
1797
+ return {
1798
+ hooks: eventBus,
1799
+ eventBus,
1800
+ env,
1801
+ envForRequest,
1802
+ metrics,
1803
+ tracer,
1804
+ traceIdGenerator,
1805
+ exporters: {
1806
+ prometheus,
1807
+ otlpMetrics: otlpMetricsExporter,
1808
+ otlpTraces,
1809
+ otlpLogs
1810
+ },
1811
+ prometheus,
1812
+ health: (healthOptions = {}) => snapshotRuntimeHealth({ ...healthOptions, metrics }),
1813
+ readiness: (healthOptions = {}) => snapshotRuntimeHealth({ ...healthOptions, metrics }).ready,
1814
+ flush,
1815
+ start,
1816
+ stop,
1817
+ shutdown
1818
+ };
1819
+ }
1820
+ function resolveRequestTraceSeed(input) {
1821
+ if (!input) return void 0;
1822
+ if (isTraceContext(input)) return input;
1823
+ const maybeRequest = input;
1824
+ const explicitBaggage = typeof maybeRequest.baggage === "string" ? parseBaggage(maybeRequest.baggage) : maybeRequest.baggage;
1825
+ if (maybeRequest.trace) {
1826
+ return {
1827
+ ...maybeRequest.trace,
1828
+ ...explicitBaggage ? { baggage: { ...maybeRequest.trace.baggage ?? {}, ...explicitBaggage } } : {}
1829
+ };
1830
+ }
1831
+ if (maybeRequest.traceparent) {
1832
+ const trace = parseTraceparent(maybeRequest.traceparent);
1833
+ return trace ? {
1834
+ ...trace,
1835
+ ...maybeRequest.tracestate ? { traceState: maybeRequest.tracestate } : {},
1836
+ ...explicitBaggage ? { baggage: explicitBaggage } : {}
1837
+ } : void 0;
1838
+ }
1839
+ if (maybeRequest.headers) {
1840
+ const trace = extractTraceContext(maybeRequest.headers);
1841
+ return trace && explicitBaggage ? { ...trace, baggage: { ...trace.baggage ?? {}, ...explicitBaggage } } : trace;
1842
+ }
1843
+ return extractTraceContext(input);
1844
+ }
1845
+ function resolveRequestBaggage(input) {
1846
+ if (!input) return void 0;
1847
+ if (isTraceContext(input)) return input.baggage;
1848
+ const maybeRequest = input;
1849
+ if (typeof maybeRequest.baggage === "string") return parseBaggage(maybeRequest.baggage);
1850
+ if (maybeRequest.baggage) return maybeRequest.baggage;
1851
+ if (maybeRequest.trace?.baggage) return maybeRequest.trace.baggage;
1852
+ if (maybeRequest.headers) return extractBaggage(maybeRequest.headers);
1853
+ return extractBaggage(input);
1854
+ }
1855
+ function isTraceContext(value) {
1856
+ return typeof value === "object" && value !== null && typeof value.traceId === "string" && typeof value.spanId === "string";
1857
+ }
1858
+ function makeRuntimeTraceIdGenerator() {
1859
+ return {
1860
+ newTraceId: () => randomHexId(32),
1861
+ newSpanId: () => randomHexId(16)
1862
+ };
1863
+ }
1864
+ function randomHexId(length) {
1865
+ const cryptoLike = globalThis.crypto;
1866
+ if (typeof cryptoLike?.getRandomValues === "function") {
1867
+ const bytes = new Uint8Array(Math.ceil(length / 2));
1868
+ cryptoLike.getRandomValues(bytes);
1869
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, length);
1870
+ }
1871
+ if (typeof cryptoLike?.randomUUID === "function") {
1872
+ const hex = cryptoLike.randomUUID().replace(/-/g, "");
1873
+ if (hex.length >= length) return hex.slice(0, length);
1874
+ }
1875
+ let out = "";
1876
+ while (out.length < length) {
1877
+ out += Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
1878
+ }
1879
+ return out.slice(0, length);
1880
+ }
1881
+
1882
+ // src/observability/request.ts
1883
+ function makeRequestObservabilityContext(observability, input = {}) {
1884
+ const trace = resolveRequestTraceSeed(input);
1885
+ const env = observability.envForRequest(input);
1886
+ const attributes = requestSpanAttributes(input);
1887
+ const defaultSpanName = input.spanName ?? (input.method && input.route ? `${input.method} ${input.route}` : input.route ?? "request");
1888
+ const makeRuntime = (extraEnv, options = {}) => new Runtime({
1889
+ ...options,
1890
+ env: mergeEnv(extraEnv, env),
1891
+ hooks: observability.hooks
1892
+ });
1893
+ return {
1894
+ env,
1895
+ trace,
1896
+ route: input.route,
1897
+ attributes,
1898
+ makeRuntime,
1899
+ run: (effect, extraEnv, options) => makeRuntime(extraEnv, options).toPromise(effect),
1900
+ span: (name, effect, spanAttributes) => withSpan(name, effect, spanAttributes),
1901
+ withRequestSpan: (effect, spanAttributes) => withSpan(defaultSpanName, effect, { ...attributes, ...spanAttributes ?? {} }),
1902
+ flush: observability.flush,
1903
+ shutdown: observability.shutdown
1904
+ };
1905
+ }
1906
+ function mergeEnv(extraEnv, observabilityEnv) {
1907
+ return Object.assign({}, extraEnv ?? {}, observabilityEnv);
1908
+ }
1909
+ function requestSpanAttributes(input) {
1910
+ return {
1911
+ "span.kind": "server",
1912
+ ...input.method ? { "http.method": input.method } : {},
1913
+ ...input.method ? { "http.request.method": input.method } : {},
1914
+ ...input.target ? { "http.target": input.target } : {},
1915
+ ...input.target ? { "url.path": targetPath(input.target) } : {},
1916
+ ...input.route ? { "http.route": input.route } : {},
1917
+ ...input.attributes ?? {}
1918
+ };
1919
+ }
1920
+ function targetPath(target) {
1921
+ try {
1922
+ return new URL(target, "http://local").pathname;
1923
+ } catch {
1924
+ return target.split("?", 1)[0] ?? target;
1925
+ }
1926
+ }
1927
+
1928
+ // src/observability/server.ts
1929
+ var DEFAULT_DURATION_BUCKETS2 = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4];
1930
+ async function runObservedHttpServerEffect(observability, input, effect, options = {}, env, runtimeOptions) {
1931
+ return observeHttpServerRequest(
1932
+ observability,
1933
+ input,
1934
+ async (ctx) => {
1935
+ const effectWithServerEvents = withServerSpanEvents(effect, options);
1936
+ const wrapped = options.spans === false ? effectWithServerEvents : ctx.span(serverSpanName(input, options), effectWithServerEvents, {
1937
+ ...ctx.attributes,
1938
+ ...serverSpanAttributes(input, options)
1939
+ });
1940
+ return ctx.run(wrapped, env, runtimeOptions);
1941
+ },
1942
+ options
1943
+ );
1944
+ }
1945
+ function withServerSpanEvents(effect, options) {
1946
+ return asyncFold(
1947
+ effect,
1948
+ (error) => asyncFlatMap(
1949
+ spanEvent("http.server.error", {
1950
+ "http.response.status_code": 500,
1951
+ "error.type": error instanceof Error ? error.name : typeof error
1952
+ }),
1953
+ () => asyncFail(error)
1954
+ ),
1955
+ (value) => {
1956
+ const statusCode = options.statusCode?.(value) ?? 200;
1957
+ return asyncFlatMap(
1958
+ spanEvent("http.server.response", {
1959
+ "http.response.status_code": statusCode,
1960
+ "http.outcome": statusCode >= 500 ? "error" : "success"
1961
+ }),
1962
+ () => asyncSucceed(value)
1963
+ );
1964
+ }
1965
+ );
1966
+ }
1967
+ async function observeHttpServerRequest(observability, input, handler, options = {}) {
1968
+ const resolved = resolveServerOptions(observability, options);
1969
+ const ctx = makeRequestObservabilityContext(observability, input);
1970
+ const startedAt = resolved.clock();
1971
+ const baseLabels = requestBaseLabels(input, resolved);
1972
+ const inFlight = resolved.metrics?.gauge("brass_http_server_in_flight", baseLabels);
1973
+ inFlight?.increment();
1974
+ try {
1975
+ await ctx.run(logHttpServerRequest(input, resolved));
1976
+ const value = await handler(ctx);
1977
+ const statusCode = resolved.statusCode(value) ?? 200;
1978
+ const durationMs = finishServerObservation(startedAt, input, ctx.trace, statusCode, "success", resolved);
1979
+ await ctx.run(logHttpServerResponse(input, statusCode, durationMs, "success", resolved));
1980
+ return { value, ctx, statusCode, durationMs };
1981
+ } catch (error) {
1982
+ const durationMs = finishServerObservation(startedAt, input, ctx.trace, 500, "exception", resolved);
1983
+ await ctx.run(logHttpServerError(input, error, durationMs, resolved));
1984
+ throw error;
1985
+ } finally {
1986
+ if (inFlight && inFlight.value() > 0) inFlight.decrement();
1987
+ }
1988
+ }
1989
+ function resolveServerOptions(observability, options) {
1990
+ return {
1991
+ metrics: options.metrics === false ? void 0 : options.metrics ?? observability.metrics,
1992
+ logs: options.logs ?? {},
1993
+ spans: options.spans ?? {},
1994
+ includeRouteLabel: options.includeRouteLabel ?? true,
1995
+ clock: options.clock ?? Date.now,
1996
+ durationBuckets: options.durationBuckets,
1997
+ statusCode: options.statusCode ?? (() => void 0)
1998
+ };
1999
+ }
2000
+ function finishServerObservation(startedAt, input, trace, statusCode, outcome, options) {
2001
+ const durationMs = Math.max(0, options.clock() - startedAt);
2002
+ const labels = {
2003
+ ...requestBaseLabels(input, options),
2004
+ outcome: statusCode >= 500 ? "error" : outcome,
2005
+ status: String(statusCode)
2006
+ };
2007
+ options.metrics?.counter("brass_http_server_requests_total", labels).increment();
2008
+ options.metrics?.histogram("brass_http_server_duration_ms", [...options.durationBuckets ?? DEFAULT_DURATION_BUCKETS2], labels).observe(
2009
+ durationMs,
2010
+ exemplarFromTraceContext(trace, durationMs, startedAt + durationMs)
2011
+ );
2012
+ return durationMs;
2013
+ }
2014
+ function logHttpServerRequest(input, options) {
2015
+ const level = options.logs === false ? false : options.logs.requestLevel ?? false;
2016
+ if (!level) return logNoop();
2017
+ return logEffect(level, "http.server.request", requestLogFields(input));
2018
+ }
2019
+ function logHttpServerResponse(input, statusCode, durationMs, outcome, options) {
2020
+ const configured = options.logs === false ? false : options.logs.responseLevel ?? false;
2021
+ const level = configured || (statusCode >= 500 ? options.logs !== false ? options.logs.errorLevel ?? "warn" : false : false);
2022
+ if (!level) return logNoop();
2023
+ return logEffect(level, "http.server.response", {
2024
+ ...requestLogFields(input),
2025
+ status: statusCode,
2026
+ outcome,
2027
+ durationMs
2028
+ });
2029
+ }
2030
+ function logHttpServerError(input, error, durationMs, options) {
2031
+ const level = options.logs === false ? false : options.logs.errorLevel ?? "error";
2032
+ if (!level) return logNoop();
2033
+ return logEffect(level, "http.server.error", {
2034
+ ...requestLogFields(input),
2035
+ status: 500,
2036
+ outcome: "exception",
2037
+ durationMs,
2038
+ error: error instanceof Error ? error.message : String(error)
2039
+ });
2040
+ }
2041
+ function logNoop() {
2042
+ return asyncSucceed(void 0);
2043
+ }
2044
+ function serverSpanName(input, options) {
2045
+ if (options.spans && options.spans.name) {
2046
+ return typeof options.spans.name === "function" ? options.spans.name(input) : options.spans.name;
2047
+ }
2048
+ return input.method && input.route ? `${input.method} ${input.route}` : input.route ?? "request";
2049
+ }
2050
+ function serverSpanAttributes(input, options) {
2051
+ const spanOptions = options.spans === false ? {} : options.spans?.attributes;
2052
+ const custom = typeof spanOptions === "function" ? spanOptions(input) : spanOptions ?? {};
2053
+ return {
2054
+ "span.kind": "server",
2055
+ ...input.method ? { "http.request.method": input.method } : {},
2056
+ ...input.route ? { "http.route": input.route } : {},
2057
+ ...custom
2058
+ };
2059
+ }
2060
+ function requestBaseLabels(input, options) {
2061
+ return compactLabels({
2062
+ method: input.method,
2063
+ ...options.includeRouteLabel ? { route: input.route } : {}
2064
+ });
2065
+ }
2066
+ function requestLogFields(input) {
2067
+ return {
2068
+ ...input.method ? { method: input.method } : {},
2069
+ ...input.route ? { route: input.route } : {},
2070
+ ...input.target ? { target: input.target } : {}
2071
+ };
2072
+ }
2073
+ function compactLabels(labels) {
2074
+ const out = {};
2075
+ for (const [key, value] of Object.entries(labels)) {
2076
+ if (value !== void 0 && value !== "") out[key] = value;
2077
+ }
2078
+ return out;
2079
+ }
2080
+
2081
+ export {
2082
+ snapshotRuntimeHealth,
2083
+ makeRuntimeHealth,
2084
+ runtimeHealth,
2085
+ readiness,
2086
+ healthToHttpResponse,
2087
+ makeObservabilityRedactor,
2088
+ parseTraceparent,
2089
+ formatTraceparent,
2090
+ extractTraceContext,
2091
+ injectTraceContext,
2092
+ parseBaggage,
2093
+ formatBaggage,
2094
+ extractBaggage,
2095
+ injectBaggage,
2096
+ normalizeTraceId,
2097
+ normalizeSpanId,
2098
+ PROMETHEUS_CONTENT_TYPE,
2099
+ OTLP_JSON_CONTENT_TYPE,
2100
+ makePrometheusMetricsExporter,
2101
+ formatPrometheusMetrics,
2102
+ metricsSnapshotToOtlp,
2103
+ makeOtlpHttpMetricsExporter,
2104
+ makeRuntimeMetricsSink,
2105
+ exemplarFromTraceContext,
2106
+ toOtlpAttributes,
2107
+ otlpAnyValue,
2108
+ postOtlpJson,
2109
+ unixNanoFromMs,
2110
+ makeStructuredLogSink,
2111
+ formatStructuredLog,
2112
+ structuredLogsToOtlp,
2113
+ makeOtlpHttpLogExporter,
2114
+ logEffect,
2115
+ withLogContext,
2116
+ defaultStructuredLogWriter,
2117
+ alwaysOnSampler,
2118
+ alwaysOffSampler,
2119
+ ratioSampler,
2120
+ makeTraceSampler,
2121
+ resolveTraceSampling,
2122
+ shouldSampleWith,
2123
+ withSpan,
2124
+ spanLink,
2125
+ currentSpanLink,
2126
+ withBaggage,
2127
+ currentBaggage,
2128
+ spanEvent,
2129
+ spansToOtlp,
2130
+ makeOtlpHttpSpanExporter,
2131
+ makeExportPipeline,
2132
+ exportWithRetry,
2133
+ withTimeout,
2134
+ makeCardinalityLimitedMetrics,
2135
+ normalizeHttpRoute,
2136
+ sanitizeHttpTarget,
2137
+ makeObservability,
2138
+ resolveRequestTraceSeed,
2139
+ resolveRequestBaggage,
2140
+ makeRequestObservabilityContext,
2141
+ runObservedHttpServerEffect,
2142
+ observeHttpServerRequest
2143
+ };