@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.
- 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 +22 -2
- package/dist/event-bus/event-bus.d.ts.map +1 -1
- package/dist/event-bus/event-bus.js +63 -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 +72 -5
- 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/dist/application-node.js
CHANGED
|
@@ -177,19 +177,24 @@ export async function runNodeApplication(options) {
|
|
|
177
177
|
// ── Node-owned plugins ──────────────────────────────────────────────
|
|
178
178
|
const plugins = [];
|
|
179
179
|
let dbAdapter = options.services?.db;
|
|
180
|
-
const rawTel = { ...telemetryOpts };
|
|
181
180
|
// Node OTel telemetry as a failFast plugin
|
|
182
|
-
|
|
181
|
+
const otlpEndpoint = telemetryOpts.endpoint;
|
|
182
|
+
if (telemetryOpts.enabled !== false && otlpEndpoint) {
|
|
183
|
+
const otlpHeaders = telemetryOpts.headers;
|
|
184
|
+
const serviceName = telemetryOpts.serviceName ?? 'ts-libs';
|
|
183
185
|
plugins.push({
|
|
184
186
|
name: 'builtin:node-telemetry',
|
|
185
187
|
version: '0.0.0',
|
|
186
188
|
failFast: true,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
// Providers must register during loadAll: the OTel metrics API has no
|
|
190
|
+
// proxy provider, so instruments created by telemetryPlugin's onStart
|
|
191
|
+
// (`initMetrics` pre-warm) bind permanently to whatever meter provider
|
|
192
|
+
// is global at that moment. loadAll runs before every onStart.
|
|
193
|
+
onLoad: async () => {
|
|
189
194
|
initNodeTelemetry({
|
|
190
|
-
serviceName
|
|
191
|
-
endpoint:
|
|
192
|
-
headers:
|
|
195
|
+
serviceName,
|
|
196
|
+
endpoint: otlpEndpoint,
|
|
197
|
+
...(otlpHeaders ? { headers: otlpHeaders } : {}),
|
|
193
198
|
});
|
|
194
199
|
},
|
|
195
200
|
onStop: async () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { JobQueue } from '../job-queue/types';
|
|
2
|
-
import
|
|
1
|
+
import type { JobHandler, JobQueue } from '../job-queue/types';
|
|
2
|
+
import { type Logger } from '../logger';
|
|
3
|
+
import type { AsyncEventJobPayload, BusLifecycleEvents, EventMap, SubscribeOptions } from './types';
|
|
3
4
|
/**
|
|
4
5
|
* Type-safe event bus supporting both synchronous (in-process) and
|
|
5
6
|
* asynchronous handlers with optional job-queue enqueue instrumentation.
|
|
@@ -8,12 +9,15 @@ export declare class EventBus<TEvents extends EventMap> {
|
|
|
8
9
|
private readonly syncHandlers;
|
|
9
10
|
private readonly asyncHandlers;
|
|
10
11
|
private readonly asyncHandlerIds;
|
|
12
|
+
private readonly asyncHandlersById;
|
|
11
13
|
private readonly jobQueue;
|
|
12
14
|
private readonly lifecycleBus;
|
|
15
|
+
private readonly logger;
|
|
13
16
|
private nextAsyncHandlerId;
|
|
14
17
|
constructor(opts?: {
|
|
15
18
|
jobQueue?: JobQueue;
|
|
16
19
|
lifecycleBus?: EventBus<BusLifecycleEvents>;
|
|
20
|
+
logger?: Logger;
|
|
17
21
|
});
|
|
18
22
|
on<K extends keyof TEvents>(event: K, handler: TEvents[K], opts?: SubscribeOptions): void;
|
|
19
23
|
once<K extends keyof TEvents>(event: K, handler: TEvents[K], opts?: SubscribeOptions): void;
|
|
@@ -25,6 +29,22 @@ export declare class EventBus<TEvents extends EventMap> {
|
|
|
25
29
|
private registerSync;
|
|
26
30
|
private registerAsync;
|
|
27
31
|
private getAsyncHandlerId;
|
|
32
|
+
/** Drop a handler's id mappings once it is subscribed to no event at all. */
|
|
33
|
+
private releaseAsyncIdIfUnsubscribed;
|
|
34
|
+
/**
|
|
35
|
+
* Bridge for queue-backed async dispatch: returns a `JobHandler` to
|
|
36
|
+
* register on a queue consumer for each async event's job type. It
|
|
37
|
+
* dispatches the enqueued `{ event, args, handlerId }` payload back to the
|
|
38
|
+
* matching async handler on THIS bus instance.
|
|
39
|
+
*
|
|
40
|
+
* Use named async subscriptions (`SubscribeOptions.name`) when jobs may be
|
|
41
|
+
* consumed after a process restart — anonymous handler ids are
|
|
42
|
+
* process-local and not stable across restarts.
|
|
43
|
+
*
|
|
44
|
+
* @throws when the payload references an unknown handler id, so the job
|
|
45
|
+
* lands in the queue's retry/fail path instead of vanishing silently.
|
|
46
|
+
*/
|
|
47
|
+
createJobHandler(): JobHandler<AsyncEventJobPayload>;
|
|
28
48
|
private publishEmitDone;
|
|
29
49
|
private publishEmitNoop;
|
|
30
50
|
private publishHandlerError;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/event-bus/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/event-bus/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAER,oBAAoB,EACpB,kBAAkB,EAElB,QAAQ,EAER,gBAAgB,EACnB,MAAM,SAAS,CAAC;AAQjB;;;GAGG;AACH,qBAAa,QAAQ,CAAC,OAAO,SAAS,QAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyD;IACtF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyD;IACvF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6C;IAC7E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6C;IAC/E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsC;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,kBAAkB,CAAK;gBACnB,IAAI,CAAC,EAAE;QACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACpB,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;KACnB;IAMD,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAQzF,IAAI,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAS3F,GAAG,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAmBjE,kBAAkB,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI;IAgBtD,IAAI,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkF7F,aAAa,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM;IAMjF,UAAU,IAAI,MAAM,EAAE;IAOtB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,iBAAiB;IAUzB,6EAA6E;IAC7E,OAAO,CAAC,4BAA4B;IAWpC;;;;;;;;;;;;OAYG;IACH,gBAAgB,IAAI,UAAU,CAAC,oBAAoB,CAAC;IAepD,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,oBAAoB;CAU/B"}
|
|
@@ -13,17 +13,20 @@ function busLogger() {
|
|
|
13
13
|
export class EventBus {
|
|
14
14
|
syncHandlers = new Map();
|
|
15
15
|
asyncHandlers = new Map();
|
|
16
|
-
asyncHandlerIds = new
|
|
16
|
+
asyncHandlerIds = new Map();
|
|
17
|
+
asyncHandlersById = new Map();
|
|
17
18
|
jobQueue;
|
|
18
19
|
lifecycleBus;
|
|
20
|
+
logger;
|
|
19
21
|
nextAsyncHandlerId = 0;
|
|
20
22
|
constructor(opts) {
|
|
21
23
|
this.jobQueue = opts?.jobQueue ?? null;
|
|
22
24
|
this.lifecycleBus = opts?.lifecycleBus ?? null;
|
|
25
|
+
this.logger = opts?.logger;
|
|
23
26
|
}
|
|
24
27
|
on(event, handler, opts) {
|
|
25
28
|
if (opts?.async) {
|
|
26
|
-
this.registerAsync(event, handler);
|
|
29
|
+
this.registerAsync(event, handler, opts.name);
|
|
27
30
|
}
|
|
28
31
|
else {
|
|
29
32
|
this.registerSync(event, handler);
|
|
@@ -50,16 +53,24 @@ export class EventBus {
|
|
|
50
53
|
if (asyncSet.size === 0) {
|
|
51
54
|
this.asyncHandlers.delete(event);
|
|
52
55
|
}
|
|
56
|
+
this.releaseAsyncIdIfUnsubscribed(handler);
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
removeAllListeners(event) {
|
|
56
60
|
if (event !== undefined) {
|
|
57
61
|
this.syncHandlers.delete(event);
|
|
62
|
+
const asyncSet = this.asyncHandlers.get(event);
|
|
58
63
|
this.asyncHandlers.delete(event);
|
|
64
|
+
if (asyncSet) {
|
|
65
|
+
for (const handler of asyncSet)
|
|
66
|
+
this.releaseAsyncIdIfUnsubscribed(handler);
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
else {
|
|
61
70
|
this.syncHandlers.clear();
|
|
62
71
|
this.asyncHandlers.clear();
|
|
72
|
+
this.asyncHandlerIds.clear();
|
|
73
|
+
this.asyncHandlersById.clear();
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
async emit(event, ...args) {
|
|
@@ -68,6 +79,11 @@ export class EventBus {
|
|
|
68
79
|
let syncCount = 0;
|
|
69
80
|
let asyncCount = 0;
|
|
70
81
|
let errors = 0;
|
|
82
|
+
this.logger?.debug('event.emit', {
|
|
83
|
+
event: eventName,
|
|
84
|
+
syncHandlers: this.syncHandlers.get(event)?.size ?? 0,
|
|
85
|
+
asyncHandlers: this.asyncHandlers.get(event)?.size ?? 0,
|
|
86
|
+
});
|
|
71
87
|
const syncSet = this.syncHandlers.get(event);
|
|
72
88
|
if (syncSet) {
|
|
73
89
|
syncCount = syncSet.size;
|
|
@@ -155,13 +171,21 @@ export class EventBus {
|
|
|
155
171
|
}
|
|
156
172
|
set.add(handler);
|
|
157
173
|
}
|
|
158
|
-
registerAsync(event, handler) {
|
|
174
|
+
registerAsync(event, handler, name) {
|
|
159
175
|
let set = this.asyncHandlers.get(event);
|
|
160
176
|
if (!set) {
|
|
161
177
|
set = new Set();
|
|
162
178
|
this.asyncHandlers.set(event, set);
|
|
163
179
|
}
|
|
164
180
|
set.add(handler);
|
|
181
|
+
if (!this.asyncHandlerIds.has(handler)) {
|
|
182
|
+
const id = name ?? `handler-${++this.nextAsyncHandlerId}`;
|
|
183
|
+
if (this.asyncHandlersById.has(id)) {
|
|
184
|
+
throw new Error(`Duplicate async handler name: "${id}"`);
|
|
185
|
+
}
|
|
186
|
+
this.asyncHandlerIds.set(handler, id);
|
|
187
|
+
this.asyncHandlersById.set(id, handler);
|
|
188
|
+
}
|
|
165
189
|
}
|
|
166
190
|
getAsyncHandlerId(handler) {
|
|
167
191
|
const existing = this.asyncHandlerIds.get(handler);
|
|
@@ -169,8 +193,44 @@ export class EventBus {
|
|
|
169
193
|
return existing;
|
|
170
194
|
const id = `handler-${++this.nextAsyncHandlerId}`;
|
|
171
195
|
this.asyncHandlerIds.set(handler, id);
|
|
196
|
+
this.asyncHandlersById.set(id, handler);
|
|
172
197
|
return id;
|
|
173
198
|
}
|
|
199
|
+
/** Drop a handler's id mappings once it is subscribed to no event at all. */
|
|
200
|
+
releaseAsyncIdIfUnsubscribed(handler) {
|
|
201
|
+
for (const set of this.asyncHandlers.values()) {
|
|
202
|
+
if (set.has(handler))
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const id = this.asyncHandlerIds.get(handler);
|
|
206
|
+
if (id !== undefined) {
|
|
207
|
+
this.asyncHandlerIds.delete(handler);
|
|
208
|
+
this.asyncHandlersById.delete(id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Bridge for queue-backed async dispatch: returns a `JobHandler` to
|
|
213
|
+
* register on a queue consumer for each async event's job type. It
|
|
214
|
+
* dispatches the enqueued `{ event, args, handlerId }` payload back to the
|
|
215
|
+
* matching async handler on THIS bus instance.
|
|
216
|
+
*
|
|
217
|
+
* Use named async subscriptions (`SubscribeOptions.name`) when jobs may be
|
|
218
|
+
* consumed after a process restart — anonymous handler ids are
|
|
219
|
+
* process-local and not stable across restarts.
|
|
220
|
+
*
|
|
221
|
+
* @throws when the payload references an unknown handler id, so the job
|
|
222
|
+
* lands in the queue's retry/fail path instead of vanishing silently.
|
|
223
|
+
*/
|
|
224
|
+
createJobHandler() {
|
|
225
|
+
return async (job) => {
|
|
226
|
+
const { event, args, handlerId } = job.payload;
|
|
227
|
+
const handler = this.asyncHandlersById.get(handlerId);
|
|
228
|
+
if (!handler) {
|
|
229
|
+
throw new Error(`No async handler registered for id "${handlerId}" (event "${event}")`);
|
|
230
|
+
}
|
|
231
|
+
await handler(...args);
|
|
232
|
+
};
|
|
233
|
+
}
|
|
174
234
|
publishEmitDone(detail) {
|
|
175
235
|
if (this.lifecycleBus) {
|
|
176
236
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { attachDefaultObservers, attachLogObserver, attachTelemetryObserver, createLifecycleBus, } from './default-observers';
|
|
2
2
|
export { EventBus } from './event-bus';
|
|
3
3
|
export { attachFileObserver, type FileObserverWriter } from './file-observer';
|
|
4
|
-
export type { AsyncEnqueuedDetail, BusLifecycleEvents, EmitDoneDetail, EventMap, HandlerErrorDetail, SubscribeOptions, } from './types';
|
|
4
|
+
export type { AsyncEnqueuedDetail, AsyncEventJobPayload, BusLifecycleEvents, EmitDoneDetail, EventMap, HandlerErrorDetail, SubscribeOptions, } from './types';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/event-bus/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EACR,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/event-bus/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EACR,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
|
|
@@ -7,6 +7,24 @@ export type EventMap = Record<string, (...args: never[]) => void>;
|
|
|
7
7
|
export interface SubscribeOptions {
|
|
8
8
|
/** When true, the handler is dispatched through the async handler path. */
|
|
9
9
|
async?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Stable identifier for an async handler. Used as the `handlerId` in jobs
|
|
12
|
+
* enqueued through an injected `JobQueue`, so a queue consumer (see
|
|
13
|
+
* `EventBus.createJobHandler`) can dispatch jobs back to this handler —
|
|
14
|
+
* including after a process restart. Anonymous async handlers get a
|
|
15
|
+
* process-local generated id that is NOT stable across restarts.
|
|
16
|
+
* Must be unique per bus instance.
|
|
17
|
+
*/
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Payload shape the bus enqueues for queue-backed async handlers. */
|
|
21
|
+
export interface AsyncEventJobPayload {
|
|
22
|
+
/** Event name the handler was subscribed to. */
|
|
23
|
+
event: string;
|
|
24
|
+
/** Arguments the event was emitted with. */
|
|
25
|
+
args: unknown[];
|
|
26
|
+
/** Async handler id (subscription `name` or a generated id). */
|
|
27
|
+
handlerId: string;
|
|
10
28
|
}
|
|
11
29
|
/** Payload emitted on `bus.emit.done` after synchronous handlers complete. */
|
|
12
30
|
export interface EmitDoneDetail {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/event-bus/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6FAA6F;AAC7F,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;AAElE,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC7B,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/event-bus/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6FAA6F;AAC7F,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;AAElE,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC7B,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sEAAsE;AACtE,MAAM,WAAW,oBAAoB;IACjC,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;CACrB;AAID,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,eAAe,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD,eAAe,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrD,mBAAmB,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC1D,4BAA4B,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACvE,CAAC"}
|
package/dist/events.d.ts
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Infrastructure-level event definitions for ts-infra observability.
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the package-local pattern in `@gobing-ai/ts-ai-runner` (`events.ts`):
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* these maps define the infra-level event *contract*. ts-infra itself currently
|
|
6
|
+
* emits only `queue.stats` (`QueueStatsAction`) and `scheduler.job.executed`
|
|
7
|
+
* (`wrapScheduledHandler`); the remaining events are contracts for the consuming
|
|
8
|
+
* app or higher-level wiring to emit on the same bus. Application-domain events
|
|
9
|
+
* (history import, HTTP server, …) belong to the consuming app, not this
|
|
7
10
|
* library. Process events belong to `@gobing-ai/ts-runtime` (the owner of
|
|
8
11
|
* `ProcessExecutor`) and are intentionally not re-exported here.
|
|
9
12
|
*
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD,yCAAyC;AACzC,MAAM,WAAW,uBAAuB;IACpC,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,0BAA0B;IACvC,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAID,mEAAmE;AACnE,MAAM,MAAM,QAAQ,GAAG;IACnB,uCAAuC;IACvC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,iCAAiC;IACjC,qBAAqB,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;IACtB,8BAA8B;IAC9B,oBAAoB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,qCAAqC;IACrC,wBAAwB,EAAE,MAAM,IAAI,CAAC;IACrC,wBAAwB;IACxB,wBAAwB,EAAE,MAAM,IAAI,CAAC;IACrC,oCAAoC;IACpC,qBAAqB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACzE,yDAAyD;IACzD,kBAAkB,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC3D,2CAA2C;IAC3C,oBAAoB,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC/D,oDAAoD;IACpD,aAAa,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG;IAC1B,yDAAyD;IACzD,wBAAwB,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC1E,CAAC;AAEF,yBAAyB;AACzB,MAAM,MAAM,eAAe,GAAG;IAC1B,wDAAwD;IACxD,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC"}
|
package/dist/events.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Infrastructure-level event definitions for ts-infra observability.
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the package-local pattern in `@gobing-ai/ts-ai-runner` (`events.ts`):
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* these maps define the infra-level event *contract*. ts-infra itself currently
|
|
6
|
+
* emits only `queue.stats` (`QueueStatsAction`) and `scheduler.job.executed`
|
|
7
|
+
* (`wrapScheduledHandler`); the remaining events are contracts for the consuming
|
|
8
|
+
* app or higher-level wiring to emit on the same bus. Application-domain events
|
|
9
|
+
* (history import, HTTP server, …) belong to the consuming app, not this
|
|
7
10
|
* library. Process events belong to `@gobing-ai/ts-runtime` (the owner of
|
|
8
11
|
* `ProcessExecutor`) and are intentionally not re-exported here.
|
|
9
12
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { APIClient, type APIClientConfig, APIError, type RequestOptions } from './api-client';
|
|
2
|
-
export { attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, createLifecycleBus, EventBus, type EventMap, type FileObserverWriter, type SubscribeOptions, } from './event-bus/index';
|
|
1
|
+
export { APIClient, type APIClientConfig, APIError, type RawHttpResponse, type RawRequestOptions, type RequestOptions, } from './api-client';
|
|
2
|
+
export { type AsyncEventJobPayload, attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, createLifecycleBus, EventBus, type EventMap, type FileObserverWriter, type SubscribeOptions, } from './event-bus/index';
|
|
3
3
|
export type { ApiClientEvents, ApiRequestErrorDetail, DbConnectionErrorDetail, DbEvents, InfraEvents, QueueEvents, QueueJobFailedDetail, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, } from './events';
|
|
4
4
|
export type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig, QueueStats, } from './job-queue/index';
|
|
5
5
|
export { getLogger, type InitLoggerOptions, initializeLogger, type Logger, type LogLevel, setLoggerMuted, } from './logger';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,SAAS,EACT,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACH,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACxB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EACR,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,0BAA0B,GAC7B,MAAM,UAAU,CAAC;AAElB,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,SAAS,EACT,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,cAAc,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACH,cAAc,EACd,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// API Client
|
|
2
|
-
export { APIClient, APIError } from './api-client.js';
|
|
2
|
+
export { APIClient, APIError, } from './api-client.js';
|
|
3
3
|
// Event Bus
|
|
4
4
|
export { attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, createLifecycleBus, EventBus, } from './event-bus/index.js';
|
|
5
5
|
// Logger
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { QueueJobDao, QueueStats } from '@gobing-ai/ts-db';
|
|
2
|
+
import type { EventBus } from '../event-bus/event-bus';
|
|
3
|
+
import type { QueueEvents } from '../events';
|
|
2
4
|
import type { EnqueueOptions, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig } from './types';
|
|
3
5
|
/** DB-backed job queue implementation over `@gobing-ai/ts-db`'s `QueueJobDao`. */
|
|
4
6
|
export declare class DBJobQueue<T = unknown> implements JobQueue<T> {
|
|
5
7
|
readonly dao: QueueJobDao;
|
|
6
|
-
|
|
8
|
+
private readonly events?;
|
|
9
|
+
constructor(dao: QueueJobDao, events?: EventBus<QueueEvents> | undefined);
|
|
7
10
|
enqueue(type: string, payload: T, options?: EnqueueOptions): Promise<string>;
|
|
8
11
|
enqueueBatch(jobs: Array<{
|
|
9
12
|
type: string;
|
|
@@ -22,6 +25,7 @@ export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
22
25
|
private readonly baseDelay;
|
|
23
26
|
private readonly maxDelay;
|
|
24
27
|
private readonly drainTimeoutMs;
|
|
28
|
+
private readonly events;
|
|
25
29
|
private timer;
|
|
26
30
|
private running;
|
|
27
31
|
private inFlight;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-job-queue.d.ts","sourceRoot":"","sources":["../../src/job-queue/db-job-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"db-job-queue.d.ts","sourceRoot":"","sources":["../../src/job-queue/db-job-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAS7C,OAAO,KAAK,EAAE,cAAc,EAAO,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7G,kFAAkF;AAClF,qBAAa,UAAU,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAEnD,QAAQ,CAAC,GAAG,EAAE,WAAW;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADf,GAAG,EAAE,WAAW,EACR,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,YAAA;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAW3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAED,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAe7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;gBAGA,GAAG,EAAE,WAAW,EACjC,MAAM,GAAE,mBAAwB;IAYpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAMF,IAAI;YAgBJ,UAAU;YAqCV,WAAW;CA4B5B"}
|
|
@@ -1,19 +1,34 @@
|
|
|
1
|
+
import { getLogger } from '../logger.js';
|
|
1
2
|
import { getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, } from '../telemetry/metrics.js';
|
|
2
3
|
import { addSpanAttributes, traceAsync } from '../telemetry/tracing.js';
|
|
4
|
+
let _queueLogger;
|
|
5
|
+
function queueLogger() {
|
|
6
|
+
if (!_queueLogger)
|
|
7
|
+
_queueLogger = getLogger('job-queue');
|
|
8
|
+
return _queueLogger;
|
|
9
|
+
}
|
|
3
10
|
/** DB-backed job queue implementation over `@gobing-ai/ts-db`'s `QueueJobDao`. */
|
|
4
11
|
export class DBJobQueue {
|
|
5
12
|
dao;
|
|
6
|
-
|
|
13
|
+
events;
|
|
14
|
+
constructor(dao, events) {
|
|
7
15
|
this.dao = dao;
|
|
16
|
+
this.events = events;
|
|
8
17
|
}
|
|
9
18
|
async enqueue(type, payload, options) {
|
|
10
19
|
const id = await this.dao.enqueue(type, payload, options);
|
|
11
20
|
getQueueJobEnqueuedTotal().add(1, { type });
|
|
21
|
+
await this.events?.emit('queue.job.enqueued', { jobId: id, type });
|
|
12
22
|
return id;
|
|
13
23
|
}
|
|
14
24
|
async enqueueBatch(jobs) {
|
|
15
25
|
const ids = await this.dao.enqueueBatch(jobs);
|
|
16
26
|
getQueueJobEnqueuedTotal().add(jobs.length);
|
|
27
|
+
if (this.events) {
|
|
28
|
+
for (const [index, jobId] of ids.entries()) {
|
|
29
|
+
await this.events.emit('queue.job.enqueued', { jobId, type: jobs[index]?.type ?? 'unknown' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
17
32
|
return ids;
|
|
18
33
|
}
|
|
19
34
|
async stats() {
|
|
@@ -31,6 +46,7 @@ export class DBQueueConsumer {
|
|
|
31
46
|
baseDelay;
|
|
32
47
|
maxDelay;
|
|
33
48
|
drainTimeoutMs;
|
|
49
|
+
events;
|
|
34
50
|
timer = null;
|
|
35
51
|
running = false;
|
|
36
52
|
inFlight = 0;
|
|
@@ -43,6 +59,7 @@ export class DBQueueConsumer {
|
|
|
43
59
|
this.baseDelay = config.baseDelay ?? 1_000;
|
|
44
60
|
this.maxDelay = config.maxDelay ?? 60_000;
|
|
45
61
|
this.drainTimeoutMs = config.drainTimeoutMs ?? 30_000;
|
|
62
|
+
this.events = config.events;
|
|
46
63
|
}
|
|
47
64
|
register(type, handler) {
|
|
48
65
|
this.handlers.set(type, handler);
|
|
@@ -52,8 +69,10 @@ export class DBQueueConsumer {
|
|
|
52
69
|
return;
|
|
53
70
|
this.running = true;
|
|
54
71
|
this.schedule(0);
|
|
72
|
+
await this.events?.emit('queue.consumer.started');
|
|
55
73
|
}
|
|
56
74
|
async stop() {
|
|
75
|
+
const wasRunning = this.running;
|
|
57
76
|
this.running = false;
|
|
58
77
|
if (this.timer !== null) {
|
|
59
78
|
clearTimeout(this.timer);
|
|
@@ -63,6 +82,8 @@ export class DBQueueConsumer {
|
|
|
63
82
|
while (this.inFlight > 0 && Date.now() < deadline) {
|
|
64
83
|
await sleep(10);
|
|
65
84
|
}
|
|
85
|
+
if (wasRunning)
|
|
86
|
+
await this.events?.emit('queue.consumer.stopped');
|
|
66
87
|
}
|
|
67
88
|
async stats() {
|
|
68
89
|
return this.dao.getStats();
|
|
@@ -102,13 +123,30 @@ export class DBQueueConsumer {
|
|
|
102
123
|
try {
|
|
103
124
|
await this.processOnce();
|
|
104
125
|
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
// The timer fires poll() as a floating promise — a DAO/processing error
|
|
128
|
+
// must be contained here or it becomes an unhandled rejection that can
|
|
129
|
+
// kill the process. Log and let the next cycle retry.
|
|
130
|
+
queueLogger().error('queue poll cycle failed', {
|
|
131
|
+
error: error instanceof Error ? error.message : String(error),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
105
134
|
finally {
|
|
106
135
|
if (this.running)
|
|
107
136
|
this.schedule(this.pollInterval);
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
139
|
async processJob(record) {
|
|
111
|
-
|
|
140
|
+
let job;
|
|
141
|
+
try {
|
|
142
|
+
job = toJob(record);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// Corrupt payload — the job can never parse; route it through the
|
|
146
|
+
// retry/fail path instead of rejecting the whole batch.
|
|
147
|
+
await this.failOrRetry(record, error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
112
150
|
return traceAsync('queue.job.process', async () => {
|
|
113
151
|
addSpanAttributes({
|
|
114
152
|
'queue.job_id': job.id,
|
|
@@ -126,6 +164,7 @@ export class DBQueueConsumer {
|
|
|
126
164
|
await this.dao.markCompleted(job.id);
|
|
127
165
|
getQueueJobCompletedTotal().add(1, { type: job.type });
|
|
128
166
|
getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
|
|
167
|
+
await this.events?.emit('queue.job.completed', { jobId: job.id, type: job.type });
|
|
129
168
|
}
|
|
130
169
|
catch (error) {
|
|
131
170
|
getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
|
|
@@ -139,10 +178,23 @@ export class DBQueueConsumer {
|
|
|
139
178
|
if (attempts >= job.maxRetries) {
|
|
140
179
|
await this.dao.markFailed(job.id, attempts, message);
|
|
141
180
|
getQueueJobFailedTotal().add(1, { type: job.type });
|
|
181
|
+
await this.events?.emit('queue.job.failed', {
|
|
182
|
+
jobId: job.id,
|
|
183
|
+
type: job.type,
|
|
184
|
+
error: message,
|
|
185
|
+
attempt: attempts,
|
|
186
|
+
});
|
|
142
187
|
return;
|
|
143
188
|
}
|
|
144
189
|
const delay = Math.min(this.maxDelay, this.baseDelay * 2 ** Math.max(0, attempts - 1));
|
|
145
|
-
|
|
190
|
+
const nextRetryAt = Date.now() + delay;
|
|
191
|
+
await this.dao.markForRetry(job.id, attempts, message, nextRetryAt);
|
|
192
|
+
await this.events?.emit('queue.job.retrying', {
|
|
193
|
+
jobId: job.id,
|
|
194
|
+
type: job.type,
|
|
195
|
+
attempt: attempts,
|
|
196
|
+
nextRetryAt,
|
|
197
|
+
});
|
|
146
198
|
}
|
|
147
199
|
}
|
|
148
200
|
function toJob(record) {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* The concrete DB-backed implementations live beside these interfaces in
|
|
5
5
|
* `DBJobQueue` and `DBQueueConsumer`.
|
|
6
6
|
*/
|
|
7
|
+
import type { EventBus } from '../event-bus/event-bus';
|
|
8
|
+
import type { QueueEvents } from '../events';
|
|
7
9
|
/** A queued job with status tracking, retry metadata, and timestamps. */
|
|
8
10
|
export interface Job<T = unknown> {
|
|
9
11
|
id: string;
|
|
@@ -51,6 +53,12 @@ export interface QueueConsumerConfig {
|
|
|
51
53
|
baseDelay?: number;
|
|
52
54
|
maxDelay?: number;
|
|
53
55
|
drainTimeoutMs?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Optional bus for queue lifecycle events: `queue.consumer.started` /
|
|
58
|
+
* `queue.consumer.stopped` and `queue.job.completed` / `queue.job.failed` /
|
|
59
|
+
* `queue.job.retrying`. Omitting it leaves the consumer silent (default).
|
|
60
|
+
*/
|
|
61
|
+
events?: EventBus<QueueEvents>;
|
|
54
62
|
}
|
|
55
63
|
/** Consumer interface for the job queue — register handlers and control the processing loop. */
|
|
56
64
|
export interface QueueConsumer<T = unknown> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,OAAO;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,OAAO;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC"}
|
package/dist/scheduler/action.js
CHANGED
|
@@ -70,7 +70,7 @@ export class QueueStatsAction {
|
|
|
70
70
|
const dao = await this.daoProvider();
|
|
71
71
|
const stats = await dao.getStats();
|
|
72
72
|
logger.info('Queue stats snapshot', { action: this.name, ...stats });
|
|
73
|
-
this.systemBus?.emit('queue.stats', stats);
|
|
73
|
+
await this.systemBus?.emit('queue.stats', stats);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/scheduler/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/scheduler/node.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAoCjE;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,OAAO,CAAS;;IAIxB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAU/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,OAAO,CAAC,UAAU;YAOJ,gBAAgB;CAYjC"}
|
package/dist/scheduler/node.js
CHANGED
|
@@ -2,24 +2,34 @@
|
|
|
2
2
|
* Node.js scheduler adapter using a simple setInterval-based approach.
|
|
3
3
|
* No external cron library dependency — cron expressions are parsed minimally.
|
|
4
4
|
*/
|
|
5
|
+
import { getLogger } from '../logger.js';
|
|
5
6
|
import { getSchedulerJobDuration, getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal, } from '../telemetry/metrics.js';
|
|
6
7
|
/** Simple helper to parse cron-like interval strings into milliseconds. */
|
|
7
8
|
function parseInterval(cron) {
|
|
8
9
|
// Support simple patterns: "* * * * *" (every minute), "*/5 * * * *" (every 5 min)
|
|
9
10
|
// Also support direct ms strings like "60000"
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const trimmed = cron.trim();
|
|
12
|
+
const num = Number(trimmed);
|
|
13
|
+
if (trimmed !== '' && !Number.isNaN(num)) {
|
|
14
|
+
// Guard against 0/negative intervals — setInterval would spin hot.
|
|
15
|
+
if (num > 0)
|
|
16
|
+
return num;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
else {
|
|
19
|
+
const parts = trimmed.split(/\s+/);
|
|
20
|
+
if (parts.length === 5 && parts[0] === '*') {
|
|
21
|
+
return 60_000; // every minute
|
|
22
|
+
}
|
|
23
|
+
// */N pattern
|
|
24
|
+
const match = parts[0]?.match(/^\*\/(\d+)$/);
|
|
25
|
+
if (match && Number(match[1]) > 0) {
|
|
26
|
+
return Number(match[1]) * 60_000;
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
|
-
|
|
29
|
+
// Real cron field expressions ("0 3 * * *") are NOT supported — running
|
|
30
|
+
// them every minute silently would badly misfire, so warn on fallback.
|
|
31
|
+
getLogger('scheduler.node').warn('Unsupported cron expression — falling back to a 60s interval', { cron });
|
|
32
|
+
return 60_000;
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
25
35
|
* Scheduler adapter for Node.js using a setInterval-based approach.
|
|
@@ -34,7 +34,7 @@ export function wrapScheduledHandler(name, action, systemBus) {
|
|
|
34
34
|
finally {
|
|
35
35
|
const durationMs = Math.round(performance.now() - startTime);
|
|
36
36
|
addSpanAttributes({ 'scheduler.duration_ms': durationMs });
|
|
37
|
-
systemBus?.emit('scheduler.job.executed', {
|
|
37
|
+
void systemBus?.emit('scheduler.job.executed', {
|
|
38
38
|
name,
|
|
39
39
|
durationMs,
|
|
40
40
|
...(execError !== undefined ? { error: execError } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/telemetry/metrics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/telemetry/metrics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,OAAO,EAAmB,KAAK,SAAS,EAAW,MAAM,oBAAoB,CAAC;AAG5F,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAI7D,kFAAkF;AAClF,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAgDD,kFAAkF;AAClF,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,IAAI,SAAS,CAExD;AAED,8DAA8D;AAC9D,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAID,yCAAyC;AACzC,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED,4CAA4C;AAC5C,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAID,uCAAuC;AACvC,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,qDAAqD;AACrD,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED,6DAA6D;AAC7D,wBAAgB,6BAA6B,IAAI,SAAS,CAEzD;AAID,kDAAkD;AAClD,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD;AAED,sEAAsE;AACtE,wBAAgB,uBAAuB,IAAI,SAAS,CAEnD;AAED,mDAAmD;AACnD,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAID;;;;;;;;GAQG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAgBlC;AAED,oEAAoE;AACpE,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAS/C;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAMpC"}
|