@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 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
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
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;IAUxB;;;;OAIG;YACW,UAAU;YAkGV,OAAO;IAmCrB;;;;;;;OAOG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA+C3G,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvD,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIzE,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAGnE"}
@@ -112,6 +112,7 @@ export class APIClient {
112
112
  method,
113
113
  ...(status !== undefined ? { status } : {}),
114
114
  error,
115
+ severity: 'error',
115
116
  });
116
117
  }
117
118
  /**
@@ -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;IAoF7F,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"}
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({ event: eventName, syncCount, asyncCount, emitDurationMs: durationMs, errors, detail });
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,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"}
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 = {
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
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';
@@ -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,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"}
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;IAatB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCrB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAWF,IAAI;YAgBJ,UAAU;YA6CV,WAAW;CAmC5B"}
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: this.inFlight === 0,
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
  }
@@ -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;AASH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAoCjE,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;IAU/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,OAAO,CAAC,UAAU;YAiBJ,gBAAgB;CAYjC"}
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"}
@@ -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
- if (parts.length === 5 && parts[0] === '*') {
22
- return 60_000; // every minute
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
- // */N pattern
25
- const match = parts[0]?.match(/^\*\/(\d+)$/);
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 silently would badly misfire, so warn on fallback.
32
- getLogger('scheduler.node').warn('Unsupported cron expression falling back to a 60s interval', { cron });
33
- return 60_000;
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,CA2BjB"}
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"}
@@ -38,6 +38,7 @@ export function wrapScheduledHandler(name, action, systemBus) {
38
38
  name,
39
39
  durationMs,
40
40
  ...(execError !== undefined ? { error: execError } : {}),
41
+ severity: execError !== undefined ? 'error' : 'info',
41
42
  });
42
43
  }
43
44
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-infra",
3
- "version": "0.4.28",
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.28",
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.28",
86
- "@gobing-ai/ts-runtime": "^0.4.28",
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.28",
120
- "@gobing-ai/ts-runtime": "^0.4.28",
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
@@ -176,6 +176,7 @@ export class APIClient {
176
176
  method,
177
177
  ...(status !== undefined ? { status } : {}),
178
178
  error,
179
+ severity: 'error',
179
180
  });
180
181
  }
181
182
 
@@ -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({ event: eventName, syncCount, asyncCount, emitDurationMs: durationMs, errors, detail });
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 {
@@ -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: this.inFlight === 0,
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
  }
@@ -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
 
@@ -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
- if (parts.length === 5 && parts[0] === '*') {
27
- return 60_000; // every minute
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
- // */N pattern
31
- const match = parts[0]?.match(/^\*\/(\d+)$/);
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 silently would badly misfire, so warn on fallback.
39
- getLogger('scheduler.node').warn('Unsupported cron expression falling back to a 60s interval', { cron });
40
- return 60_000;
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];
@@ -47,6 +47,7 @@ export function wrapScheduledHandler(
47
47
  name,
48
48
  durationMs,
49
49
  ...(execError !== undefined ? { error: execError } : {}),
50
+ severity: execError !== undefined ? 'error' : 'info',
50
51
  });
51
52
  }
52
53
  });