@gobing-ai/ts-infra 0.4.28 → 0.4.30
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 +10 -3
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +1 -0
- package/dist/event-bus/event-bus.d.ts.map +1 -1
- package/dist/event-bus/event-bus.js +12 -4
- package/dist/event-bus/types.d.ts +5 -4
- package/dist/event-bus/types.d.ts.map +1 -1
- package/dist/events.d.ts +12 -10
- package/dist/events.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/job-queue/db-job-queue.d.ts.map +1 -1
- package/dist/job-queue/db-job-queue.js +8 -2
- package/dist/scheduler/action.js +1 -1
- package/dist/scheduler/node.d.ts.map +1 -1
- package/dist/scheduler/node.js +15 -9
- package/dist/scheduler/wrap-handler.d.ts.map +1 -1
- package/dist/scheduler/wrap-handler.js +1 -0
- package/package.json +6 -6
- package/src/api-client.ts +1 -0
- package/src/event-bus/event-bus.ts +12 -4
- package/src/event-bus/types.ts +6 -4
- package/src/events.ts +13 -10
- package/src/index.ts +2 -0
- package/src/job-queue/db-job-queue.ts +8 -2
- package/src/scheduler/action.ts +1 -1
- package/src/scheduler/node.ts +18 -9
- package/src/scheduler/wrap-handler.ts +1 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Infrastructure backbone — typed event bus, queue/scheduler contracts, adapter
|
|
|
7
7
|
`ts-infra` provides the subsystems that form the application backbone:
|
|
8
8
|
|
|
9
9
|
| Subsystem | Module | Purpose |
|
|
10
|
-
|
|
10
|
+
| ----------- | -------- | --------- |
|
|
11
11
|
| **Event Bus** | `event-bus/` | Typed pub/sub with sync + async dispatch, named async handlers, queue consumer bridge, lifecycle self-observability |
|
|
12
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 |
|
|
@@ -326,6 +326,11 @@ export default {
|
|
|
326
326
|
};
|
|
327
327
|
```
|
|
328
328
|
|
|
329
|
+
**Cron support is deliberately minimal (task 0060 F7):** a positive millisecond number
|
|
330
|
+
(`'60000'`), `'* * * * *'`, or `'*/N * * * *'` (N > 0). Any other expression — including a
|
|
331
|
+
full 5-field cron like `'0 3 * * *'` — makes `register()` throw a `RangeError`; it never
|
|
332
|
+
silently falls back to a 60s interval.
|
|
333
|
+
|
|
329
334
|
### API Client — typed HTTP with tracing
|
|
330
335
|
|
|
331
336
|
```ts
|
|
@@ -497,7 +502,6 @@ bun add @opentelemetry/sdk-trace-node @opentelemetry/sdk-metrics \
|
|
|
497
502
|
@opentelemetry/exporter-metrics-otlp-http
|
|
498
503
|
```
|
|
499
504
|
|
|
500
|
-
|
|
501
505
|
### Application Bootstrap
|
|
502
506
|
|
|
503
507
|
The application bootstrap API provides a deterministic lifecycle for wiring
|
|
@@ -606,6 +610,7 @@ billing:
|
|
|
606
610
|
```
|
|
607
611
|
|
|
608
612
|
Config validators accept four shapes:
|
|
613
|
+
|
|
609
614
|
- `{ safeParse(raw) → { success, data?, errors? } }` — Zod-compatible
|
|
610
615
|
- `(raw) => TAppConfig` — bare function
|
|
611
616
|
- `{ validate(raw) => TAppConfig }` — method form
|
|
@@ -640,6 +645,7 @@ await runCliApplication({
|
|
|
640
645
|
```
|
|
641
646
|
|
|
642
647
|
Differences from `runNodeApplication`:
|
|
648
|
+
|
|
643
649
|
- `start` returns `number | void` — the number becomes the exit code (void = 0)
|
|
644
650
|
- On success, calls `app.stop('shutdown')` then `process.exit(code)`
|
|
645
651
|
- On error, writes the message to stderr and exits 1 (after graceful teardown)
|
|
@@ -719,7 +725,7 @@ attached — land in the JSONL System Events log alongside the domain event itse
|
|
|
719
725
|
**Six System Events prefixes and where they originate:**
|
|
720
726
|
|
|
721
727
|
| Prefix | Source | Constructed when `events` is omitted |
|
|
722
|
-
|
|
728
|
+
| -------- | -------- | --------------------------------------- |
|
|
723
729
|
| `bus.*` | `EventBus` lifecycle self-observability (the `lifecycleBus` itself) | always — `runApplication` creates it |
|
|
724
730
|
| `api.*` | `APIClient` (`api.request.error`) | `new EventBus<ApiClientEvents>({ lifecycleBus })` (R5) |
|
|
725
731
|
| `rule.*` | `RuleEngine` (rule-run structured observability) | `new EventBus<RuleEngineEvents>({ lifecycleBus })` (R2) |
|
|
@@ -788,6 +794,7 @@ The host forwards the optional teardown `reason` (a plain `string` on the core c
|
|
|
788
794
|
`ApplicationStopReason` — `'manual' | 'signal' | 'error' | 'shutdown'` — at the app level)
|
|
789
795
|
to every plugin's `onStop`/`onUnload`. Built-in service plugins use this for log context;
|
|
790
796
|
the user-callback plugin delivers it to your `stop(app, reason)`.
|
|
797
|
+
|
|
791
798
|
```
|
|
792
799
|
|
|
793
800
|
### Graceful shutdown
|
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":"AASA,OAAO,EAAE,KAAK,kBAAkB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACtE,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;IACnC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;CAC/C;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;AAoED;;;;;;;GAOG;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;IAUnC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,gBAAgB;
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/api-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;IA4F7F,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"}
|
|
@@ -146,7 +146,15 @@ export class EventBus {
|
|
|
146
146
|
getEventbusEmitsTotal().add(1, { event: eventName });
|
|
147
147
|
if (errors > 0)
|
|
148
148
|
getEventbusErrorsTotal().add(errors, { event: eventName });
|
|
149
|
-
this.publishEmitDone({
|
|
149
|
+
this.publishEmitDone({
|
|
150
|
+
event: eventName,
|
|
151
|
+
syncCount,
|
|
152
|
+
asyncCount,
|
|
153
|
+
emitDurationMs: durationMs,
|
|
154
|
+
errors,
|
|
155
|
+
detail,
|
|
156
|
+
severity: errors > 0 ? 'warning' : 'info',
|
|
157
|
+
});
|
|
150
158
|
if (syncCount === 0 && asyncCount === 0) {
|
|
151
159
|
busLogger().debug('emit with zero handlers', { event: eventName });
|
|
152
160
|
this.publishEmitNoop(eventName);
|
|
@@ -246,7 +254,7 @@ export class EventBus {
|
|
|
246
254
|
publishEmitNoop(event) {
|
|
247
255
|
if (this.lifecycleBus) {
|
|
248
256
|
try {
|
|
249
|
-
void this.lifecycleBus.emit('bus.emit.noop', { event });
|
|
257
|
+
void this.lifecycleBus.emit('bus.emit.noop', { event, severity: 'info' });
|
|
250
258
|
}
|
|
251
259
|
catch {
|
|
252
260
|
// Swallow.
|
|
@@ -255,7 +263,7 @@ export class EventBus {
|
|
|
255
263
|
}
|
|
256
264
|
publishHandlerError(event, mode, error) {
|
|
257
265
|
if (this.lifecycleBus) {
|
|
258
|
-
const detail = { event, mode, error };
|
|
266
|
+
const detail = { event, mode, error, severity: 'error' };
|
|
259
267
|
try {
|
|
260
268
|
void this.lifecycleBus.emit('bus.handler.error', detail);
|
|
261
269
|
}
|
|
@@ -266,7 +274,7 @@ export class EventBus {
|
|
|
266
274
|
}
|
|
267
275
|
publishAsyncEnqueued(event, jobId, handlerCount) {
|
|
268
276
|
if (this.lifecycleBus) {
|
|
269
|
-
const detail = { event, jobId, handlerCount };
|
|
277
|
+
const detail = { event, jobId, handlerCount, severity: 'info' };
|
|
270
278
|
try {
|
|
271
279
|
void this.lifecycleBus.emit('bus.handler.async.enqueued', detail);
|
|
272
280
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Event bus types — shared constraints for typed pub-sub.
|
|
3
3
|
*/
|
|
4
|
+
import type { WithEventSeverity } from '@gobing-ai/ts-utils';
|
|
4
5
|
/** Type constraint for event maps — keys are event names, values are listener signatures. */
|
|
5
6
|
export type EventMap = Record<string, (...args: never[]) => void>;
|
|
6
7
|
/** Options for subscribing to an event, including async dispatch mode. */
|
|
@@ -27,7 +28,7 @@ export interface AsyncEventJobPayload {
|
|
|
27
28
|
handlerId: string;
|
|
28
29
|
}
|
|
29
30
|
/** Payload emitted on `bus.emit.done` after synchronous handlers complete. */
|
|
30
|
-
export interface EmitDoneDetail {
|
|
31
|
+
export interface EmitDoneDetail extends WithEventSeverity {
|
|
31
32
|
event: string;
|
|
32
33
|
syncCount: number;
|
|
33
34
|
asyncCount: number;
|
|
@@ -36,13 +37,13 @@ export interface EmitDoneDetail {
|
|
|
36
37
|
detail?: unknown;
|
|
37
38
|
}
|
|
38
39
|
/** Payload emitted on `bus.handler.error` when a handler throws. */
|
|
39
|
-
export interface HandlerErrorDetail {
|
|
40
|
+
export interface HandlerErrorDetail extends WithEventSeverity {
|
|
40
41
|
event: string;
|
|
41
42
|
mode: 'sync' | 'async';
|
|
42
43
|
error: string;
|
|
43
44
|
}
|
|
44
45
|
/** Payload emitted on `bus.handler.async.enqueued` when async handlers are scheduled. */
|
|
45
|
-
export interface AsyncEnqueuedDetail {
|
|
46
|
+
export interface AsyncEnqueuedDetail extends WithEventSeverity {
|
|
46
47
|
event: string;
|
|
47
48
|
jobId: string;
|
|
48
49
|
handlerCount: number;
|
|
@@ -52,7 +53,7 @@ export type BusLifecycleEvents = {
|
|
|
52
53
|
'bus.emit.done': (detail: EmitDoneDetail) => void;
|
|
53
54
|
'bus.emit.noop': (detail: {
|
|
54
55
|
event: string;
|
|
55
|
-
}) => void;
|
|
56
|
+
} & WithEventSeverity) => void;
|
|
56
57
|
'bus.handler.error': (detail: HandlerErrorDetail) => void;
|
|
57
58
|
'bus.handler.async.enqueued': (detail: AsyncEnqueuedDetail) => void;
|
|
58
59
|
};
|
|
@@ -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;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,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/event-bus/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,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,cAAe,SAAQ,iBAAiB;IACrD,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,kBAAmB,SAAQ,iBAAiB;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,yFAAyF;AACzF,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC1D,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,GAAG,iBAAiB,KAAK,IAAI,CAAC;IACzE,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
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
* Consume via `EventBus<InfraEvents>` or compose individual maps into a wider
|
|
18
18
|
* app event map.
|
|
19
19
|
*/
|
|
20
|
+
import type { EventSeverity, WithEventSeverity } from '@gobing-ai/ts-utils';
|
|
20
21
|
import type { QueueStats } from './job-queue/types';
|
|
22
|
+
export type { EventSeverity, WithEventSeverity };
|
|
21
23
|
/** Payload for `db.connection.error`. */
|
|
22
|
-
export interface DbConnectionErrorDetail {
|
|
24
|
+
export interface DbConnectionErrorDetail extends WithEventSeverity {
|
|
23
25
|
/** Error message. */
|
|
24
26
|
error: string;
|
|
25
27
|
/** Adapter type (e.g. 'sqlite', 'd1'). */
|
|
@@ -33,7 +35,7 @@ export interface QueueJobRef {
|
|
|
33
35
|
type: string;
|
|
34
36
|
}
|
|
35
37
|
/** Payload for `queue.job.enqueued` — a new job was added to the queue. */
|
|
36
|
-
export interface QueueJobEnqueuedDetail extends QueueJobRef {
|
|
38
|
+
export interface QueueJobEnqueuedDetail extends QueueJobRef, WithEventSeverity {
|
|
37
39
|
/** Epoch ms when the job was enqueued. */
|
|
38
40
|
enqueuedAt: number;
|
|
39
41
|
/** Max retry count from `EnqueueOptions.maxRetries`, when supplied. */
|
|
@@ -44,7 +46,7 @@ export interface QueueJobEnqueuedDetail extends QueueJobRef {
|
|
|
44
46
|
ttlMs?: number;
|
|
45
47
|
}
|
|
46
48
|
/** Payload for `queue.consumer.started` — the polling loop began. */
|
|
47
|
-
export interface QueueConsumerStartedDetail {
|
|
49
|
+
export interface QueueConsumerStartedDetail extends WithEventSeverity {
|
|
48
50
|
/** Epoch ms when the consumer was started. */
|
|
49
51
|
startedAt: number;
|
|
50
52
|
/** Polling interval in ms. */
|
|
@@ -57,7 +59,7 @@ export interface QueueConsumerStartedDetail {
|
|
|
57
59
|
visibilityTimeout: number;
|
|
58
60
|
}
|
|
59
61
|
/** Payload for `queue.consumer.stopped` — the polling loop was halted. */
|
|
60
|
-
export interface QueueConsumerStoppedDetail {
|
|
62
|
+
export interface QueueConsumerStoppedDetail extends WithEventSeverity {
|
|
61
63
|
/** Epoch ms when `stop()` was called. */
|
|
62
64
|
stoppedAt: number;
|
|
63
65
|
/** Drain deadline in ms applied to in-flight handlers at stop time. */
|
|
@@ -68,14 +70,14 @@ export interface QueueConsumerStoppedDetail {
|
|
|
68
70
|
drained: boolean;
|
|
69
71
|
}
|
|
70
72
|
/** Payload for `queue.job.completed` — a job ran to success. */
|
|
71
|
-
export interface QueueJobCompletedDetail extends QueueJobRef {
|
|
73
|
+
export interface QueueJobCompletedDetail extends QueueJobRef, WithEventSeverity {
|
|
72
74
|
/** Handler wall-clock duration in ms. */
|
|
73
75
|
durationMs: number;
|
|
74
76
|
/** Attempts counter on the job row at success (0 on the first successful run). */
|
|
75
77
|
attempt: number;
|
|
76
78
|
}
|
|
77
79
|
/** Payload for `queue.job.failed` — a job exhausted retries. */
|
|
78
|
-
export interface QueueJobFailedDetail extends QueueJobRef {
|
|
80
|
+
export interface QueueJobFailedDetail extends QueueJobRef, WithEventSeverity {
|
|
79
81
|
/** Error message from the final attempt. */
|
|
80
82
|
error: string;
|
|
81
83
|
/** 1-based attempt number of the failure. */
|
|
@@ -86,7 +88,7 @@ export interface QueueJobFailedDetail extends QueueJobRef {
|
|
|
86
88
|
durationMs?: number;
|
|
87
89
|
}
|
|
88
90
|
/** Payload for `queue.job.retrying` — a job will be retried after backoff. */
|
|
89
|
-
export interface QueueJobRetryingDetail extends QueueJobRef {
|
|
91
|
+
export interface QueueJobRetryingDetail extends QueueJobRef, WithEventSeverity {
|
|
90
92
|
/** 1-based attempt number of the failure that triggers this retry. */
|
|
91
93
|
attempt: number;
|
|
92
94
|
/** Maximum retry count configured for the job. */
|
|
@@ -97,7 +99,7 @@ export interface QueueJobRetryingDetail extends QueueJobRef {
|
|
|
97
99
|
error: string;
|
|
98
100
|
}
|
|
99
101
|
/** Payload for `scheduler.job.executed`. */
|
|
100
|
-
export interface SchedulerJobExecutedDetail {
|
|
102
|
+
export interface SchedulerJobExecutedDetail extends WithEventSeverity {
|
|
101
103
|
/** Job name. */
|
|
102
104
|
name: string;
|
|
103
105
|
/** Wall-clock duration in milliseconds. */
|
|
@@ -106,7 +108,7 @@ export interface SchedulerJobExecutedDetail {
|
|
|
106
108
|
error?: string;
|
|
107
109
|
}
|
|
108
110
|
/** Payload for `api.request.error`. */
|
|
109
|
-
export interface ApiRequestErrorDetail {
|
|
111
|
+
export interface ApiRequestErrorDetail extends WithEventSeverity {
|
|
110
112
|
url: string;
|
|
111
113
|
method: string;
|
|
112
114
|
/** HTTP status code, if a response was received. */
|
|
@@ -135,7 +137,7 @@ export type QueueEvents = {
|
|
|
135
137
|
/** A job will be retried after backoff. */
|
|
136
138
|
'queue.job.retrying': (detail: QueueJobRetryingDetail) => void;
|
|
137
139
|
/** Queue depth snapshot emitted on a poll cycle. */
|
|
138
|
-
'queue.stats': (detail: QueueStats) => void;
|
|
140
|
+
'queue.stats': (detail: QueueStats & WithEventSeverity) => void;
|
|
139
141
|
};
|
|
140
142
|
/** Scheduler events emitted by scheduler adapters and the handler wrapper. */
|
|
141
143
|
export type SchedulerEvents = {
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { APIClient, type APIClientConfig, APIError, type RawHttpResponse, type RawRequestOptions, type RequestOptions, } from './api-client';
|
|
2
2
|
export { type AsyncEventJobPayload, attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, type BusLifecycleEvents, createLifecycleBus, EventBus, type EventMap, type FileObserverWriter, type SubscribeOptions, } from './event-bus/index';
|
|
3
|
-
export type { ApiClientEvents, ApiRequestErrorDetail, DbConnectionErrorDetail, DbEvents, InfraEvents, QueueConsumerStartedDetail, QueueConsumerStoppedDetail, QueueEvents, QueueJobCompletedDetail, QueueJobEnqueuedDetail, QueueJobFailedDetail, QueueJobRef, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, } from './events';
|
|
3
|
+
export type { ApiClientEvents, ApiRequestErrorDetail, DbConnectionErrorDetail, DbEvents, EventSeverity, InfraEvents, QueueConsumerStartedDetail, QueueConsumerStoppedDetail, QueueEvents, QueueJobCompletedDetail, QueueJobEnqueuedDetail, QueueJobFailedDetail, QueueJobRef, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, WithEventSeverity, } 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';
|
|
6
6
|
export { ActionRegistry, type CreateDefaultRegistryOptions, createDefaultRegistry, HealthPingAction, type HealthPingWriter, initScheduler, LogAction, NoopSchedulerAdapter, QueueStatsAction, type QueueStatsDaoProvider, type ScheduledAction, type SchedulerAction, type SchedulerAdapter, toScheduledAction, wrapScheduledHandler, } from './scheduler/index';
|
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,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,KAAK,kBAAkB,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,0BAA0B,EAC1B,0BAA0B,EAC1B,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,0BAA0B,
|
|
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,KAAK,kBAAkB,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,aAAa,EACb,WAAW,EACX,0BAA0B,EAC1B,0BAA0B,EAC1B,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,GACpB,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"}
|
|
@@ -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;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAER,WAAW,EAKd,MAAM,WAAW,CAAC;AAUnB,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;IAY3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAcD,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAgB7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAfxB,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;IACrB,OAAO,CAAC,WAAW,CAA8B;gBAG5B,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;
|
|
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,EAER,WAAW,EAKd,MAAM,WAAW,CAAC;AAUnB,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;IAY3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAcD,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAgB7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAfxB,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;IACrB,OAAO,CAAC,WAAW,CAA8B;gBAG5B,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;IActB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoCrB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAWF,IAAI;YAgBJ,UAAU;YA8CV,WAAW;CAqC5B"}
|
|
@@ -42,7 +42,7 @@ export class DBJobQueue {
|
|
|
42
42
|
* fields only when supplied. Shared by single and batch enqueue so both emit one shape.
|
|
43
43
|
*/
|
|
44
44
|
function enqueuedDetail(jobId, type, options) {
|
|
45
|
-
const detail = { jobId, type, enqueuedAt: Date.now() };
|
|
45
|
+
const detail = { jobId, type, enqueuedAt: Date.now(), severity: 'info' };
|
|
46
46
|
if (options && options.maxRetries !== undefined)
|
|
47
47
|
detail.maxRetries = options.maxRetries;
|
|
48
48
|
if (options && options.delay !== undefined)
|
|
@@ -92,6 +92,7 @@ export class DBQueueConsumer {
|
|
|
92
92
|
batchSize: this.batchSize,
|
|
93
93
|
maxConcurrency: this.maxConcurrency,
|
|
94
94
|
visibilityTimeout: this.visibilityTimeout,
|
|
95
|
+
severity: 'info',
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
async stop() {
|
|
@@ -115,11 +116,13 @@ export class DBQueueConsumer {
|
|
|
115
116
|
await sleep(10);
|
|
116
117
|
}
|
|
117
118
|
if (wasRunning) {
|
|
119
|
+
const drained = this.inFlight === 0;
|
|
118
120
|
const detail = {
|
|
119
121
|
stoppedAt: Date.now(),
|
|
120
122
|
drainTimeoutMs: this.drainTimeoutMs,
|
|
121
123
|
inFlightAtStop: this.inFlight,
|
|
122
|
-
drained
|
|
124
|
+
drained,
|
|
125
|
+
severity: drained ? 'info' : 'warning',
|
|
123
126
|
};
|
|
124
127
|
await this.events?.emit('queue.consumer.stopped', detail);
|
|
125
128
|
}
|
|
@@ -215,6 +218,7 @@ export class DBQueueConsumer {
|
|
|
215
218
|
type: job.type,
|
|
216
219
|
durationMs: Number.isFinite(durationMs) ? durationMs : 0,
|
|
217
220
|
attempt: job.attempts,
|
|
221
|
+
severity: 'info',
|
|
218
222
|
};
|
|
219
223
|
await this.events?.emit('queue.job.completed', completed);
|
|
220
224
|
}
|
|
@@ -238,6 +242,7 @@ export class DBQueueConsumer {
|
|
|
238
242
|
attempt: attempts,
|
|
239
243
|
maxRetries: job.maxRetries,
|
|
240
244
|
durationMs,
|
|
245
|
+
severity: 'error',
|
|
241
246
|
};
|
|
242
247
|
await this.events?.emit('queue.job.failed', failed);
|
|
243
248
|
return;
|
|
@@ -252,6 +257,7 @@ export class DBQueueConsumer {
|
|
|
252
257
|
maxRetries: job.maxRetries,
|
|
253
258
|
nextRetryAt,
|
|
254
259
|
error: message,
|
|
260
|
+
severity: 'warning',
|
|
255
261
|
};
|
|
256
262
|
await this.events?.emit('queue.job.retrying', retrying);
|
|
257
263
|
}
|
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
|
-
await this.systemBus?.emit('queue.stats', stats);
|
|
73
|
+
await this.systemBus?.emit('queue.stats', { ...stats, severity: 'info' });
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/scheduler/node.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/scheduler/node.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA2CjE,4DAA4D;AAC5D,MAAM,WAAW,0BAA0B;IACvC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAEzC,MAAM,GAAE,0BAA+B;IAUnD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAa/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,OAAO,CAAC,UAAU;YAiBJ,gBAAgB;CAYjC"}
|
package/dist/scheduler/node.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* No external cron library dependency — cron expressions are parsed minimally.
|
|
4
4
|
*/
|
|
5
5
|
import { settleWithin } from '../internals/drain.js';
|
|
6
|
-
import { getLogger } from '../logger.js';
|
|
7
6
|
import { getSchedulerJobDuration, getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal, } from '../telemetry/metrics.js';
|
|
8
7
|
/** Simple helper to parse cron-like interval strings into milliseconds. */
|
|
9
8
|
function parseInterval(cron) {
|
|
@@ -18,19 +17,23 @@ function parseInterval(cron) {
|
|
|
18
17
|
}
|
|
19
18
|
else {
|
|
20
19
|
const parts = trimmed.split(/\s+/);
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
// Only the documented 5-field forms are every-N-minutes: "* * * * *" and
|
|
21
|
+
// "*/N * * * *". A first-field wildcard alone ("* 3 * * *") is real cron
|
|
22
|
+
// and must not silently fire every 60s (task 0060 F7).
|
|
23
|
+
const restWild = parts.length === 5 && parts[1] === '*' && parts[2] === '*' && parts[3] === '*' && parts[4] === '*';
|
|
24
|
+
if (restWild && parts[0] === '*') {
|
|
25
|
+
return 60_000;
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
const match =
|
|
27
|
+
const nField = restWild ? parts[0] : parts.length === 1 ? parts[0] : undefined;
|
|
28
|
+
const match = nField?.match(/^\*\/(\d+)$/);
|
|
26
29
|
if (match && Number(match[1]) > 0) {
|
|
27
30
|
return Number(match[1]) * 60_000;
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
|
-
// Real cron field expressions ("0 3 * * *") are NOT supported — running
|
|
31
|
-
// them every minute
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// Real cron field expressions ("0 3 * * *") are NOT supported — silently running
|
|
34
|
+
// them every minute would badly misfire. 0032 made that a warn; task 0060 F7 makes
|
|
35
|
+
// it a hard error so an unsupported schedule can never fire at the wrong cadence.
|
|
36
|
+
throw new RangeError(`Unsupported cron expression: "${cron}" — supported forms are a positive millisecond number, "* * * * *", or "*/N * * * *" with N > 0`);
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
39
|
* Scheduler adapter for Node.js using a setInterval-based approach.
|
|
@@ -53,6 +56,9 @@ export class NodeSchedulerAdapter {
|
|
|
53
56
|
this.drainTimeoutMs = drainTimeoutMs ?? 30_000;
|
|
54
57
|
}
|
|
55
58
|
register(cron, action) {
|
|
59
|
+
// Fail at registration time: an unsupported expression must never reach start(),
|
|
60
|
+
// where it would otherwise create a silently-wrong interval (task 0060 F7).
|
|
61
|
+
parseInterval(cron);
|
|
56
62
|
this.entries.push({ cron, action });
|
|
57
63
|
if (this.running) {
|
|
58
64
|
const last = this.entries[this.entries.length - 1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrap-handler.d.ts","sourceRoot":"","sources":["../../src/scheduler/wrap-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,EACvB,SAAS,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,IAAI,GAC7C,eAAe,
|
|
1
|
+
{"version":3,"file":"wrap-handler.d.ts","sourceRoot":"","sources":["../../src/scheduler/wrap-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,EACvB,SAAS,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,IAAI,GAC7C,eAAe,CA4BjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-infra",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
4
4
|
"description": "@gobing-ai/ts-infra — Infrastructure backbone: event bus, job queue, scheduler, telemetry, API client, and logging.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -78,12 +78,12 @@
|
|
|
78
78
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-infra-v<version> && git push --tags' && exit 1"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
81
|
+
"@gobing-ai/ts-utils": "^0.4.30",
|
|
82
82
|
"@logtape/logtape": "^2.0.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
86
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
85
|
+
"@gobing-ai/ts-db": "^0.4.30",
|
|
86
|
+
"@gobing-ai/ts-runtime": "^0.4.30",
|
|
87
87
|
"@opentelemetry/api": "^1.9.0",
|
|
88
88
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
89
89
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
@@ -116,8 +116,8 @@
|
|
|
116
116
|
}
|
|
117
117
|
},
|
|
118
118
|
"devDependencies": {
|
|
119
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
120
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
119
|
+
"@gobing-ai/ts-db": "^0.4.30",
|
|
120
|
+
"@gobing-ai/ts-runtime": "^0.4.30",
|
|
121
121
|
"@types/bun": "1.3.14",
|
|
122
122
|
"@opentelemetry/api": "^1.9.0",
|
|
123
123
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
package/src/api-client.ts
CHANGED
|
@@ -168,7 +168,15 @@ export class EventBus<TEvents extends EventMap> {
|
|
|
168
168
|
getEventbusEmitsTotal().add(1, { event: eventName });
|
|
169
169
|
if (errors > 0) getEventbusErrorsTotal().add(errors, { event: eventName });
|
|
170
170
|
|
|
171
|
-
this.publishEmitDone({
|
|
171
|
+
this.publishEmitDone({
|
|
172
|
+
event: eventName,
|
|
173
|
+
syncCount,
|
|
174
|
+
asyncCount,
|
|
175
|
+
emitDurationMs: durationMs,
|
|
176
|
+
errors,
|
|
177
|
+
detail,
|
|
178
|
+
severity: errors > 0 ? 'warning' : 'info',
|
|
179
|
+
});
|
|
172
180
|
|
|
173
181
|
if (syncCount === 0 && asyncCount === 0) {
|
|
174
182
|
busLogger().debug('emit with zero handlers', { event: eventName });
|
|
@@ -279,7 +287,7 @@ export class EventBus<TEvents extends EventMap> {
|
|
|
279
287
|
private publishEmitNoop(event: string): void {
|
|
280
288
|
if (this.lifecycleBus) {
|
|
281
289
|
try {
|
|
282
|
-
void this.lifecycleBus.emit('bus.emit.noop', { event });
|
|
290
|
+
void this.lifecycleBus.emit('bus.emit.noop', { event, severity: 'info' });
|
|
283
291
|
} catch {
|
|
284
292
|
// Swallow.
|
|
285
293
|
}
|
|
@@ -288,7 +296,7 @@ export class EventBus<TEvents extends EventMap> {
|
|
|
288
296
|
|
|
289
297
|
private publishHandlerError(event: string, mode: 'sync' | 'async', error: string): void {
|
|
290
298
|
if (this.lifecycleBus) {
|
|
291
|
-
const detail: HandlerErrorDetail = { event, mode, error };
|
|
299
|
+
const detail: HandlerErrorDetail = { event, mode, error, severity: 'error' };
|
|
292
300
|
try {
|
|
293
301
|
void this.lifecycleBus.emit('bus.handler.error', detail);
|
|
294
302
|
} catch {
|
|
@@ -299,7 +307,7 @@ export class EventBus<TEvents extends EventMap> {
|
|
|
299
307
|
|
|
300
308
|
private publishAsyncEnqueued(event: string, jobId: string, handlerCount: number): void {
|
|
301
309
|
if (this.lifecycleBus) {
|
|
302
|
-
const detail: AsyncEnqueuedDetail = { event, jobId, handlerCount };
|
|
310
|
+
const detail: AsyncEnqueuedDetail = { event, jobId, handlerCount, severity: 'info' };
|
|
303
311
|
try {
|
|
304
312
|
void this.lifecycleBus.emit('bus.handler.async.enqueued', detail);
|
|
305
313
|
} catch {
|
package/src/event-bus/types.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Event bus types — shared constraints for typed pub-sub.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { WithEventSeverity } from '@gobing-ai/ts-utils';
|
|
6
|
+
|
|
5
7
|
/** Type constraint for event maps — keys are event names, values are listener signatures. */
|
|
6
8
|
export type EventMap = Record<string, (...args: never[]) => void>;
|
|
7
9
|
|
|
@@ -33,7 +35,7 @@ export interface AsyncEventJobPayload {
|
|
|
33
35
|
// ── Lifecycle events ────────────────────────────────────────────────
|
|
34
36
|
|
|
35
37
|
/** Payload emitted on `bus.emit.done` after synchronous handlers complete. */
|
|
36
|
-
export interface EmitDoneDetail {
|
|
38
|
+
export interface EmitDoneDetail extends WithEventSeverity {
|
|
37
39
|
event: string;
|
|
38
40
|
syncCount: number;
|
|
39
41
|
asyncCount: number;
|
|
@@ -43,14 +45,14 @@ export interface EmitDoneDetail {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/** Payload emitted on `bus.handler.error` when a handler throws. */
|
|
46
|
-
export interface HandlerErrorDetail {
|
|
48
|
+
export interface HandlerErrorDetail extends WithEventSeverity {
|
|
47
49
|
event: string;
|
|
48
50
|
mode: 'sync' | 'async';
|
|
49
51
|
error: string;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/** Payload emitted on `bus.handler.async.enqueued` when async handlers are scheduled. */
|
|
53
|
-
export interface AsyncEnqueuedDetail {
|
|
55
|
+
export interface AsyncEnqueuedDetail extends WithEventSeverity {
|
|
54
56
|
event: string;
|
|
55
57
|
jobId: string;
|
|
56
58
|
handlerCount: number;
|
|
@@ -59,7 +61,7 @@ export interface AsyncEnqueuedDetail {
|
|
|
59
61
|
/** Strongly-typed lifecycle events emitted by the event bus. */
|
|
60
62
|
export type BusLifecycleEvents = {
|
|
61
63
|
'bus.emit.done': (detail: EmitDoneDetail) => void;
|
|
62
|
-
'bus.emit.noop': (detail: { event: string }) => void;
|
|
64
|
+
'bus.emit.noop': (detail: { event: string } & WithEventSeverity) => void;
|
|
63
65
|
'bus.handler.error': (detail: HandlerErrorDetail) => void;
|
|
64
66
|
'bus.handler.async.enqueued': (detail: AsyncEnqueuedDetail) => void;
|
|
65
67
|
};
|
package/src/events.ts
CHANGED
|
@@ -18,12 +18,15 @@
|
|
|
18
18
|
* app event map.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import type { EventSeverity, WithEventSeverity } from '@gobing-ai/ts-utils';
|
|
21
22
|
import type { QueueStats } from './job-queue/types';
|
|
22
23
|
|
|
24
|
+
export type { EventSeverity, WithEventSeverity };
|
|
25
|
+
|
|
23
26
|
// ── Detail payloads ────────────────────────────────────────────────────────
|
|
24
27
|
|
|
25
28
|
/** Payload for `db.connection.error`. */
|
|
26
|
-
export interface DbConnectionErrorDetail {
|
|
29
|
+
export interface DbConnectionErrorDetail extends WithEventSeverity {
|
|
27
30
|
/** Error message. */
|
|
28
31
|
error: string;
|
|
29
32
|
/** Adapter type (e.g. 'sqlite', 'd1'). */
|
|
@@ -39,7 +42,7 @@ export interface QueueJobRef {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/** Payload for `queue.job.enqueued` — a new job was added to the queue. */
|
|
42
|
-
export interface QueueJobEnqueuedDetail extends QueueJobRef {
|
|
45
|
+
export interface QueueJobEnqueuedDetail extends QueueJobRef, WithEventSeverity {
|
|
43
46
|
/** Epoch ms when the job was enqueued. */
|
|
44
47
|
enqueuedAt: number;
|
|
45
48
|
/** Max retry count from `EnqueueOptions.maxRetries`, when supplied. */
|
|
@@ -51,7 +54,7 @@ export interface QueueJobEnqueuedDetail extends QueueJobRef {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/** Payload for `queue.consumer.started` — the polling loop began. */
|
|
54
|
-
export interface QueueConsumerStartedDetail {
|
|
57
|
+
export interface QueueConsumerStartedDetail extends WithEventSeverity {
|
|
55
58
|
/** Epoch ms when the consumer was started. */
|
|
56
59
|
startedAt: number;
|
|
57
60
|
/** Polling interval in ms. */
|
|
@@ -65,7 +68,7 @@ export interface QueueConsumerStartedDetail {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
/** Payload for `queue.consumer.stopped` — the polling loop was halted. */
|
|
68
|
-
export interface QueueConsumerStoppedDetail {
|
|
71
|
+
export interface QueueConsumerStoppedDetail extends WithEventSeverity {
|
|
69
72
|
/** Epoch ms when `stop()` was called. */
|
|
70
73
|
stoppedAt: number;
|
|
71
74
|
/** Drain deadline in ms applied to in-flight handlers at stop time. */
|
|
@@ -77,7 +80,7 @@ export interface QueueConsumerStoppedDetail {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/** Payload for `queue.job.completed` — a job ran to success. */
|
|
80
|
-
export interface QueueJobCompletedDetail extends QueueJobRef {
|
|
83
|
+
export interface QueueJobCompletedDetail extends QueueJobRef, WithEventSeverity {
|
|
81
84
|
/** Handler wall-clock duration in ms. */
|
|
82
85
|
durationMs: number;
|
|
83
86
|
/** Attempts counter on the job row at success (0 on the first successful run). */
|
|
@@ -85,7 +88,7 @@ export interface QueueJobCompletedDetail extends QueueJobRef {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/** Payload for `queue.job.failed` — a job exhausted retries. */
|
|
88
|
-
export interface QueueJobFailedDetail extends QueueJobRef {
|
|
91
|
+
export interface QueueJobFailedDetail extends QueueJobRef, WithEventSeverity {
|
|
89
92
|
/** Error message from the final attempt. */
|
|
90
93
|
error: string;
|
|
91
94
|
/** 1-based attempt number of the failure. */
|
|
@@ -97,7 +100,7 @@ export interface QueueJobFailedDetail extends QueueJobRef {
|
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
/** Payload for `queue.job.retrying` — a job will be retried after backoff. */
|
|
100
|
-
export interface QueueJobRetryingDetail extends QueueJobRef {
|
|
103
|
+
export interface QueueJobRetryingDetail extends QueueJobRef, WithEventSeverity {
|
|
101
104
|
/** 1-based attempt number of the failure that triggers this retry. */
|
|
102
105
|
attempt: number;
|
|
103
106
|
/** Maximum retry count configured for the job. */
|
|
@@ -109,7 +112,7 @@ export interface QueueJobRetryingDetail extends QueueJobRef {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/** Payload for `scheduler.job.executed`. */
|
|
112
|
-
export interface SchedulerJobExecutedDetail {
|
|
115
|
+
export interface SchedulerJobExecutedDetail extends WithEventSeverity {
|
|
113
116
|
/** Job name. */
|
|
114
117
|
name: string;
|
|
115
118
|
/** Wall-clock duration in milliseconds. */
|
|
@@ -119,7 +122,7 @@ export interface SchedulerJobExecutedDetail {
|
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
/** Payload for `api.request.error`. */
|
|
122
|
-
export interface ApiRequestErrorDetail {
|
|
125
|
+
export interface ApiRequestErrorDetail extends WithEventSeverity {
|
|
123
126
|
url: string;
|
|
124
127
|
method: string;
|
|
125
128
|
/** HTTP status code, if a response was received. */
|
|
@@ -152,7 +155,7 @@ export type QueueEvents = {
|
|
|
152
155
|
/** A job will be retried after backoff. */
|
|
153
156
|
'queue.job.retrying': (detail: QueueJobRetryingDetail) => void;
|
|
154
157
|
/** Queue depth snapshot emitted on a poll cycle. */
|
|
155
|
-
'queue.stats': (detail: QueueStats) => void;
|
|
158
|
+
'queue.stats': (detail: QueueStats & WithEventSeverity) => void;
|
|
156
159
|
};
|
|
157
160
|
|
|
158
161
|
/** Scheduler events emitted by scheduler adapters and the handler wrapper. */
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export type {
|
|
|
29
29
|
ApiRequestErrorDetail,
|
|
30
30
|
DbConnectionErrorDetail,
|
|
31
31
|
DbEvents,
|
|
32
|
+
EventSeverity,
|
|
32
33
|
InfraEvents,
|
|
33
34
|
QueueConsumerStartedDetail,
|
|
34
35
|
QueueConsumerStoppedDetail,
|
|
@@ -40,6 +41,7 @@ export type {
|
|
|
40
41
|
QueueJobRetryingDetail,
|
|
41
42
|
SchedulerEvents,
|
|
42
43
|
SchedulerJobExecutedDetail,
|
|
44
|
+
WithEventSeverity,
|
|
43
45
|
} from './events';
|
|
44
46
|
|
|
45
47
|
export type {
|
|
@@ -61,7 +61,7 @@ export class DBJobQueue<T = unknown> implements JobQueue<T> {
|
|
|
61
61
|
* fields only when supplied. Shared by single and batch enqueue so both emit one shape.
|
|
62
62
|
*/
|
|
63
63
|
function enqueuedDetail(jobId: string, type: string, options: EnqueueOptions | undefined): QueueJobEnqueuedDetail {
|
|
64
|
-
const detail: QueueJobEnqueuedDetail = { jobId, type, enqueuedAt: Date.now() };
|
|
64
|
+
const detail: QueueJobEnqueuedDetail = { jobId, type, enqueuedAt: Date.now(), severity: 'info' };
|
|
65
65
|
if (options && options.maxRetries !== undefined) detail.maxRetries = options.maxRetries;
|
|
66
66
|
if (options && options.delay !== undefined) detail.delayMs = options.delay;
|
|
67
67
|
if (options && options.ttlMs !== undefined) detail.ttlMs = options.ttlMs;
|
|
@@ -112,6 +112,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
112
112
|
batchSize: this.batchSize,
|
|
113
113
|
maxConcurrency: this.maxConcurrency,
|
|
114
114
|
visibilityTimeout: this.visibilityTimeout,
|
|
115
|
+
severity: 'info',
|
|
115
116
|
});
|
|
116
117
|
}
|
|
117
118
|
|
|
@@ -139,11 +140,13 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
139
140
|
await sleep(10);
|
|
140
141
|
}
|
|
141
142
|
if (wasRunning) {
|
|
143
|
+
const drained = this.inFlight === 0;
|
|
142
144
|
const detail: QueueConsumerStoppedDetail = {
|
|
143
145
|
stoppedAt: Date.now(),
|
|
144
146
|
drainTimeoutMs: this.drainTimeoutMs,
|
|
145
147
|
inFlightAtStop: this.inFlight,
|
|
146
|
-
drained
|
|
148
|
+
drained,
|
|
149
|
+
severity: drained ? 'info' : 'warning',
|
|
147
150
|
};
|
|
148
151
|
await this.events?.emit('queue.consumer.stopped', detail);
|
|
149
152
|
}
|
|
@@ -244,6 +247,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
244
247
|
type: job.type,
|
|
245
248
|
durationMs: Number.isFinite(durationMs) ? durationMs : 0,
|
|
246
249
|
attempt: job.attempts,
|
|
250
|
+
severity: 'info',
|
|
247
251
|
};
|
|
248
252
|
await this.events?.emit('queue.job.completed', completed);
|
|
249
253
|
} catch (error) {
|
|
@@ -271,6 +275,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
271
275
|
attempt: attempts,
|
|
272
276
|
maxRetries: job.maxRetries,
|
|
273
277
|
durationMs,
|
|
278
|
+
severity: 'error',
|
|
274
279
|
};
|
|
275
280
|
await this.events?.emit('queue.job.failed', failed);
|
|
276
281
|
return;
|
|
@@ -286,6 +291,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
286
291
|
maxRetries: job.maxRetries,
|
|
287
292
|
nextRetryAt,
|
|
288
293
|
error: message,
|
|
294
|
+
severity: 'warning',
|
|
289
295
|
};
|
|
290
296
|
await this.events?.emit('queue.job.retrying', retrying);
|
|
291
297
|
}
|
package/src/scheduler/action.ts
CHANGED
|
@@ -110,7 +110,7 @@ export class QueueStatsAction implements SchedulerAction {
|
|
|
110
110
|
const dao = await this.daoProvider();
|
|
111
111
|
const stats = await dao.getStats();
|
|
112
112
|
logger.info('Queue stats snapshot', { action: this.name, ...stats });
|
|
113
|
-
await this.systemBus?.emit('queue.stats', stats);
|
|
113
|
+
await this.systemBus?.emit('queue.stats', { ...stats, severity: 'info' });
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
package/src/scheduler/node.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { settleWithin } from '../internals/drain';
|
|
7
|
-
import { getLogger } from '../logger';
|
|
8
7
|
import {
|
|
9
8
|
getSchedulerJobDuration,
|
|
10
9
|
getSchedulerJobExecutedTotal,
|
|
@@ -23,21 +22,28 @@ function parseInterval(cron: string): number {
|
|
|
23
22
|
if (num > 0) return num;
|
|
24
23
|
} else {
|
|
25
24
|
const parts = trimmed.split(/\s+/);
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// Only the documented 5-field forms are every-N-minutes: "* * * * *" and
|
|
26
|
+
// "*/N * * * *". A first-field wildcard alone ("* 3 * * *") is real cron
|
|
27
|
+
// and must not silently fire every 60s (task 0060 F7).
|
|
28
|
+
const restWild =
|
|
29
|
+
parts.length === 5 && parts[1] === '*' && parts[2] === '*' && parts[3] === '*' && parts[4] === '*';
|
|
30
|
+
if (restWild && parts[0] === '*') {
|
|
31
|
+
return 60_000;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
const match =
|
|
34
|
+
const nField = restWild ? parts[0] : parts.length === 1 ? parts[0] : undefined;
|
|
35
|
+
const match = nField?.match(/^\*\/(\d+)$/);
|
|
32
36
|
if (match && Number(match[1]) > 0) {
|
|
33
37
|
return Number(match[1]) * 60_000;
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
// Real cron field expressions ("0 3 * * *") are NOT supported — running
|
|
38
|
-
// them every minute
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
// Real cron field expressions ("0 3 * * *") are NOT supported — silently running
|
|
42
|
+
// them every minute would badly misfire. 0032 made that a warn; task 0060 F7 makes
|
|
43
|
+
// it a hard error so an unsupported schedule can never fire at the wrong cadence.
|
|
44
|
+
throw new RangeError(
|
|
45
|
+
`Unsupported cron expression: "${cron}" — supported forms are a positive millisecond number, "* * * * *", or "*/N * * * *" with N > 0`,
|
|
46
|
+
);
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
interface ScheduledEntry {
|
|
@@ -81,6 +87,9 @@ export class NodeSchedulerAdapter implements SchedulerAdapter {
|
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
register(cron: string, action: ScheduledAction): void {
|
|
90
|
+
// Fail at registration time: an unsupported expression must never reach start(),
|
|
91
|
+
// where it would otherwise create a silently-wrong interval (task 0060 F7).
|
|
92
|
+
parseInterval(cron);
|
|
84
93
|
this.entries.push({ cron, action });
|
|
85
94
|
if (this.running) {
|
|
86
95
|
const last = this.entries[this.entries.length - 1];
|