@mandujs/core 0.54.11 → 0.54.13

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 (76) hide show
  1. package/package.json +208 -200
  2. package/scripts/postinstall-lock.ts +153 -153
  3. package/src/a11y/run-audit.ts +15 -15
  4. package/src/agent/__tests__/context.test.ts +49 -1
  5. package/src/agent/context.ts +535 -535
  6. package/src/agent/index.ts +6 -6
  7. package/src/agent/plan.ts +282 -282
  8. package/src/agent/repair.ts +171 -171
  9. package/src/agent/sync.ts +200 -200
  10. package/src/agent/types.ts +8 -0
  11. package/src/agent/verify.ts +100 -2
  12. package/src/brain/doctor/analyzer.ts +7 -7
  13. package/src/bundler/__tests__/build-runner.ts +5 -4
  14. package/src/bundler/__tests__/cold-start.test.ts +60 -60
  15. package/src/bundler/__tests__/css.test.ts +20 -20
  16. package/src/bundler/analyzer.ts +15 -15
  17. package/src/bundler/build.test.ts +73 -7
  18. package/src/bundler/build.ts +139 -31
  19. package/src/bundler/css.ts +42 -42
  20. package/src/bundler/manifest-schema.ts +21 -21
  21. package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
  22. package/src/bundler/plugins/block-generated-imports.ts +13 -13
  23. package/src/bundler/types.ts +31 -31
  24. package/src/client/island.ts +79 -79
  25. package/src/config/validate.ts +1 -1
  26. package/src/contract/schema.ts +7 -0
  27. package/src/deploy/inference/context.ts +82 -82
  28. package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
  29. package/src/devtools/client/components/panel/panel-container.tsx +1 -1
  30. package/src/error/formatter.ts +10 -1
  31. package/src/experimental/index.ts +10 -0
  32. package/src/filling/context.ts +17 -17
  33. package/src/filling/filling.ts +22 -1
  34. package/src/filling/index.ts +15 -1
  35. package/src/generator/generate.ts +30 -30
  36. package/src/generator/index.ts +3 -3
  37. package/src/generator/templates.ts +210 -210
  38. package/src/guard/check.ts +9 -9
  39. package/src/guard/config-guard.ts +13 -13
  40. package/src/guard/fs-routes-policy.ts +51 -51
  41. package/src/guard/index.ts +11 -11
  42. package/src/index.ts +0 -10
  43. package/src/internal/index.ts +25 -0
  44. package/src/kitchen/api/file-api.ts +11 -11
  45. package/src/report/index.ts +1 -1
  46. package/src/resource/__tests__/generator.test.ts +6 -6
  47. package/src/resource/__tests__/schema.test.ts +14 -14
  48. package/src/resource/ddl/__tests__/emit.test.ts +165 -165
  49. package/src/resource/ddl/emit.ts +146 -146
  50. package/src/resource/generator-schema.ts +11 -11
  51. package/src/resource/generators/slot.ts +72 -72
  52. package/src/resource/schema.ts +21 -21
  53. package/src/router/client-entry.test.ts +69 -33
  54. package/src/router/client-entry.ts +134 -74
  55. package/src/router/fs-routes.ts +24 -22
  56. package/src/router/fs-scanner.ts +21 -17
  57. package/src/router/fs-types.ts +8 -5
  58. package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
  59. package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
  60. package/src/runtime/__tests__/page-render-response.test.ts +103 -103
  61. package/src/runtime/__tests__/request-middleware.test.ts +70 -70
  62. package/src/runtime/devtools-adapter.ts +68 -68
  63. package/src/runtime/escape.ts +34 -34
  64. package/src/runtime/image-feature.ts +15 -0
  65. package/src/runtime/observability-lifecycle.ts +290 -290
  66. package/src/runtime/page-render-response.ts +106 -106
  67. package/src/runtime/rate-limit.ts +231 -0
  68. package/src/runtime/request-middleware.ts +31 -31
  69. package/src/runtime/scheduler-lifecycle.ts +64 -0
  70. package/src/runtime/server.ts +27 -295
  71. package/src/runtime/ssr.ts +59 -59
  72. package/src/runtime/static-files.ts +289 -289
  73. package/src/runtime/streaming-ssr.ts +22 -22
  74. package/src/spec/schema.ts +4 -3
  75. package/src/watcher/__tests__/watcher.test.ts +59 -59
  76. package/src/watcher/watcher.ts +61 -61
