@gobing-ai/ts-infra 0.3.9 → 0.3.11

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 (59) hide show
  1. package/README.md +96 -22
  2. package/dist/api-client.d.ts +44 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +144 -22
  5. package/dist/application/index.d.ts.map +1 -1
  6. package/dist/application/index.js +3 -3
  7. package/dist/application/plugins/builtins.d.ts.map +1 -1
  8. package/dist/application/plugins/builtins.js +9 -7
  9. package/dist/application/types.d.ts +1 -1
  10. package/dist/application-node.d.ts +18 -2
  11. package/dist/application-node.d.ts.map +1 -1
  12. package/dist/application-node.js +12 -7
  13. package/dist/event-bus/event-bus.d.ts +22 -2
  14. package/dist/event-bus/event-bus.d.ts.map +1 -1
  15. package/dist/event-bus/event-bus.js +63 -3
  16. package/dist/event-bus/index.d.ts +1 -1
  17. package/dist/event-bus/index.d.ts.map +1 -1
  18. package/dist/event-bus/types.d.ts +18 -0
  19. package/dist/event-bus/types.d.ts.map +1 -1
  20. package/dist/events.d.ts +5 -2
  21. package/dist/events.d.ts.map +1 -1
  22. package/dist/events.js +5 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/job-queue/db-job-queue.d.ts +5 -1
  27. package/dist/job-queue/db-job-queue.d.ts.map +1 -1
  28. package/dist/job-queue/db-job-queue.js +55 -3
  29. package/dist/job-queue/types.d.ts +8 -0
  30. package/dist/job-queue/types.d.ts.map +1 -1
  31. package/dist/scheduler/action.js +1 -1
  32. package/dist/scheduler/node.d.ts.map +1 -1
  33. package/dist/scheduler/node.js +21 -11
  34. package/dist/scheduler/wrap-handler.js +1 -1
  35. package/dist/telemetry/metrics.d.ts.map +1 -1
  36. package/dist/telemetry/metrics.js +21 -1
  37. package/dist/telemetry/sdk.d.ts +7 -2
  38. package/dist/telemetry/sdk.d.ts.map +1 -1
  39. package/dist/telemetry/tracing.d.ts.map +1 -1
  40. package/dist/telemetry/tracing.js +18 -2
  41. package/package.json +5 -5
  42. package/src/api-client.ts +212 -24
  43. package/src/application/index.ts +4 -3
  44. package/src/application/plugins/builtins.ts +9 -7
  45. package/src/application/types.ts +1 -1
  46. package/src/application-node.ts +33 -10
  47. package/src/event-bus/event-bus.ts +72 -5
  48. package/src/event-bus/index.ts +1 -0
  49. package/src/event-bus/types.ts +19 -0
  50. package/src/events.ts +5 -2
  51. package/src/index.ts +9 -1
  52. package/src/job-queue/db-job-queue.ts +59 -4
  53. package/src/job-queue/types.ts +9 -0
  54. package/src/scheduler/action.ts +1 -1
  55. package/src/scheduler/node.ts +20 -12
  56. package/src/scheduler/wrap-handler.ts +1 -1
  57. package/src/telemetry/metrics.ts +20 -1
  58. package/src/telemetry/sdk.ts +7 -2
  59. package/src/telemetry/tracing.ts +20 -2
package/README.md CHANGED
@@ -4,15 +4,15 @@ Infrastructure backbone — typed event bus, queue/scheduler contracts, adapter
4
4
 
5
5
  ## Overview
6
6
 
7
- `ts-infra` provides six subsystems that form the application backbone:
7
+ `ts-infra` provides the subsystems that form the application backbone:
8
8
 
9
9
  | Subsystem | Module | Purpose |
10
10
  |-----------|--------|---------|
11
- | **Event Bus** | `event-bus/` | Typed pub/sub with sync + async dispatch, lifecycle self-observability |
12
- | **Job Queue** | core + `/job-queue-db` | Queue interfaces in core; DB-backed enqueue/consume flow (`DBJobQueue`, `DBQueueConsumer`) via opt-in subpath |
11
+ | **Event Bus** | `event-bus/` | Typed pub/sub with sync + async dispatch, named async handlers, queue consumer bridge, lifecycle self-observability |
12
+ | **Job Queue** | core + `/job-queue-db` | Queue interfaces in core; DB-backed enqueue/consume flow (`DBJobQueue`, `DBQueueConsumer`) via opt-in subpath; optional typed event emission |
13
13
  | **Scheduler** | core + scheduler subpaths | Scheduler contracts, registry, built-in actions, noop adapter in core; Node and Cloudflare adapters via opt-in subpaths |
