@contractspec/lib.observability 1.57.0 → 1.59.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 (77) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/anomaly/alert-manager.d.ts +17 -0
  3. package/dist/anomaly/alert-manager.js +24 -0
  4. package/dist/anomaly/anomaly-detector.d.ts +22 -0
  5. package/dist/anomaly/anomaly-detector.js +102 -0
  6. package/dist/anomaly/baseline-calculator.d.ts +23 -0
  7. package/dist/anomaly/baseline-calculator.js +40 -0
  8. package/dist/anomaly/root-cause-analyzer.d.ts +19 -0
  9. package/dist/anomaly/root-cause-analyzer.js +32 -0
  10. package/dist/index.d.ts +16 -0
  11. package/dist/index.js +1078 -0
  12. package/dist/intent/aggregator.d.ts +57 -0
  13. package/dist/intent/aggregator.js +110 -0
  14. package/dist/intent/detector.d.ts +28 -0
  15. package/dist/intent/detector.js +133 -0
  16. package/dist/logging/index.d.ts +17 -0
  17. package/dist/logging/index.js +42 -0
  18. package/dist/metrics/index.d.ts +12 -0
  19. package/dist/metrics/index.js +31 -0
  20. package/dist/node/anomaly/alert-manager.js +23 -0
  21. package/dist/node/anomaly/anomaly-detector.js +101 -0
  22. package/dist/node/anomaly/baseline-calculator.js +39 -0
  23. package/dist/node/anomaly/root-cause-analyzer.js +31 -0
  24. package/dist/node/index.js +1077 -0
  25. package/dist/node/intent/aggregator.js +109 -0
  26. package/dist/node/intent/detector.js +132 -0
  27. package/dist/node/logging/index.js +41 -0
  28. package/dist/node/metrics/index.js +30 -0
  29. package/dist/node/pipeline/evolution-pipeline.js +299 -0
  30. package/dist/node/pipeline/lifecycle-pipeline.js +85 -0
  31. package/dist/node/telemetry/posthog-baseline-reader.js +308 -0
  32. package/dist/node/telemetry/posthog-telemetry.js +60 -0
  33. package/dist/node/tracing/index.js +52 -0
  34. package/dist/node/tracing/middleware.js +150 -0
  35. package/dist/pipeline/evolution-pipeline.d.ts +36 -0
  36. package/dist/pipeline/evolution-pipeline.js +300 -0
  37. package/dist/pipeline/lifecycle-pipeline.d.ts +40 -0
  38. package/dist/pipeline/lifecycle-pipeline.js +86 -0
  39. package/dist/telemetry/posthog-baseline-reader.d.ts +27 -0
  40. package/dist/telemetry/posthog-baseline-reader.js +309 -0
  41. package/dist/telemetry/posthog-telemetry.d.ts +15 -0
  42. package/dist/telemetry/posthog-telemetry.js +61 -0
  43. package/dist/tracing/index.d.ts +5 -0
  44. package/dist/tracing/index.js +53 -0
  45. package/dist/tracing/middleware.d.ts +15 -0
  46. package/dist/tracing/middleware.js +151 -0
  47. package/package.json +140 -43
  48. package/dist/anomaly/alert-manager.d.mts +0 -21
  49. package/dist/anomaly/alert-manager.mjs +0 -23
  50. package/dist/anomaly/anomaly-detector.d.mts +0 -26
  51. package/dist/anomaly/anomaly-detector.mjs +0 -58
  52. package/dist/anomaly/baseline-calculator.d.mts +0 -26
  53. package/dist/anomaly/baseline-calculator.mjs +0 -37
  54. package/dist/anomaly/root-cause-analyzer.d.mts +0 -23
  55. package/dist/anomaly/root-cause-analyzer.mjs +0 -27
  56. package/dist/index.d.mts +0 -15
  57. package/dist/index.mjs +0 -16
  58. package/dist/intent/aggregator.d.mts +0 -60
  59. package/dist/intent/aggregator.mjs +0 -98
  60. package/dist/intent/detector.d.mts +0 -32
  61. package/dist/intent/detector.mjs +0 -122
  62. package/dist/logging/index.d.mts +0 -20
  63. package/dist/logging/index.mjs +0 -40
  64. package/dist/metrics/index.d.mts +0 -17
  65. package/dist/metrics/index.mjs +0 -26
  66. package/dist/pipeline/evolution-pipeline.d.mts +0 -40
  67. package/dist/pipeline/evolution-pipeline.mjs +0 -66
  68. package/dist/pipeline/lifecycle-pipeline.d.mts +0 -44
  69. package/dist/pipeline/lifecycle-pipeline.mjs +0 -73
  70. package/dist/telemetry/posthog-baseline-reader.d.mts +0 -31
  71. package/dist/telemetry/posthog-baseline-reader.mjs +0 -266
  72. package/dist/telemetry/posthog-telemetry.d.mts +0 -19
  73. package/dist/telemetry/posthog-telemetry.mjs +0 -61
  74. package/dist/tracing/index.d.mts +0 -9
  75. package/dist/tracing/index.mjs +0 -47
  76. package/dist/tracing/middleware.d.mts +0 -19
  77. package/dist/tracing/middleware.mjs +0 -80