@@ -1,290 +1,290 @@
1
- import {
2
- eventBus,
3
- type EventType,
4
- type ObservabilityEvent,
5
- type ObservabilitySeverity,
6
- } from "../observability/event-bus";
7
- import {
8
- HEAP_ENDPOINT,
9
- METRICS_ENDPOINT,
10
- buildMetricsResponse,
11
- collectHeapSnapshot,
12
- isObservabilityExposed,
13
- recordHttpRequest,
14
- } from "../observability/metrics";
15
- import {
16
- createTracerFromConfig,
17
- runWithSpan,
18
- setTracer,
19
- type Tracer,
20
- type TracerConfig,
21
- } from "../observability/tracing";
22
- import { collectPerfSnapshot } from "../perf/user-marks";
23
-
24
- export const INTERNAL_EVENTS_ENDPOINT = "/__mandu/events";
25
-
26
- export interface RuntimeRequestRecord {
27
- id: string;
28
- method: string;
29
- path: string;
30
- status: number;
31
- duration: number;
32
- timestamp: number;
33
- cacheStatus?: string;
34
- }
35
-
36
- export interface RuntimeRequestObservation {
37
- req: Request;
38
- path: string;
39
- status: number;
40
- duration: number;
41
- correlationId: string;
42
- cacheStatus?: string;
43
- error?: boolean;
44
- recordRequest?: (entry: RuntimeRequestRecord) => void;
45
- }
46
-
47
- export interface RuntimeObservabilityLifecycle {
48
- readonly tracer: Tracer | undefined;
49
- handleEndpoint(req: Request, pathname: string): Response | null;
50
- runRequest<T extends Response>(
51
- req: Request,
52
- requestStart: number,
53
- correlationId: string,
54
- handler: () => Promise<T>
55
- ): Promise<T>;
56
- recordHttpResponse(req: Request, response: Response | undefined): void;
57
- recordDevRequest(observation: RuntimeRequestObservation): void;
58
- }
59
-
60
- export interface CreateRuntimeObservabilityLifecycleOptions {
61
- isDev: boolean;
62
- heapEndpoint?: boolean;
63
- metricsEndpoint?: boolean;
64
- tracing?: TracerConfig;
65
- }
66
-
67
- export function createRuntimeObservabilityLifecycle(
68
- options: CreateRuntimeObservabilityLifecycleOptions
69
- ): RuntimeObservabilityLifecycle {
70
- const tracerInstance = createTracerFromConfig(options.tracing);
71
- setTracer(tracerInstance);
72
- const tracer = tracerInstance.enabled ? tracerInstance : undefined;
73
-
74
- return {
75
- tracer,
76
- handleEndpoint(req, pathname) {
77
- return handleRuntimeObservabilityEndpoint(req, pathname, options);
78
- },
79
- async runRequest(req, requestStart, correlationId, handler) {
80
- if (!tracer || !tracer.enabled) {
81
- return await handler();
82
- }
83
-
84
- const url = new URL(req.url);
85
- const rootSpan = tracer.startSpanFromRequest("http.request", req, {
86
- kind: "server",
87
- attributes: {
88
- "http.method": req.method,
89
- "http.url": req.url,
90
- "http.target": url.pathname,
91
- "http.scheme": url.protocol.replace(":", ""),
92
- "http.host": url.host,
93
- "mandu.correlation_id": correlationId,
94
- "mandu.request_start_ms": requestStart,
95
- },
96
- });
97
-
98
- try {
99
- const response = await runWithSpan(rootSpan, handler);
100
- rootSpan.setAttribute("http.status_code", response.status);
101
- if (response.status >= 500) {
102
- rootSpan.setStatus("error", `HTTP ${response.status}`);
103
- } else if (rootSpan.status === "unset") {
104
- rootSpan.setStatus("ok");
105
- }
106
- return response;
107
- } catch (err) {
108
- const message = err instanceof Error ? err.message : String(err);
109
- rootSpan.setStatus("error", message);
110
- throw err;
111
- } finally {
112
- rootSpan.end();
113
- }
114
- },
115
- recordHttpResponse(req, response) {
116
- if (!response) return;
117
- try {
118
- recordHttpRequest(req.method, response.status);
119
- } catch {
120
- // Observability must never break the request path.
121
- }
122
- },
123
- recordDevRequest(observation) {
124
- recordRuntimeObservation(observation);
125
- },
126
- };
127
- }
128
-
129
- function handleRuntimeObservabilityEndpoint(
130
- req: Request,
131
- pathname: string,
132
- options: CreateRuntimeObservabilityLifecycleOptions
133
- ): Response | null {
134
- if (pathname === INTERNAL_EVENTS_ENDPOINT) {
135
- return handleEventsStreamRequest(req);
136
- }
137
- if (pathname === `${INTERNAL_EVENTS_ENDPOINT}/recent`) {
138
- return handleEventsRecentRequest(req);
139
- }
140
- if (pathname === HEAP_ENDPOINT) {
141
- if (!isObservabilityExposed(options.isDev, options.heapEndpoint)) return null;
142
- const base = collectHeapSnapshot();
143
- const perf = collectPerfSnapshot();
144
- const body = { ...base, perf };
145
- return new Response(JSON.stringify(body, null, 2), {
146
- status: 200,
147
- headers: {
148
- "Content-Type": "application/json; charset=utf-8",
149
- "Cache-Control": "no-store",
150
- },
151
- });
152
- }
153
- if (pathname === METRICS_ENDPOINT) {
154
- if (!isObservabilityExposed(options.isDev, options.metricsEndpoint)) return null;
155
- return buildMetricsResponse();
156
- }
157
- return null;
158
- }
159
-
160
- function handleEventsStreamRequest(req: Request): Response {
161
- const url = new URL(req.url);
162
- const filterType = url.searchParams.get("type") || undefined;
163
- const filterSeverity = url.searchParams.get("severity") || undefined;
164
- const filterSource = url.searchParams.get("source") || undefined;
165
- const filterTrace = url.searchParams.get("trace") || undefined;
166
-
167
- const matches = (event: ObservabilityEvent): boolean => {
168
- if (filterType && event.type !== filterType) return false;
169
- if (filterSeverity && event.severity !== filterSeverity) return false;
170
- if (filterSource && event.source !== filterSource) return false;
171
- if (filterTrace && event.correlationId !== filterTrace) return false;
172
- return true;
173
- };
174
-
175
- let unsubscribe: (() => void) | null = null;
176
- let heartbeat: ReturnType<typeof setInterval> | null = null;
177
-
178
- const stream = new ReadableStream<Uint8Array>({
179
- start(controller) {
180
- const encoder = new TextEncoder();
181
- const send = (data: string, eventName?: string) => {
182
- try {
183
- const prefix = eventName ? `event: ${eventName}\n` : "";
184
- controller.enqueue(encoder.encode(`${prefix}data: ${data}\n\n`));
185
- } catch {
186
- // Stream closed.
187
- }
188
- };
189
-
190
- for (const event of eventBus.getRecent()) {
191
- if (matches(event)) send(JSON.stringify(event));
192
- }
193
-
194
- unsubscribe = eventBus.on("*", (event) => {
195
- if (matches(event)) send(JSON.stringify(event));
196
- });
197
-
198
- heartbeat = setInterval(() => {
199
- try {
200
- controller.enqueue(encoder.encode(": heartbeat\n\n"));
201
- } catch {
202
- // Ignore closed streams.
203
- }
204
- }, 15000);
205
-
206
- req.signal.addEventListener("abort", () => {
207
- if (unsubscribe) {
208
- unsubscribe();
209
- unsubscribe = null;
210
- }
211
- if (heartbeat) {
212
- clearInterval(heartbeat);
213
- heartbeat = null;
214
- }
215
- try {
216
- controller.close();
217
- } catch {
218
- // No-op when already closed.
219
- }
220
- });
221
- },
222
- cancel() {
223
- if (unsubscribe) {
224
- unsubscribe();
225
- unsubscribe = null;
226
- }
227
- if (heartbeat) {
228
- clearInterval(heartbeat);
229
- heartbeat = null;
230
- }
231
- },
232
- });
233
-
234
- return new Response(stream, {
235
- status: 200,
236
- headers: {
237
- "Content-Type": "text/event-stream",
238
- "Cache-Control": "no-cache, no-store, must-revalidate",
239
- "Connection": "keep-alive",
240
- "X-Accel-Buffering": "no",
241
- },
242
- });
243
- }
244
-
245
- function handleEventsRecentRequest(req: Request): Response {
246
- const url = new URL(req.url);
247
- const count = url.searchParams.get("count");
248
- const type = url.searchParams.get("type") || undefined;
249
- const severity = url.searchParams.get("severity") || undefined;
250
- const windowParam = url.searchParams.get("windowMs");
251
- const windowMs = windowParam ? Number(windowParam) : undefined;
252
-
253
- const events = eventBus.getRecent(count ? Number(count) : undefined, {
254
- type: type as EventType | undefined,
255
- severity: severity as ObservabilitySeverity | undefined,
256
- });
257
- const stats = eventBus.getStats(windowMs);
258
- return Response.json({ events, stats });
259
- }
260
-
261
- function recordRuntimeObservation(observation: RuntimeRequestObservation): void {
262
- const cacheTag = observation.cacheStatus ? ` ${observation.cacheStatus}` : "";
263
- console.log(
264
- `[${new Date().toLocaleTimeString()}] ${observation.req.method} ${observation.path} ${observation.status} ${observation.duration}ms${cacheTag}`
265
- );
266
- observation.recordRequest?.({
267
- id: observation.correlationId,
268
- method: observation.req.method,
269
- path: observation.path,
270
- status: observation.status,
271
- duration: observation.duration,
272
- timestamp: Date.now(),
273
- cacheStatus: observation.cacheStatus,
274
- });
275
- eventBus.emit({
276
- type: "http",
277
- severity: observation.status >= 500 ? "error" : observation.status >= 400 ? "warn" : "info",
278
- source: "server",
279
- correlationId: observation.correlationId,
280
- message: `${observation.req.method} ${observation.path} ${observation.status}${cacheTag}`,
281
- duration: observation.duration,
282
- data: {
283
- method: observation.req.method,
284
- path: observation.path,
285
- status: observation.status,
286
- cache: observation.cacheStatus,
287
- error: observation.error || undefined,
288
- },
289
- });
290
- }
1
+ import {
2
+ eventBus,
3
+ type EventType,
4
+ type ObservabilityEvent,
5
+ type ObservabilitySeverity,
6
+ } from "../observability/event-bus";
7
+ import {
8
+ HEAP_ENDPOINT,
9
+ METRICS_ENDPOINT,
10
+ buildMetricsResponse,
11
+ collectHeapSnapshot,
12
+ isObservabilityExposed,
13
+ recordHttpRequest,
14
+ } from "../observability/metrics";
15
+ import {
16
+ createTracerFromConfig,
17
+ runWithSpan,
18
+ setTracer,
19
+ type Tracer,
20
+ type TracerConfig,
21
+ } from "../observability/tracing";
22
+ import { collectPerfSnapshot } from "../perf/user-marks";
23
+
24
+ export const INTERNAL_EVENTS_ENDPOINT = "/__mandu/events";
25
+
26
+ export interface RuntimeRequestRecord {
27
+ id: string;
28
+ method: string;
29
+ path: string;
30
+ status: number;
31
+ duration: number;
32
+ timestamp: number;
33
+ cacheStatus?: string;
34
+ }
35
+
36
+ export interface RuntimeRequestObservation {
37
+ req: Request;
38
+ path: string;
39
+ status: number;
40
+ duration: number;
41
+ correlationId: string;
42
+ cacheStatus?: string;
43
+ error?: boolean;
44
+ recordRequest?: (entry: RuntimeRequestRecord) => void;
45
+ }
46
+
47
+ export interface RuntimeObservabilityLifecycle {
48
+ readonly tracer: Tracer | undefined;
49
+ handleEndpoint(req: Request, pathname: string): Response | null;
50
+ runRequest<T extends Response>(
51
+ req: Request,
52
+ requestStart: number,
53
+ correlationId: string,
54
+ handler: () => Promise<T>
55
+ ): Promise<T>;
56
+ recordHttpResponse(req: Request, response: Response | undefined): void;
57
+ recordDevRequest(observation: RuntimeRequestObservation): void;
58
+ }
59
+
60
+ export interface CreateRuntimeObservabilityLifecycleOptions {
61
+ isDev: boolean;
62
+ heapEndpoint?: boolean;
63
+ metricsEndpoint?: boolean;
64
+ tracing?: TracerConfig;
65
+ }
66
+
67
+ export function createRuntimeObservabilityLifecycle(
68
+ options: CreateRuntimeObservabilityLifecycleOptions
69
+ ): RuntimeObservabilityLifecycle {
70
+ const tracerInstance = createTracerFromConfig(options.tracing);
71
+ setTracer(tracerInstance);
72
+ const tracer = tracerInstance.enabled ? tracerInstance : undefined;
73
+
74
+ return {
75
+ tracer,
76
+ handleEndpoint(req, pathname) {
77
+ return handleRuntimeObservabilityEndpoint(req, pathname, options);
78
+ },
79
+ async runRequest(req, requestStart, correlationId, handler) {
80
+ if (!tracer || !tracer.enabled) {
81
+ return await handler();
82
+ }
83
+
84
+ const url = new URL(req.url);
85
+ const rootSpan = tracer.startSpanFromRequest("http.request", req, {
86
+ kind: "server",
87
+ attributes: {
88
+ "http.method": req.method,
89
+ "http.url": req.url,
90
+ "http.target": url.pathname,
91
+ "http.scheme": url.protocol.replace(":", ""),
92
+ "http.host": url.host,
93
+ "mandu.correlation_id": correlationId,
94
+ "mandu.request_start_ms": requestStart,
95
+ },
96
+ });
97
+
98
+ try {
99
+ const response = await runWithSpan(rootSpan, handler);
100
+ rootSpan.setAttribute("http.status_code", response.status);
101
+ if (response.status >= 500) {
102
+ rootSpan.setStatus("error", `HTTP ${response.status}`);
103
+ } else if (rootSpan.status === "unset") {
104
+ rootSpan.setStatus("ok");
105
+ }
106
+ return response;
107
+ } catch (err) {
108
+ const message = err instanceof Error ? err.message : String(err);
109
+ rootSpan.setStatus("error", message);
110
+ throw err;
111
+ } finally {
112
+ rootSpan.end();
113
+ }
114
+ },
115
+ recordHttpResponse(req, response) {
116
+ if (!response) return;
117
+ try {
118
+ recordHttpRequest(req.method, response.status);
119
+ } catch {
120
+ // Observability must never break the request path.
121
+ }
122
+ },
123
+ recordDevRequest(observation) {
124
+ recordRuntimeObservation(observation);
125
+ },
126
+ };
127
+ }
128
+
129
+ function handleRuntimeObservabilityEndpoint(
130
+ req: Request,
131
+ pathname: string,
132
+ options: CreateRuntimeObservabilityLifecycleOptions
133
+ ): Response | null {
134
+ if (pathname === INTERNAL_EVENTS_ENDPOINT) {
135
+ return handleEventsStreamRequest(req);
136
+ }
137
+ if (pathname === `${INTERNAL_EVENTS_ENDPOINT}/recent`) {
138
+ return handleEventsRecentRequest(req);
139
+ }
140
+ if (pathname === HEAP_ENDPOINT) {
141
+ if (!isObservabilityExposed(options.isDev, options.heapEndpoint)) return null;
142
+ const base = collectHeapSnapshot();
143
+ const perf = collectPerfSnapshot();
144
+ const body = { ...base, perf };
145
+ return new Response(JSON.stringify(body, null, 2), {
146
+ status: 200,
147
+ headers: {
148
+ "Content-Type": "application/json; charset=utf-8",
149
+ "Cache-Control": "no-store",
150
+ },
151
+ });
152
+ }
153
+ if (pathname === METRICS_ENDPOINT) {
154
+ if (!isObservabilityExposed(options.isDev, options.metricsEndpoint)) return null;
155
+ return buildMetricsResponse();
156
+ }
157
+ return null;
158
+ }
159
+
160
+ function handleEventsStreamRequest(req: Request): Response {
161
+ const url = new URL(req.url);
162
+ const filterType = url.searchParams.get("type") || undefined;
163
+ const filterSeverity = url.searchParams.get("severity") || undefined;
164
+ const filterSource = url.searchParams.get("source") || undefined;
165
+ const filterTrace = url.searchParams.get("trace") || undefined;
166
+
167
+ const matches = (event: ObservabilityEvent): boolean => {
168
+ if (filterType && event.type !== filterType) return false;
169
+ if (filterSeverity && event.severity !== filterSeverity) return false;
170
+ if (filterSource && event.source !== filterSource) return false;
171
+ if (filterTrace && event.correlationId !== filterTrace) return false;
172
+ return true;
173
+ };
174
+
175
+ let unsubscribe: (() => void) | null = null;
176
+ let heartbeat: ReturnType<typeof setInterval> | null = null;
177
+
178
+ const stream = new ReadableStream<Uint8Array>({
179
+ start(controller) {
180
+ const encoder = new TextEncoder();
181
+ const send = (data: string, eventName?: string) => {
182
+ try {
183
+ const prefix = eventName ? `event: ${eventName}\n` : "";
184
+ controller.enqueue(encoder.encode(`${prefix}data: ${data}\n\n`));
185
+ } catch {
186
+ // Stream closed.
187
+ }
188
+ };
189
+
190
+ for (const event of eventBus.getRecent()) {
191
+ if (matches(event)) send(JSON.stringify(event));
192
+ }
193
+
194
+ unsubscribe = eventBus.on("*", (event) => {
195
+ if (matches(event)) send(JSON.stringify(event));
196
+ });
197
+
198
+ heartbeat = setInterval(() => {
199
+ try {
200
+ controller.enqueue(encoder.encode(": heartbeat\n\n"));
201
+ } catch {
202
+ // Ignore closed streams.
203
+ }
204
+ }, 15000);
205
+
206
+ req.signal.addEventListener("abort", () => {
207
+ if (unsubscribe) {
208
+ unsubscribe();
209
+ unsubscribe = null;
210
+ }
211
+ if (heartbeat) {
212
+ clearInterval(heartbeat);
213
+ heartbeat = null;
214
+ }
215
+ try {
216
+ controller.close();
217
+ } catch {
218
+ // No-op when already closed.
219
+ }
220
+ });
221
+ },
222
+ cancel() {
223
+ if (unsubscribe) {
224
+ unsubscribe();
225
+ unsubscribe = null;
226
+ }
227
+ if (heartbeat) {
228
+ clearInterval(heartbeat);
229
+ heartbeat = null;
230
+ }
231
+ },
232
+ });
233
+
234
+ return new Response(stream, {
235
+ status: 200,
236
+ headers: {
237
+ "Content-Type": "text/event-stream",
238
+ "Cache-Control": "no-cache, no-store, must-revalidate",
239
+ "Connection": "keep-alive",
240
+ "X-Accel-Buffering": "no",
241
+ },
242
+ });
243
+ }
244
+
245
+ function handleEventsRecentRequest(req: Request): Response {
246
+ const url = new URL(req.url);
247
+ const count = url.searchParams.get("count");
248
+ const type = url.searchParams.get("type") || undefined;
249
+ const severity = url.searchParams.get("severity") || undefined;
250
+ const windowParam = url.searchParams.get("windowMs");
251
+ const windowMs = windowParam ? Number(windowParam) : undefined;
252
+
253
+ const events = eventBus.getRecent(count ? Number(count) : undefined, {
254
+ type: type as EventType | undefined,
255
+ severity: severity as ObservabilitySeverity | undefined,
256
+ });
257
+ const stats = eventBus.getStats(windowMs);
258
+ return Response.json({ events, stats });
259
+ }
260
+
261
+ function recordRuntimeObservation(observation: RuntimeRequestObservation): void {
262
+ const cacheTag = observation.cacheStatus ? ` ${observation.cacheStatus}` : "";
263
+ console.log(
264
+ `[${new Date().toLocaleTimeString()}] ${observation.req.method} ${observation.path} ${observation.status} ${observation.duration}ms${cacheTag}`
265
+ );
266
+ observation.recordRequest?.({
267
+ id: observation.correlationId,
268
+ method: observation.req.method,
269
+ path: observation.path,
270
+ status: observation.status,
271
+ duration: observation.duration,
272
+ timestamp: Date.now(),
273
+ cacheStatus: observation.cacheStatus,
274
+ });
275
+ eventBus.emit({
276
+ type: "http",
277
+ severity: observation.status >= 500 ? "error" : observation.status >= 400 ? "warn" : "info",
278
+ source: "server",
279
+ correlationId: observation.correlationId,
280
+ message: `${observation.req.method} ${observation.path} ${observation.status}${cacheTag}`,
281
+ duration: observation.duration,
282
+ data: {
283
+ method: observation.req.method,
284
+ path: observation.path,
285
+ status: observation.status,
286
+ cache: observation.cacheStatus,
287
+ error: observation.error || undefined,
288
+ },
289
+ });
290
+ }