14
- | **Telemetry** | `telemetry/` | OTel instrumentation — tracing (`traceAsync`), metrics (12 instruments), SQL sanitizer; opt-in OTLP export via the `/otel-node` subpath |
15
- | **API Client** | `api-client.ts` | Typed HTTP client with OTel tracing, timeout, and error handling |
14
+ | **Telemetry** | `telemetry/` | OTel instrumentation — tracing (`traceAsync`) with master switch, metrics (12 instruments), SQL sanitizer; opt-in OTLP export via the `/otel-node` subpath |
15
+ | **API Client** | `api-client.ts` | Typed HTTP client with OTel tracing, timeout, per-request abort, optional event emission |
16
16
  | **Logger** | `logger.ts` | LogTape-backed structured logger with levels, child loggers, injectable sinks, and mute toggle |
17
17
  | **Application Bootstrap** | `application/` | Plugin-driven lifecycle: deterministic startup → shutdown, DI orchestration, services as plugins |
18
18
  | **Plugin Host** | `application/plugins/` | Insertion-ordered plugin registry, fail-fast/fail-soft lifecycle fan-out, reason-carrying teardown |
@@ -30,6 +30,7 @@ classDiagram
30
30
  +removeAllListeners(event?) void
31
31
  +listenerCount(event) number
32
32
  +eventNames() string[]
33
+ +createJobHandler() JobHandler<AsyncEventJobPayload>
33
34
  }
34
35
  }
35
36
 
@@ -120,12 +121,20 @@ classDiagram
120
121
  +get~T~(path, opts?) Promise~T~
121
122
  +post~T~(path, body?, opts?) Promise~T~
122
123
  +put~T~(path, body?, opts?) Promise~T~
124
+ +patch~T~(path, body?, opts?) Promise~T~
123
125
  +delete~T~(path, opts?) Promise~T~
126
+ +rawRequest(method, path, body?, opts?) Promise~RawHttpResponse~
124
127
  }
125
128
  class APIError {
126
129
  +number status
127
130
  +string body
128
131
  }
132
+ class RawHttpResponse {
133
+ +number status
134
+ +Record&lt;string,string&gt; headers
135
+ +string body
136
+ +boolean truncated?
137
+ }
129
138
  }
130
139
 