@@ -0,0 +1,308 @@
1
+ // src/telemetry/posthog-baseline-reader.ts
2
+ class PosthogBaselineReader {
3
+ reader;
4
+ eventPrefix;
5
+ constructor(reader, options = {}) {
6
+ this.reader = reader;
7
+ this.eventPrefix = options.eventPrefix ?? "observability";
8
+ }
9
+ async readSamples(input) {
10
+ const result = await this.queryHogQL({
11
+ query: [
12
+ "select",
13
+ " properties.operation as operationName,",
14
+ " properties.version as version,",
15
+ " properties.durationMs as durationMs,",
16
+ " properties.success as success,",
17
+ " properties.errorCode as errorCode,",
18
+ " properties.tenantId as tenantId,",
19
+ " properties.traceId as traceId,",
20
+ " properties.metadata as metadata,",
21
+ " distinct_id as actorId,",
22
+ " timestamp as timestamp",
23
+ "from events",
24
+ `where ${buildOperationWhereClause(this.eventPrefix, input)}`,
25
+ "order by timestamp desc",
26
+ `limit ${input.limit ?? 1000}`
27
+ ].join(`
28
+ `),
29
+ values: buildOperationValues(input)
30
+ });
31
+ return mapTelemetrySamples(result);
32
+ }
33
+ async readAggregatedMetrics(operation, windowDays = 7) {
34
+ const dateRange = buildWindowRange(windowDays);
35
+ const result = await this.queryHogQL({
36
+ query: [
37
+ "select",
38
+ " count() as totalCalls,",
39
+ " avg(properties.durationMs) as averageLatencyMs,",
40
+ " quantile(0.95)(properties.durationMs) as p95LatencyMs,",
41
+ " quantile(0.99)(properties.durationMs) as p99LatencyMs,",
42
+ " max(properties.durationMs) as maxLatencyMs,",
43
+ " sum(if(properties.success = 1, 1, 0)) as successCount,",
44
+ " sum(if(properties.success = 0, 1, 0)) as errorCount",
45
+ "from events",
46
+ `where ${buildOperationWhereClause(this.eventPrefix, {
47
+ operations: [operation],
48
+ dateRange
49
+ })}`
50
+ ].join(`
51
+ `),
52
+ values: buildOperationValues({
53
+ operations: [operation],
54
+ dateRange
55
+ })
56
+ });
57
+ const stats = mapAggregatedMetrics(result, operation, dateRange);
58
+ if (!stats)
59
+ return null;
60
+ const topErrors = await this.readTopErrors(operation, dateRange);
61
+ return {
62
+ ...stats,
63
+ topErrors
64
+ };
65
+ }
66
+ async readOperationSequences(dateRange) {
67
+ const result = await this.queryHogQL({
68
+ query: [
69
+ "select",
70
+ " properties.sequences as sequences",
71
+ "from events",
72
+ `where event = {eventName}`,
73
+ dateRange?.from ? "and timestamp >= {dateFrom}" : "",
74
+ dateRange?.to ? "and timestamp < {dateTo}" : "",
75
+ "order by timestamp desc",
76
+ "limit 50"
77
+ ].filter(Boolean).join(`
78
+ `),
79
+ values: {
80
+ eventName: `${this.eventPrefix}.window`,
81
+ dateFrom: toIsoString(dateRange?.from),
82
+ dateTo: toIsoString(dateRange?.to)
83
+ }
84
+ });
85
+ return mergeSequences(result);
86
+ }
87
+ async readTopErrors(operation, dateRange) {
88
+ const result = await this.queryHogQL({
89
+ query: [
90
+ "select",
91
+ " properties.errorCode as errorCode,",
92
+ " count() as errorCount",
93
+ "from events",
94
+ `where ${buildOperationWhereClause(this.eventPrefix, {
95
+ operations: [operation],
96
+ dateRange
97
+ })} and properties.success = 0`,
98
+ "group by errorCode",
99
+ "order by errorCount desc",
100
+ "limit 5"
101
+ ].join(`
102
+ `),
103
+ values: buildOperationValues({
104
+ operations: [operation],
105
+ dateRange
106
+ })
107
+ });
108
+ const rows = mapRows(result);
109
+ return rows.reduce((acc, row) => {
110
+ const code = asString(row.errorCode);
111
+ if (!code)
112
+ return acc;
113
+ acc[code] = asNumber(row.errorCount);
114
+ return acc;
115
+ }, {});
116
+ }
117
+ async queryHogQL(input) {
118
+ if (!this.reader.queryHogQL) {
119
+ throw new Error("Analytics reader does not support HogQL queries.");
120
+ }
121
+ return this.reader.queryHogQL(input);
122
+ }
123
+ }
124
+ function buildOperationWhereClause(eventPrefix, input) {
125
+ const clauses = [`event = '${eventPrefix}.operation'`];
126
+ if (input.operations?.length) {
127
+ clauses.push(`(${buildOperationFilters(input.operations)})`);
128
+ }
129
+ if (input.dateRange?.from) {
130
+ clauses.push("timestamp >= {dateFrom}");
131
+ }
132
+ if (input.dateRange?.to) {
133
+ clauses.push("timestamp < {dateTo}");
134
+ }
135
+ return clauses.join(" and ");
136
+ }
137
+ function buildOperationValues(input) {
138
+ const values = {
139
+ dateFrom: toIsoString(input.dateRange?.from),
140
+ dateTo: toIsoString(input.dateRange?.to)
141
+ };
142
+ input.operations?.forEach((op, index) => {
143
+ values[`operationName${index}`] = op.name;
144
+ values[`operationVersion${index}`] = op.version;
145
+ });
146
+ return values;
147
+ }
148
+ function buildOperationFilters(operations) {
149
+ return operations.map((_op, index) => `(properties.operation = {operationName${index}} and properties.version = {operationVersion${index}})`).join(" or ");
150
+ }
151
+ function mapTelemetrySamples(result) {
152
+ const rows = mapRows(result);
153
+ return rows.flatMap((row) => {
154
+ const operationName = asString(row.operationName);
155
+ const version = asString(row.version);
156
+ const timestamp = asDate(row.timestamp);
157
+ if (!operationName || !version || !timestamp) {
158
+ return [];
159
+ }
160
+ return [
161
+ {
162
+ operation: { name: operationName, version },
163
+ durationMs: asNumber(row.durationMs),
164
+ success: asBoolean(row.success),
165
+ timestamp,
166
+ errorCode: asOptionalString(row.errorCode) ?? undefined,
167
+ tenantId: asOptionalString(row.tenantId) ?? undefined,
168
+ traceId: asOptionalString(row.traceId) ?? undefined,
169
+ actorId: asOptionalString(row.actorId) ?? undefined,
170
+ metadata: isRecord(row.metadata) ? row.metadata : undefined
171
+ }
172
+ ];
173
+ });
174
+ }
175
+ function mapAggregatedMetrics(result, operation, dateRange) {
176
+ const rows = mapRows(result);
177
+ const row = rows[0];
178
+ if (!row)
179
+ return null;
180
+ const totalCalls = asNumber(row.totalCalls);
181
+ if (!totalCalls)
182
+ return null;
183
+ const successCount = asNumber(row.successCount);
184
+ const errorCount = asNumber(row.errorCount);
185
+ const windowStart = toDate(dateRange.from) ?? new Date;
186
+ const windowEnd = toDate(dateRange.to) ?? new Date;
187
+ return {
188
+ operation,
189
+ totalCalls,
190
+ successRate: totalCalls ? successCount / totalCalls : 0,
191
+ errorRate: totalCalls ? errorCount / totalCalls : 0,
192
+ averageLatencyMs: asNumber(row.averageLatencyMs),
193
+ p95LatencyMs: asNumber(row.p95LatencyMs),
194
+ p99LatencyMs: asNumber(row.p99LatencyMs),
195
+ maxLatencyMs: asNumber(row.maxLatencyMs),
196
+ windowStart,
197
+ windowEnd,
198
+ topErrors: {}
199
+ };
200
+ }
201
+ function mergeSequences(result) {
202
+ const rows = mapRows(result);
203
+ const merged = new Map;
204
+ rows.forEach((row) => {
205
+ const sequences = row.sequences;
206
+ if (!Array.isArray(sequences))
207
+ return;
208
+ sequences.forEach((sequence) => {
209
+ if (!isRecord(sequence))
210
+ return;
211
+ const steps = Array.isArray(sequence.steps) ? sequence.steps.filter((step) => typeof step === "string") : [];
212
+ if (steps.length === 0)
213
+ return;
214
+ const tenantId = typeof sequence.tenantId === "string" ? sequence.tenantId : undefined;
215
+ const count = typeof sequence.count === "number" && Number.isFinite(sequence.count) ? sequence.count : 0;
216
+ const key = `${tenantId ?? "global"}:${steps.join(">")}`;
217
+ const existing = merged.get(key);
218
+ if (existing) {
219
+ existing.count += count;
220
+ } else {
221
+ merged.set(key, { steps, tenantId, count });
222
+ }
223
+ });
224
+ });
225
+ return [...merged.values()];
226
+ }
227
+ function mapRows(result) {
228
+ if (!Array.isArray(result.results) || !Array.isArray(result.columns)) {
229
+ return [];
230
+ }
231
+ const columns = result.columns;
232
+ return result.results.flatMap((row) => {
233
+ if (!Array.isArray(row))
234
+ return [];
235
+ const record = {};
236
+ columns.forEach((column, index) => {
237
+ record[column] = row[index];
238
+ });
239
+ return [record];
240
+ });
241
+ }
242
+ function buildWindowRange(windowDays) {
243
+ const windowEnd = new Date;
244
+ const windowStart = new Date(windowEnd.getTime() - windowDays * 24 * 60 * 60 * 1000);
245
+ return {
246
+ from: windowStart,
247
+ to: windowEnd
248
+ };
249
+ }
250
+ function asString(value) {
251
+ if (typeof value === "string" && value.trim())
252
+ return value;
253
+ if (typeof value === "number")
254
+ return String(value);
255
+ return null;
256
+ }
257
+ function asOptionalString(value) {
258
+ if (typeof value === "string")
259
+ return value;
260
+ if (typeof value === "number")
261
+ return String(value);
262
+ return null;
263
+ }
264
+ function asNumber(value) {
265
+ if (typeof value === "number" && Number.isFinite(value))
266
+ return value;
267
+ if (typeof value === "string" && value.trim()) {
268
+ const parsed = Number(value);
269
+ if (Number.isFinite(parsed))
270
+ return parsed;
271
+ }
272
+ return 0;
273
+ }
274
+ function asBoolean(value) {
275
+ if (typeof value === "boolean")
276
+ return value;
277
+ if (typeof value === "number")
278
+ return value !== 0;
279
+ if (typeof value === "string")
280
+ return value.toLowerCase() === "true";
281
+ return false;
282
+ }
283
+ function asDate(value) {
284
+ if (value instanceof Date)
285
+ return value;
286
+ if (typeof value === "string" || typeof value === "number") {
287
+ const date = new Date(value);
288
+ if (!Number.isNaN(date.getTime()))
289
+ return date;
290
+ }
291
+ return null;
292
+ }
293
+ function toIsoString(value) {
294
+ if (!value)
295
+ return;
296
+ return typeof value === "string" ? value : value.toISOString();
297
+ }
298
+ function toDate(value) {
299
+ if (!value)
300
+ return null;
301
+ return value instanceof Date ? value : new Date(value);
302
+ }
303
+ function isRecord(value) {
304
+ return typeof value === "object" && value !== null;
305
+ }
306
+ export {
307
+ PosthogBaselineReader
308
+ };
@@ -0,0 +1,60 @@
1
+ // src/telemetry/posthog-telemetry.ts
2
+ class PosthogTelemetryProvider {
3
+ provider;
4
+ eventPrefix;
5
+ includeMetadata;
6
+ constructor(provider, options = {}) {
7
+ this.provider = provider;
8
+ this.eventPrefix = options.eventPrefix ?? "observability";
9
+ this.includeMetadata = options.includeMetadata ?? false;
10
+ }
11
+ async captureSample(sample) {
12
+ await this.provider.capture({
13
+ distinctId: sample.actorId ?? sample.tenantId ?? "unknown",
14
+ event: `${this.eventPrefix}.operation`,
15
+ timestamp: sample.timestamp,
16
+ properties: {
17
+ operation: sample.operation.name,
18
+ version: sample.operation.version,
19
+ durationMs: sample.durationMs,
20
+ success: sample.success,
21
+ errorCode: sample.errorCode ?? null,
22
+ tenantId: sample.tenantId ?? null,
23
+ traceId: sample.traceId ?? null,
24
+ ...this.includeMetadata && sample.metadata ? { metadata: sample.metadata } : {}
25
+ }
26
+ });
27
+ }
28
+ async captureSnapshot(snapshot) {
29
+ await this.provider.capture({
30
+ distinctId: "system",
31
+ event: `${this.eventPrefix}.window`,
32
+ timestamp: snapshot.windowEnd ?? new Date,
33
+ properties: {
34
+ sampleCount: snapshot.sampleCount,
35
+ metricsCount: snapshot.metrics.length,
36
+ sequencesCount: snapshot.sequences.length,
37
+ windowStart: snapshot.windowStart?.toISOString() ?? null,
38
+ windowEnd: snapshot.windowEnd?.toISOString() ?? null,
39
+ ...this.includeMetadata ? {
40
+ metrics: snapshot.metrics.map((metric) => ({
41
+ operation: metric.operation.name,
42
+ version: metric.operation.version,
43
+ totalCalls: metric.totalCalls,
44
+ successRate: metric.successRate,
45
+ errorRate: metric.errorRate,
46
+ averageLatencyMs: metric.averageLatencyMs,
47
+ p95LatencyMs: metric.p95LatencyMs,
48
+ p99LatencyMs: metric.p99LatencyMs,
49
+ maxLatencyMs: metric.maxLatencyMs,
50
+ topErrors: metric.topErrors
51
+ })),
52
+ sequences: snapshot.sequences
53
+ } : {}
54
+ }
55
+ });
56
+ }
57
+ }
58
+ export {
59
+ PosthogTelemetryProvider
60
+ };
@@ -0,0 +1,52 @@
1
+ // src/tracing/index.ts
2
+ import {
3
+ SpanStatusCode,
4
+ trace
5
+ } from "@opentelemetry/api";
6
+ var DEFAULT_TRACER_NAME = "@contractspec/lib.observability";
7
+ function getTracer(name = DEFAULT_TRACER_NAME) {
8
+ return trace.getTracer(name);
9
+ }
10
+ async function traceAsync(name, fn, tracerName) {
11
+ const tracer = getTracer(tracerName);
12
+ return tracer.startActiveSpan(name, async (span) => {
13
+ try {
14
+ const result = await fn(span);
15
+ span.setStatus({ code: SpanStatusCode.OK });
16
+ return result;
17
+ } catch (error) {
18
+ span.recordException(error);
19
+ span.setStatus({
20
+ code: SpanStatusCode.ERROR,
21
+ message: error instanceof Error ? error.message : String(error)
22
+ });
23
+ throw error;
24
+ } finally {
25
+ span.end();
26
+ }
27
+ });
28
+ }
29
+ function traceSync(name, fn, tracerName) {
30
+ const tracer = getTracer(tracerName);
31
+ return tracer.startActiveSpan(name, (span) => {
32
+ try {
33
+ const result = fn(span);
34
+ span.setStatus({ code: SpanStatusCode.OK });
35
+ return result;
36
+ } catch (error) {
37
+ span.recordException(error);
38
+ span.setStatus({
39
+ code: SpanStatusCode.ERROR,
40
+ message: error instanceof Error ? error.message : String(error)
41
+ });
42
+ throw error;
43
+ } finally {
44
+ span.end();
45
+ }
46
+ });
47
+ }
48
+ export {
49
+ traceSync,
50
+ traceAsync,
51
+ getTracer
52
+ };
@@ -0,0 +1,150 @@
1
+ // src/tracing/index.ts
2
+ import {
3
+ SpanStatusCode,
4
+ trace
5
+ } from "@opentelemetry/api";
6
+ var DEFAULT_TRACER_NAME = "@contractspec/lib.observability";
7
+ function getTracer(name = DEFAULT_TRACER_NAME) {
8
+ return trace.getTracer(name);
9
+ }
10
+ async function traceAsync(name, fn, tracerName) {
11
+ const tracer = getTracer(tracerName);
12
+ return tracer.startActiveSpan(name, async (span) => {
13
+ try {
14
+ const result = await fn(span);
15
+ span.setStatus({ code: SpanStatusCode.OK });
16
+ return result;
17
+ } catch (error) {
18
+ span.recordException(error);
19
+ span.setStatus({
20
+ code: SpanStatusCode.ERROR,
21
+ message: error instanceof Error ? error.message : String(error)
22
+ });
23
+ throw error;
24
+ } finally {
25
+ span.end();
26
+ }
27
+ });
28
+ }
29
+ function traceSync(name, fn, tracerName) {
30
+ const tracer = getTracer(tracerName);
31
+ return tracer.startActiveSpan(name, (span) => {
32
+ try {
33
+ const result = fn(span);
34
+ span.setStatus({ code: SpanStatusCode.OK });
35
+ return result;
36
+ } catch (error) {
37
+ span.recordException(error);
38
+ span.setStatus({
39
+ code: SpanStatusCode.ERROR,
40
+ message: error instanceof Error ? error.message : String(error)
41
+ });
42
+ throw error;
43
+ } finally {
44
+ span.end();
45
+ }
46
+ });
47
+ }
48
+
49
+ // src/metrics/index.ts
50
+ import {
51
+ metrics
52
+ } from "@opentelemetry/api";
53
+ var DEFAULT_METER_NAME = "@contractspec/lib.observability";
54
+ function getMeter(name = DEFAULT_METER_NAME) {
55
+ return metrics.getMeter(name);
56
+ }
57
+ function createCounter(name, description, meterName) {
58
+ return getMeter(meterName).createCounter(name, { description });
59
+ }
60
+ function createUpDownCounter(name, description, meterName) {
61
+ return getMeter(meterName).createUpDownCounter(name, { description });
62
+ }
63
+ function createHistogram(name, description, meterName) {
64
+ return getMeter(meterName).createHistogram(name, { description });
65
+ }
66
+ var standardMetrics = {
67
+ httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
68
+ httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
69
+ operationErrors: createCounter("operation_errors_total", "Total operation errors"),
70
+ workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
71
+ };
72
+
73
+ // src/tracing/middleware.ts
74
+ function createTracingMiddleware(options = {}) {
75
+ return async (req, next) => {
76
+ const method = req.method;
77
+ const url = new URL(req.url);
78
+ const path = url.pathname;
79
+ standardMetrics.httpRequests.add(1, { method, path });
80
+ const startTime = performance.now();
81
+ return traceAsync(`HTTP ${method} ${path}`, async (span) => {
82
+ span.setAttribute("http.method", method);
83
+ span.setAttribute("http.url", req.url);
84
+ try {
85
+ const response = await next();
86
+ span.setAttribute("http.status_code", response.status);
87
+ const duration = (performance.now() - startTime) / 1000;
88
+ standardMetrics.httpDuration.record(duration, {
89
+ method,
90
+ path,
91
+ status: response.status.toString()
92
+ });
93
+ emitTelemetrySample({
94
+ req,
95
+ res: response,
96
+ span,
97
+ success: true,
98
+ durationMs: duration * 1000,
99
+ options
100
+ });
101
+ return response;
102
+ } catch (error) {
103
+ standardMetrics.operationErrors.add(1, { method, path });
104
+ emitTelemetrySample({
105
+ req,
106
+ span,
107
+ success: false,
108
+ durationMs: performance.now() - startTime,
109
+ error,
110
+ options
111
+ });
112
+ throw error;
113
+ }
114
+ });
115
+ };
116
+ }
117
+ function emitTelemetrySample({
118
+ req,
119
+ res,
120
+ span,
121
+ success,
122
+ durationMs,
123
+ error,
124
+ options
125
+ }) {
126
+ if (!options.onSample || !options.resolveOperation)
127
+ return;
128
+ const operation = options.resolveOperation({ req, res });
129
+ if (!operation)
130
+ return;
131
+ const sample = {
132
+ operation,
133
+ durationMs,
134
+ success,
135
+ timestamp: new Date,
136
+ errorCode: !success && error instanceof Error ? error.name : success ? undefined : "unknown",
137
+ tenantId: options.tenantResolver?.(req),
138
+ actorId: options.actorResolver?.(req),
139
+ traceId: span.spanContext().traceId,
140
+ metadata: {
141
+ method: req.method,
142
+ path: new URL(req.url).pathname,
143
+ status: res?.status
144
+ }
145
+ };
146
+ options.onSample(sample);
147
+ }
148
+ export {
149
+ createTracingMiddleware
150
+ };
@@ -0,0 +1,36 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { IntentAggregator, type IntentAggregatorSnapshot, type TelemetrySample } from '../intent/aggregator';
3
+ import { IntentDetector, type IntentSignal } from '../intent/detector';
4
+ export type EvolutionPipelineEvent = {
5
+ type: 'intent.detected';
6
+ payload: IntentSignal;
7
+ } | {
8
+ type: 'telemetry.window';
9
+ payload: {
10
+ sampleCount: number;
11
+ };
12
+ };
13
+ export interface EvolutionPipelineOptions {
14
+ detector?: IntentDetector;
15
+ aggregator?: IntentAggregator;
16
+ emitter?: EventEmitter;
17
+ onIntent?: (intent: IntentSignal) => Promise<void> | void;
18
+ onSnapshot?: (snapshot: IntentAggregatorSnapshot) => Promise<void> | void;
19
+ }
20
+ export declare class EvolutionPipeline {
21
+ private readonly detector;
22
+ private readonly aggregator;
23
+ private readonly emitter;
24
+ private readonly onIntent?;
25
+ private readonly onSnapshot?;
26
+ private timer?;
27
+ private previousMetrics?;
28
+ constructor(options?: EvolutionPipelineOptions);
29
+ ingest(sample: TelemetrySample): void;
30
+ on(listener: (event: EvolutionPipelineEvent) => void): void;
31
+ start(intervalMs?: number): void;
32
+ stop(): void;
33
+ run(): Promise<void>;
34
+ private emit;
35
+ }
36
+ //# sourceMappingURL=evolution-pipeline.d.ts.map