@gobing-ai/ts-infra 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -22
- package/dist/api-client.d.ts +44 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +144 -22
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +3 -3
- package/dist/application/plugins/builtins.d.ts.map +1 -1
- package/dist/application/plugins/builtins.js +9 -7
- package/dist/application/types.d.ts +1 -1
- package/dist/application-node.d.ts +18 -2
- package/dist/application-node.d.ts.map +1 -1
- package/dist/application-node.js +12 -7
- package/dist/event-bus/event-bus.d.ts +19 -2
- package/dist/event-bus/event-bus.d.ts.map +1 -1
- package/dist/event-bus/event-bus.js +56 -3
- package/dist/event-bus/index.d.ts +1 -1
- package/dist/event-bus/index.d.ts.map +1 -1
- package/dist/event-bus/types.d.ts +18 -0
- package/dist/event-bus/types.d.ts.map +1 -1
- package/dist/events.d.ts +5 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +5 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/job-queue/db-job-queue.d.ts +5 -1
- package/dist/job-queue/db-job-queue.d.ts.map +1 -1
- package/dist/job-queue/db-job-queue.js +55 -3
- package/dist/job-queue/types.d.ts +8 -0
- package/dist/job-queue/types.d.ts.map +1 -1
- package/dist/scheduler/action.js +1 -1
- package/dist/scheduler/node.d.ts.map +1 -1
- package/dist/scheduler/node.js +21 -11
- package/dist/scheduler/wrap-handler.js +1 -1
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +21 -1
- package/dist/telemetry/sdk.d.ts +7 -2
- package/dist/telemetry/sdk.d.ts.map +1 -1
- package/dist/telemetry/tracing.d.ts.map +1 -1
- package/dist/telemetry/tracing.js +18 -2
- package/package.json +5 -5
- package/src/api-client.ts +212 -24
- package/src/application/index.ts +4 -3
- package/src/application/plugins/builtins.ts +9 -7
- package/src/application/types.ts +1 -1
- package/src/application-node.ts +33 -10
- package/src/event-bus/event-bus.ts +63 -4
- package/src/event-bus/index.ts +1 -0
- package/src/event-bus/types.ts +19 -0
- package/src/events.ts +5 -2
- package/src/index.ts +9 -1
- package/src/job-queue/db-job-queue.ts +59 -4
- package/src/job-queue/types.ts +9 -0
- package/src/scheduler/action.ts +1 -1
- package/src/scheduler/node.ts +20 -12
- package/src/scheduler/wrap-handler.ts +1 -1
- package/src/telemetry/metrics.ts +20 -1
- package/src/telemetry/sdk.ts +7 -2
- 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
|
|
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,
|
|
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<string,string> 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
|
|
296
|
+
import { initScheduler } from '@gobing-ai/ts-infra';
|
|
250
297
|
import { NodeSchedulerAdapter } from '@gobing-ai/ts-infra/scheduler-node';
|
|
251
298
|
|
|
252
|
-
//
|
|
253
|
-
const
|
|
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
|
package/dist/api-client.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/api-client.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
...
|
|
49
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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':
|
|
143
|
+
'error.type': 'Timeout',
|
|
87
144
|
});
|
|
88
|
-
|
|
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,
|
|
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
|
-
|
|
110
|
-
|
|
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,
|
|
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 `
|
|
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?:
|
|
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,
|
|
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"}
|