131
140
  namespace logger {
@@ -151,11 +160,15 @@ classDiagram
151
160
  SchedulerFactory --> SchedulerAdapter : "uses injected adapter"
152
161
  SchedulerFactory --> NoopSchedulerAdapter : "default when none injected"
153
162
  EventBus --> JobQueue : "async dispatch"
163
+ EventBus --> QueueConsumer : "createJobHandler bridge"
154
164
  DBJobQueue --> JobQueue : "implements"
155
165
  DBQueueConsumer --> QueueConsumer : "implements"
166
+ DBJobQueue --> EventBus : "optional QueueEvents"
167
+ DBQueueConsumer --> EventBus : "optional QueueEvents"
156
168
  EventBus --> Logger : "self-observability"
157
169
  APIClient --> Tracing : "traceAsync"
158
170
  APIClient --> Metrics : "counters + histograms"
171
+ APIClient --> EventBus : "optional ApiClientEvents"
159
172
  ```
160
173
 
161
174
  ## How It Works
@@ -191,6 +204,21 @@ await bus.emit('user.signed_up', 'alice@test.com', 'pro');
191
204
  bus.once('user.signed_up', () => console.log('one-time'));
192
205
  ```
193
206
 
207
+ **Named async handlers** enable queue-backed dispatch. Use `name` to assign a stable id for jobs consumed across process restarts, and `createJobHandler()` to register the bridge on a queue consumer:
208
+
209
+ ```ts
210
+ // Producer side: register async handlers with stable names
211
+ bus.on('order.placed', (orderId) => {
212
+ console.log(`Order: ${orderId}`);
213
+ }, { async: true, name: 'order-handler' });
214
+
215
+ // Consumer side: register createJobHandler on your DBQueueConsumer
216
+ const jobHandler = bus.createJobHandler();
217
+ queueConsumer.register('order.placed', jobHandler);
218
+ queueConsumer.register('order.updated', jobHandler);
219
+ // → enqueued { event, args, handlerId } jobs dispatch to the matching named handler
220
+ ```
221
+
194
222
  **Lifecycle events** — inject a second `EventBus` to observe bus internals:
195
223
 
196
224
  ```ts
@@ -241,32 +269,47 @@ For scheduled drains and tests, call `processOnce()` instead of starting the pol
241
269
  const processed = await consumer.processOnce();
242
270
  ```
243
271
 
244
- The consumer claims ready jobs, resets stuck processing jobs after the visibility timeout, retries failed jobs with exponential backoff, and marks expired jobs failed through `QueueJobDao`.
272
+ The consumer claims ready jobs, resets stuck processing jobs after the visibility timeout, retries failed jobs with exponential backoff, and marks expired jobs failed through `QueueJobDao`. Corrupt payloads are failed individually without rejecting the batch. Poll-cycle errors are logged and retried on the next cycle — a single DAO hiccup will not crash the process.
273
+
274
+ **Queue lifecycle events** are opt-in through the injected `EventBus`:
275
+
276
+ ```ts
277
+ import { EventBus } from '@gobing-ai/ts-infra';
278
+ import type { QueueEvents } from '@gobing-ai/ts-infra';
279
+ import { DBJobQueue, DBQueueConsumer } from '@gobing-ai/ts-infra/job-queue-db';
280
+
281
+ const events = new EventBus<QueueEvents>();
282
+ events.on('queue.job.enqueued', ({ jobId, type }) => { /* ... */ });
283
+ events.on('queue.job.completed', ({ jobId, type }) => { /* ... */ });
284
+ events.on('queue.job.failed', ({ jobId, type, error, attempt }) => { /* ... */ });
285
+ events.on('queue.job.retrying', ({ jobId, type, attempt, nextRetryAt }) => { /* ... */ });
286
+ events.on('queue.consumer.started', () => { /* ... */ });
287
+ events.on('queue.consumer.stopped', () => { /* ... */ });
288
+
289
+ const queue = new DBJobQueue<Payload>(dao, events); // emits enqueued
290
+ const consumer = new DBQueueConsumer<Payload>(dao, { events }); // emits the rest
291
+ ```
245
292
 
246
293
  ### Scheduler — cron-like actions
247
294
 
248
295
  ```ts
249
- import { initScheduler, setSchedulerAdapter } from '@gobing-ai/ts-infra';
296
+ import { initScheduler } from '@gobing-ai/ts-infra';
250
297
  import { NodeSchedulerAdapter } from '@gobing-ai/ts-infra/scheduler-node';
251
298
 
252
- // Node.js (interval-based)
253
- const scheduler = new NodeSchedulerAdapter();
254
- scheduler.register('60000', async () => {
255
- console.log('Runs every 60 seconds');
256
- });
257
- scheduler.register('*/5 * * * *', async () => {
258
- console.log('Runs every 5 minutes');
259
- });
260
- await scheduler.start();
261
-
262
- // Or use the core factory after injecting the runtime adapter
263
- setSchedulerAdapter(new NodeSchedulerAdapter());
264
- const sched = initScheduler([
299
+ // Core factory — pass the adapter and entries in one call
300
+ const sched = initScheduler(new NodeSchedulerAdapter(), [
265
301
  ['300000', async () => cleanupExpiredSessions()],
266
302
  ['3600000', async () => generateReports()],
267
303
  ]);
268
304
  await sched.start();
269
305
 
306
+ // Or register directly on the adapter
307
+ const sched2 = new NodeSchedulerAdapter();
308
+ sched2.register('*/5 * * * *', async () => {
309
+ console.log('Runs every 5 minutes');
310
+ });
311
+ await sched2.start();
312
+
270
313
  // Cloudflare Workers
271
314
  import { CloudflareSchedulerAdapter } from '@gobing-ai/ts-infra/scheduler-cloudflare';
272
315
  const cfScheduler = new CloudflareSchedulerAdapter();
@@ -301,11 +344,39 @@ try {
301
344
  }
302
345
  }
303
346
 
347
+ // PATCH method
348
+ await api.patch('/users/1', { name: 'updated' });
349
+
350
+ // Raw request — returns status/headers/body for ALL status codes (no throw on 4xx/5xx)
351
+ const { status, headers, body, truncated } = await api.rawRequest('GET', '/debug', undefined, {
352
+ redirect: 'follow',
353
+ maxResponseBytes: 10_000, // cap response body; truncated === true when exceeded
354
+ });
355
+
356
+ // Per-request abort signal (not confused with timeouts)
357
+ const controller = new AbortController();
358
+ setTimeout(() => controller.abort(), 2_000);
359
+ await api.get('/slow', { signal: controller.signal }); // throws DOMException AbortError
360
+
304
361
  // Custom operation name for tracing
305
362
  const items = await api.get<Item[]>('/items', { operationName: 'inventory.list' });
306
363
  ```
307
364
 
308
- The client auto-instruments every request: creates a `CLIENT` span, records method/URL/status attributes, emits request count + duration metrics, and records errors.
365
+ The client auto-instruments every request: creates a `CLIENT` span, records method/URL/status attributes, emits request count + duration metrics, and records errors. Timeouts throw `APIError(0, "Request timed out after …ms")` — never a raw `DOMException`. Caller-initiated aborts rethrow the original `AbortError`.
366
+
367
+ **API client events** are opt-in through the constructor:
368
+
369
+ ```ts
370
+ import { EventBus } from '@gobing-ai/ts-infra';
371
+ import type { ApiClientEvents } from '@gobing-ai/ts-infra';
372
+
373
+ const events = new EventBus<ApiClientEvents>();
374
+ events.on('api.request.error', ({ url, method, status, error }) => {
375
+ console.error(`HTTP error — ${method} ${url}: ${error}`);
376
+ });
377
+
378
+ const api = new APIClient({ baseUrl: '...', events });
379
+ ```
309
380
 
310
381
  ### Logger — structured JSON
311
382
 
@@ -347,7 +418,8 @@ import {
347
418
  sanitizeSql,
348
419
  } from '@gobing-ai/ts-infra';
349
420
 
350
- // Initialize at startup
421
+ // Initialize at startup — the master switch is real:
422
+ // enabled: false short-circuits ALL infra tracing + metrics to shared noop instruments
351
423
  initTelemetry({
352
424
  enabled: true,
353
425
  serviceName: 'my-api',
@@ -517,6 +589,8 @@ bootstrap:
517
589
  enabled: true
518
590
  serviceName: billing-api
519
591
  endpoint: http://otel-collector:4318
592
+ headers:
593
+ authorization: Bearer ${OTEL_TOKEN}
520
594
  database:
521
595
  enabled: true
522
596
  driver: bun-sqlite
@@ -1,9 +1,17 @@
1
+ import type { EventBus } from './event-bus/event-bus';
2
+ import type { ApiClientEvents } from './events';
1
3
  /** Configuration for {@link APIClient}: base URL, default headers, timeout, and optional custom fetch. */
2
4
  export interface APIClientConfig {
3
5
  baseUrl: string;
4
6
  defaultHeaders?: Record<string, string>;
5
7
  timeout?: number;
6
8
  fetch?: typeof globalThis.fetch;
9
+ /**
10
+ * Optional bus for `api.request.error` events. Emitted on network errors,
11
+ * timeouts, and (for the JSON methods) non-2xx responses. `rawRequest`
12
+ * returns non-2xx statuses normally, so it emits only on network/timeout.
13
+ */
14
+ events?: EventBus<ApiClientEvents>;
7
15
  }
8
16
  /** Per-request overrides: headers, timeout, operation name, and abort signal. */
9
17
  export interface RequestOptions {
@@ -12,7 +20,24 @@ export interface RequestOptions {
12
20
  operationName?: string;
13
21
  signal?: AbortSignal;
14
22
  }
15
- /** HTTP error with status code and response body text. */
23
+ /** Options for {@link APIClient.rawRequest}: extends RequestOptions with redirect policy and response-size cap. */
24
+ export interface RawRequestOptions extends RequestOptions {
25
+ /** Redirect policy (default `'manual'`). */
26
+ redirect?: 'follow' | 'error' | 'manual';
27
+ maxResponseBytes?: number;
28
+ }
29
+ /** Raw HTTP response returned by {@link APIClient.rawRequest} for all status codes. */
30
+ export interface RawHttpResponse {
31
+ status: number;
32
+ headers: Record<string, string>;
33
+ body: string;
34
+ /** True when `maxResponseBytes` capped the body. Omitted when no cap was applied. */
35
+ truncated?: boolean;
36
+ }
37
+ /**
38
+ * HTTP error with status code and response body text.
39
+ * Timeouts are reported with `status === 0` (no response was received).
40
+ */
16
41
  export declare class APIError extends Error {
17
42
  readonly status: number;
18
43
  readonly body: string;
@@ -33,12 +58,30 @@ export declare class APIClient {
33
58
  private readonly defaultHeaders;
34
59
  private readonly timeout;
35
60
  private readonly fetchFn;
61
+ private readonly events;
36
62
  constructor(config: APIClientConfig);
37
63
  private buildUrl;
64
+ private emitRequestError;
65
+ /**
66
+ * Shared request lifecycle: span + attributes, timeout/abort wiring,
67
+ * total/duration/error metrics, the fetch call, and timeout-vs-abort error
68
+ * mapping. `consume` owns response handling (parse/throw policy).
69
+ */
70
+ private runRequest;
38
71
  private request;
72
+ /**
73
+ * Make a raw HTTP request returning status, headers, and body for ALL status codes.
74
+ * Never throws on HTTP status codes (2xx, 4xx, 5xx all return a {@link RawHttpResponse}).
75
+ * Throws only on network/timeout errors.
76
+ *
77
+ * Deliberately does NOT force Content-Type: application/json — callers pass raw
78
+ * string bodies and set their own content-type header.
79
+ */
80
+ rawRequest(method: string, path: string, body?: string, opts?: RawRequestOptions): Promise<RawHttpResponse>;
39
81
  get<T>(path: string, opts?: RequestOptions): Promise<T>;
40
82
  post<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
41
83
  put<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
84
+ patch<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
42
85
  delete<T>(path: string, opts?: RequestOptions): Promise<T>;
43
86
  }
44
87
  //# sourceMappingURL=api-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAkBA,0GAA0G;AAC1G,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACnC;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,0DAA0D;AAC1D,qBAAa,QAAS,SAAQ,KAAK;aAEX,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAKnC;AAID;;;;;;;;;GASG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,MAAM,EAAE,eAAe;IAOnC,OAAO,CAAC,QAAQ;YAIF,OAAO;IAmFf,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvD,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAGnE"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAUhD,0GAA0G;AAC1G,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACtC;AACD,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,mHAAmH;AACnH,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACrD,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AACD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,KAAK;aAEX,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAKnC;AAkDD;;;;;;;;;GASG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;gBAEnD,MAAM,EAAE,eAAe;IAQnC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,gBAAgB;IASxB;;;;OAIG;YACW,UAAU;YA0FV,OAAO;IAmCrB;;;;;;;OAOG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA+C3G,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvD,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIzE,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAGnE"}
@@ -5,7 +5,10 @@ import { SpanKind } from '@opentelemetry/api';
5
5
  import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL, } from '@opentelemetry/semantic-conventions';
6
6
  import { getHttpClientRequestDuration, getHttpClientRequestErrors, getHttpClientRequestTotal, } from './telemetry/metrics.js';
7
7
  import { traceAsync } from './telemetry/tracing.js';
8
- /** HTTP error with status code and response body text. */
8
+ /**
9
+ * HTTP error with status code and response body text.
10
+ * Timeouts are reported with `status === 0` (no response was received).
11
+ */
9
12
  export class APIError extends Error {
10
13
  status;
11
14
  body;
@@ -16,6 +19,47 @@ export class APIError extends Error {
16
19
  this.name = 'APIError';
17
20
  }
18
21
  }
22
+ // ── Body reading ────────────────────────────────────────────────────
23
+ /**
24
+ * Read a response body capped at `maxResponseBytes` (UTF-8 bytes). Returns the
25
+ * decoded text and whether the body was truncated. Falls back to `text()` with
26
+ * a character cap when the response exposes no readable stream.
27
+ */
28
+ async function readBodyCapped(response, maxResponseBytes) {
29
+ const reader = response.body?.getReader();
30
+ if (!reader) {
31
+ const full = await response.text();
32
+ if (full.length > maxResponseBytes) {
33
+ return { text: full.slice(0, maxResponseBytes), truncated: true };
34
+ }
35
+ return { text: full };
36
+ }
37
+ const chunks = [];
38
+ const decoder = new TextDecoder();
39
+ let total = 0;
40
+ let truncated = false;
41
+ while (true) {
42
+ const { done, value } = await reader.read();
43
+ if (done)
44
+ break;
45
+ const remaining = maxResponseBytes - total;
46
+ if (value.length > remaining) {
47
+ // The streaming decoder holds back a trailing partial multibyte
48
+ // sequence, which is then dropped — the cap is a byte budget.
49
+ chunks.push(decoder.decode(value.slice(0, remaining), { stream: true }));
50
+ truncated = true;
51
+ break;
52
+ }
53
+ chunks.push(decoder.decode(value, { stream: true }));
54
+ total += value.length;
55
+ }
56
+ if (truncated) {
57
+ // Stop the underlying stream instead of letting it keep downloading.
58
+ await reader.cancel().catch(() => { });
59
+ }
60
+ reader.releaseLock();
61
+ return { text: chunks.join(''), ...(truncated ? { truncated } : {}) };
62
+ }
19
63
  // ── Client ──────────────────────────────────────────────────────────
20
64
  /**
21
65
  * Typed HTTP client builder wrapping fetch with automatic JSON serialization,
@@ -32,22 +76,31 @@ export class APIClient {
32
76
  defaultHeaders;
33
77
  timeout;
34
78
  fetchFn;
79
+ events;
35
80
  constructor(config) {
36
81
  this.baseUrl = config.baseUrl.replace(/\/+$/, '');
37
82
  this.defaultHeaders = config.defaultHeaders ?? {};
38
83
  this.timeout = config.timeout ?? 30_000;
39
84
  this.fetchFn = config.fetch ?? globalThis.fetch;
85
+ this.events = config.events;
40
86
  }
41
87
  buildUrl(path) {
42
88
  return `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
43
89
  }
44
- async request(method, path, body, opts) {
45
- const url = this.buildUrl(path);
46
- const headers = {
47
- 'Content-Type': 'application/json',
48
- ...this.defaultHeaders,
49
- ...opts?.headers,
50
- };
90
+ emitRequestError(method, url, error, status) {
91
+ void this.events?.emit('api.request.error', {
92
+ url,
93
+ method,
94
+ ...(status !== undefined ? { status } : {}),
95
+ error,
96
+ });
97
+ }
98
+ /**
99
+ * Shared request lifecycle: span + attributes, timeout/abort wiring,
100
+ * total/duration/error metrics, the fetch call, and timeout-vs-abort error
101
+ * mapping. `consume` owns response handling (parse/throw policy).
102
+ */
103
+ async runRequest(method, url, headers, body, opts, redirect, consume) {
51
104
  const operationName = opts?.operationName ?? `HTTP ${method} ${url}`;
52
105
  return traceAsync(operationName, async (span) => {
53
106
  span.setAttribute(ATTR_HTTP_REQUEST_METHOD, method);
@@ -66,40 +119,40 @@ export class APIClient {
66
119
  const response = await this.fetchFn(url, {
67
120
  method,
68
121
  headers,
69
- body: body !== undefined ? JSON.stringify(body) : undefined,
122
+ body,
70
123
  signal: combinedSignal,
124
+ ...(redirect ? { redirect } : {}),
71
125
  });
72
126
  span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
73
127
  getHttpClientRequestTotal().add(1, {
74
128
  'http.request.method': method,
75
129
  'http.response.status_code': response.status,
76
130
  });
77
- const duration = performance.now() - start;
78
- getHttpClientRequestDuration().record(duration, {
131
+ getHttpClientRequestDuration().record(performance.now() - start, {
79
132
  'http.request.method': method,
80
133
  'http.response.status_code': response.status,
81
134
  });
82
- if (!response.ok) {
83
- const text = await response.text();
135
+ return await consume(response, span);
136
+ }
137
+ catch (error) {
138
+ // Timeout (our own AbortController fired) — but never relabel a
139
+ // caller-initiated abort via opts.signal as a timeout.
140
+ if (error instanceof DOMException && error.name === 'AbortError' && !opts?.signal?.aborted) {
84
141
  getHttpClientRequestErrors().add(1, {
85
142
  'http.request.method': method,
86
- 'error.type': `HTTP_${response.status}`,
143
+ 'error.type': 'Timeout',
87
144
  });
88
- throw new APIError(response.status, text);
145
+ const timeoutError = new APIError(0, `Request timed out after ${timeoutMs}ms: ${method} ${url}`);
146
+ this.emitRequestError(method, url, timeoutError.message);
147
+ throw timeoutError;
89
148
  }
90
- const contentType = response.headers.get('content-type') ?? '';
91
- if (contentType.includes('application/json')) {
92
- return (await response.json());
93
- }
94
- return (await response.text());
95
- }
96
- catch (error) {
97
149
  if (!(error instanceof APIError)) {
98
150
  getHttpClientRequestErrors().add(1, {
99
151
  'http.request.method': method,
100
152
  'error.type': error instanceof Error ? error.name : 'Unknown',
101
153
  });
102
154
  }
155
+ this.emitRequestError(method, url, error instanceof Error ? error.message : String(error), error instanceof APIError && error.status > 0 ? error.status : undefined);
103
156
  throw error;
104
157
  }
105
158
  finally {
@@ -108,6 +161,72 @@ export class APIClient {
108
161
  }
109
162
  }, { kind: SpanKind.CLIENT });
110
163
  }
164
+ async request(method, path, body, opts) {
165
+ const url = this.buildUrl(path);
166
+ const headers = {
167
+ 'Content-Type': 'application/json',
168
+ ...this.defaultHeaders,
169
+ ...opts?.headers,
170
+ };
171
+ return this.runRequest(method, url, headers, body !== undefined ? JSON.stringify(body) : undefined, opts, undefined, async (response) => {
172
+ if (!response.ok) {
173
+ const text = await response.text();
174
+ getHttpClientRequestErrors().add(1, {
175
+ 'http.request.method': method,
176
+ 'error.type': `HTTP_${response.status}`,
177
+ });
178
+ throw new APIError(response.status, text);
179
+ }
180
+ const contentType = response.headers.get('content-type') ?? '';
181
+ if (contentType.includes('application/json')) {
182
+ return (await response.json());
183
+ }
184
+ return (await response.text());
185
+ });
186
+ }
187
+ /**
188
+ * Make a raw HTTP request returning status, headers, and body for ALL status codes.
189
+ * Never throws on HTTP status codes (2xx, 4xx, 5xx all return a {@link RawHttpResponse}).
190
+ * Throws only on network/timeout errors.
191
+ *
192
+ * Deliberately does NOT force Content-Type: application/json — callers pass raw
193
+ * string bodies and set their own content-type header.
194
+ */
195
+ async rawRequest(method, path, body, opts) {
196
+ const url = this.buildUrl(path);
197
+ const headers = {
198
+ ...this.defaultHeaders,
199
+ ...opts?.headers,
200
+ };
201
+ const redirect = opts?.redirect ?? 'manual';
202
+ const maxResponseBytes = opts?.maxResponseBytes;
203
+ return this.runRequest(method, url, headers, body ?? undefined, opts, redirect, async (response) => {
204
+ if (!response.ok) {
205
+ getHttpClientRequestErrors().add(1, {
206
+ 'http.request.method': method,
207
+ 'error.type': `HTTP_${response.status}`,
208
+ });
209
+ }
210
+ let text;
211
+ let truncated;
212
+ if (maxResponseBytes !== undefined) {
213
+ ({ text, truncated } = await readBodyCapped(response, maxResponseBytes));
214
+ }
215
+ else {
216
+ text = await response.text();
217
+ }
218
+ const responseHeaders = {};
219
+ response.headers.forEach((value, key) => {
220
+ responseHeaders[key] = value;
221
+ });
222
+ return {
223
+ status: response.status,
224
+ headers: responseHeaders,
225
+ body: text,
226
+ ...(truncated !== undefined ? { truncated } : {}),
227
+ };
228
+ });
229
+ }
111
230
  async get(path, opts) {
112
231
  return this.request('GET', path, undefined, opts);
113
232
  }
@@ -117,6 +236,9 @@ export class APIClient {
117
236
  async put(path, body, opts) {
118
237
  return this.request('PUT', path, body, opts);
119
238
  }
239
+ async patch(path, body, opts) {
240
+ return this.request('PATCH', path, body, opts);
241
+ }
120
242
  async delete(path, opts) {
121
243
  return this.request('DELETE', path, undefined, opts);
122
244
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAiCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,cAAc,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EAC7F,OAAO,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1D,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAkIlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,YAAY,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAiCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,cAAc,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EAC7F,OAAO,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1D,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAmIlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,YAAY,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
@@ -105,9 +105,9 @@ export async function runApplication(options) {
105
105
  if (lifecycleBus && eventsDefaultObservers) {
106
106
  attachDefaultObservers(lifecycleBus);
107
107
  }
108
- const events = options.services?.events
109
- ? options.services.events
110
- : new EventBus({ lifecycleBus: lifecycleBus });
108
+ const events = options.services?.events ??
109
+ options.config?.events?.bus ??
110
+ new EventBus({ lifecycleBus: lifecycleBus });
111
111
  // ── 4. Database (injected only) ────────────────────────────────
112
112
  const db = options.services?.db;
113
113
  // ── 5. Scheduler ───────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../../src/application/plugins/builtins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,0BAA0B,CAAC,WAAW,CAAC,GAAG,MAAM,CAwBvF;AAID;;;;;;GAMG;AACH,wBAAgB,YAAY,CACxB,MAAM,EAAE;IACJ,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACnD,EACD,QAAQ,CAAC,EAAE,OAAO,GACnB,MAAM,CAiBR;AAID,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,OAAO,SAAS,QAAQ,EACnE,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAC7E,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,SAAS,EAC1G,GAAG,CAAC,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,GAC9C,MAAM,CAgBR;AAGD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAgBrF;AAID;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,CAelD"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../../src/application/plugins/builtins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,0BAA0B,CAAC,WAAW,CAAC,GAAG,MAAM,CA0BvF;AAID;;;;;;GAMG;AACH,wBAAgB,YAAY,CACxB,MAAM,EAAE;IACJ,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACnD,EACD,QAAQ,CAAC,EAAE,OAAO,GACnB,MAAM,CAiBR;AAID,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,OAAO,SAAS,QAAQ,EACnE,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAC7E,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,SAAS,EAC1G,GAAG,CAAC,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,GAC9C,MAAM,CAgBR;AAGD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAgBrF;AAID;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,CAelD"}
@@ -25,21 +25,23 @@ export function telemetryPlugin(config) {
25
25
  failFast: true,
26
26
  onLoad: async () => { },
27
27
  onStart: async () => {
28
+ // Always record the resolved config — `enabled: false` must reach the
29
+ // master switch so infra spans/instruments actually go quiet.
30
+ initTelemetry({
31
+ enabled: config.enabled,
32
+ serviceName: config.serviceName,
33
+ environment: config.environment,
34
+ dbStatementDebug: config.dbStatementDebug,
35
+ });
28
36
  if (config.enabled) {
29
- initTelemetry({
30
- enabled: config.enabled,
31
- serviceName: config.serviceName,
32
- environment: config.environment,
33
- dbStatementDebug: config.dbStatementDebug,
34
- });
35
37
  initMetrics();
36
38
  }
37
39
  },
38
40
  onStop: async () => {
39
41
  if (config.enabled) {
40
42
  shutdownMetrics();
41
- await shutdownTelemetry();
42
43
  }
44
+ await shutdownTelemetry();
43
45
  },
44
46
  };
45
47
  }
@@ -65,7 +65,7 @@ export interface SchedulerOptions {
65
65
  }
66
66
  /**
67
67
  * Fully-resolved bootstrap config (all optionals filled with defaults).
68
- * Constructed internally by `resolveBootstrapConfig`.
68
+ * Constructed internally by `runApplication`.
69
69
  */
70
70
  export interface ApplicationBootstrapConfig {
71
71
  readonly logging: Readonly<Required<Pick<LoggingOptions, 'enabled' | 'level' | 'console' | 'json'>> & {
@@ -14,17 +14,33 @@
14
14
  *
15
15
  * @module application-node
16
16
  */
17
- import type { ApplicationBootstrapOptions, ApplicationConfigLoader, ApplicationRuntime, ApplicationStopReason, EventMap, InfraEvents } from './application/types';
17
+ import type { ApplicationBootstrapOptions, ApplicationConfigLoader, ApplicationRuntime, ApplicationStopReason, EventMap, EventsOptions, InfraEvents, LoggingOptions, SchedulerOptions, TelemetryOptions } from './application/types';
18
18
  /** Error thrown when application config validation fails. Includes file path and section name. */
19
19
  export declare class ConfigValidationError extends Error {
20
20
  constructor(message: string);
21
21
  }
22
+ /**
23
+ * Telemetry options for the Node subpath: the portable flags plus OTLP export
24
+ * wiring. Presence of `endpoint` (with `enabled` not false) turns on Node OTel
25
+ * export via `@gobing-ai/ts-infra/otel-node`.
26
+ */
27
+ export interface NodeTelemetryBootstrapOptions extends TelemetryOptions {
28
+ /** OTLP/HTTP collector endpoint base, e.g. `http://localhost:4318`. */
29
+ endpoint?: string;
30
+ /** Extra headers sent on every OTLP request (e.g. an auth token). */
31
+ headers?: Record<string, string>;
32
+ }
22
33
  /** Options for the Node/Bun convenience {@link runNodeApplication}. */
23
34
  export interface NodeApplicationOptions<TAppConfig = unknown, TEvents extends EventMap = InfraEvents> {
24
35
  /** YAML config loading options. When omitted, uses defaults. */
25
36
  readonly configLoader?: ApplicationConfigLoader<TAppConfig>;
26
37
  /** Inline bootstrap config (overrides YAML-loaded config). */
27
- readonly config?: ApplicationBootstrapOptions<TAppConfig, TEvents>['config'];
38
+ readonly config?: {
39
+ logging?: LoggingOptions;
40
+ events?: EventsOptions<TEvents>;
41
+ telemetry?: NodeTelemetryBootstrapOptions;
42
+ scheduler?: SchedulerOptions;
43
+ };
28
44
  /** Pre-built services to inject. */
29
45
  readonly services?: ApplicationBootstrapOptions<TAppConfig, TEvents>['services'];
30
46
  /** User callback: application logic. */
@@ -1 +1 @@
1
- {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,WAAW,EAId,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7E,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CA8FlD"}
1
+ {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAmGlD"}