@gobing-ai/ts-infra 0.4.13 → 0.4.15

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
@@ -247,6 +247,8 @@ const queue = new DBJobQueue<{ to: string; subject: string }>(dao);
247
247
  await queue.enqueue(
248
248
  'send-email',
249
249
  { to: 'alice@example.com', subject: 'Welcome' },
250
+ // maxRetries is a total attempt budget, not retries after the first:
251
+ // 5 means up to 5 attempts, and `maxRetries: 1` runs the job once with no retry.
250
252
  { maxRetries: 5, delay: 1_000, ttlMs: 86_400_000 },
251
253
  );
252
254
 
@@ -271,6 +273,8 @@ const processed = await consumer.processOnce();
271
273
 
272
274
  The consumer claims ready jobs, resets stuck processing jobs after the visibility timeout, retries failed jobs with exponential backoff, and marks expired jobs failed through `QueueJobDao`. Corrupt payloads are failed individually without rejecting the batch. Poll-cycle errors are logged and retried on the next cycle — a single DAO hiccup will not crash the process.
273
275
 
276
+ `stop()` drains gracefully: it stops polling, then waits for work already in flight — including a poll cycle that has claimed nothing yet — before resolving. The wait is capped by `drainTimeoutMs` (default 30s) so a stuck handler cannot block shutdown; when the cap is hit, `queue.consumer.stopped` reports `drained: false` with the outstanding `inFlightAtStop` count.
277
+
274
278
  **Queue lifecycle events** are opt-in through the injected `EventBus`:
275
279
 
276
280
  ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAgIlD"}
1
+ {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAuIlD"}
@@ -169,12 +169,19 @@ export async function runNodeApplication(options) {
169
169
  const logFilePath = yamlBootstrap.logging?.filePath;
170
170
  const loggingConfig = typeof logFilePath === 'string' ? { ...loggingOpts, fileSink: createFileSink(logFilePath) } : loggingOpts;
171
171
  // ── Scheduler adapter ───────────────────────────────────────────────
172
+ // Honour a caller-supplied `SchedulerOptions.adapter` (documented injection
173
+ // point, application/types.ts:82) instead of unconditionally overwriting
174
+ // it. Only default-construct a NodeSchedulerAdapter when none was provided.
175
+ // `drainTimeoutMs` (ADR-024) is reachable by passing a pre-built adapter;
176
+ // the auto-wired default keeps the 30000 ms bound (CHANGELOG.md:16).
172
177
  const schedulerConfig = {};
173
- const rawSched = { ...schedulerOpts };
174
- if (rawSched.enabled === true) {
178
+ if (schedulerOpts.enabled === true) {
175
179
  schedulerConfig.enabled = true;
176
180
  schedulerConfig.autoStart = schedulerOpts.autoStart;
177
- schedulerConfig.adapter = new NodeSchedulerAdapter();
181
+ schedulerConfig.adapter = schedulerOpts.adapter ?? new NodeSchedulerAdapter();
182
+ if (schedulerOpts.entries) {
183
+ schedulerConfig.entries = schedulerOpts.entries;
184
+ }
178
185
  }
179
186
  // ── Node-owned plugins ──────────────────────────────────────────────
180
187
  const plugins = [];
package/dist/events.d.ts CHANGED
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Infrastructure-level event definitions for ts-infra observability.
3
3
  *
4
- * Mirrors the package-local pattern in `@gobing-ai/ts-ai-runner` (`events.ts`):
5
- * these maps define the infra-level event *contract*. ts-infra itself currently
6
- * emits only `queue.stats` (`QueueStatsAction`) and `scheduler.job.executed`
7
- * (`wrapScheduledHandler`); the remaining events are contracts for the consuming
8
- * app or higher-level wiring to emit on the same bus. Application-domain events
9
- * (history import, HTTP server, …) belong to the consuming app, not this
10
- * library. Process events belong to `@gobing-ai/ts-runtime` (the owner of
11
- * `ProcessExecutor`) and are intentionally not re-exported here.
4
+ * These maps define the infra-level event *contract* for the consumers of
5
+ * `@gobing-ai/ts-infra`. ts-infra emits `queue.*` (DBJobQueue / DBQueueConsumer),
6
+ * `scheduler.job.executed` (`wrapScheduledHandler`), and `db.connection.error`
7
+ * (DB wiring). Application-domain events (history import, HTTP server, …) belong
8
+ * to the consuming app, not this library. Process events belong to
9
+ * `@gobing-ai/ts-runtime` (the owner of `ProcessExecutor`) and are intentionally
10
+ * not re-exported here.
11
+ *
12
+ * **Metadata-only invariant:** every `queue.*` detail object is correlator-grade —
13
+ * it carries job identity, timing, and retry counters only, and NEVER embeds the
14
+ * job business payload `T` (which may contain prompts, tokens, PII, or large
15
+ * blobs). Inspect job bodies via the Jobs DAO when needed.
12
16
  *
13
17
  * Consume via `EventBus<InfraEvents>` or compose individual maps into a wider
14
18
  * app event map.
@@ -21,21 +25,76 @@ export interface DbConnectionErrorDetail {
21
25
  /** Adapter type (e.g. 'sqlite', 'd1'). */
22
26
  adapter: string;
23
27
  }
24
- /** Payload for `queue.job.failed` a job exhausted retries. */
25
- export interface QueueJobFailedDetail {
28
+ /** Shared job identity correlators present on every `queue.job.*` event. */
29
+ export interface QueueJobRef {
30
+ /** Job id. */
26
31
  jobId: string;
32
+ /** Job type (the handler registration key). */
27
33
  type: string;
34
+ }
35
+ /** Payload for `queue.job.enqueued` — a new job was added to the queue. */
36
+ export interface QueueJobEnqueuedDetail extends QueueJobRef {
37
+ /** Epoch ms when the job was enqueued. */
38
+ enqueuedAt: number;
39
+ /** Max retry count from `EnqueueOptions.maxRetries`, when supplied. */
40
+ maxRetries?: number;
41
+ /** Requested delay before the job becomes ready (ms), from `EnqueueOptions.delay`. */
42
+ delayMs?: number;
43
+ /** Job TTL in ms, from `EnqueueOptions.ttlMs`. */
44
+ ttlMs?: number;
45
+ }
46
+ /** Payload for `queue.consumer.started` — the polling loop began. */
47
+ export interface QueueConsumerStartedDetail {
48
+ /** Epoch ms when the consumer was started. */
49
+ startedAt: number;
50
+ /** Polling interval in ms. */
51
+ pollInterval: number;
52
+ /** Claim batch size per poll cycle. */
53
+ batchSize: number;
54
+ /** Maximum concurrent in-flight handlers. */
55
+ maxConcurrency: number;
56
+ /** Visibility-timeout window in ms. */
57
+ visibilityTimeout: number;
58
+ }
59
+ /** Payload for `queue.consumer.stopped` — the polling loop was halted. */
60
+ export interface QueueConsumerStoppedDetail {
61
+ /** Epoch ms when `stop()` was called. */
62
+ stoppedAt: number;
63
+ /** Drain deadline in ms applied to in-flight handlers at stop time. */
64
+ drainTimeoutMs: number;
65
+ /** In-flight handler count observed at stop time. */
66
+ inFlightAtStop: number;
67
+ /** `true` when all in-flight handlers completed within the drain deadline. */
68
+ drained: boolean;
69
+ }
70
+ /** Payload for `queue.job.completed` — a job ran to success. */
71
+ export interface QueueJobCompletedDetail extends QueueJobRef {
72
+ /** Handler wall-clock duration in ms. */
73
+ durationMs: number;
74
+ /** Attempts counter on the job row at success (0 on the first successful run). */
75
+ attempt: number;
76
+ }
77
+ /** Payload for `queue.job.failed` — a job exhausted retries. */
78
+ export interface QueueJobFailedDetail extends QueueJobRef {
79
+ /** Error message from the final attempt. */
28
80
  error: string;
29
- /** Attempt number (0-indexed, incremented after each failure). */
81
+ /** 1-based attempt number of the failure. */
30
82
  attempt: number;
83
+ /** Maximum retry count configured for the job. */
84
+ maxRetries: number;
85
+ /** Handler wall-clock duration in ms, when measured at the failing attempt. */
86
+ durationMs?: number;
31
87
  }
32
88
  /** Payload for `queue.job.retrying` — a job will be retried after backoff. */
33
- export interface QueueJobRetryingDetail {
34
- jobId: string;
35
- type: string;
89
+ export interface QueueJobRetryingDetail extends QueueJobRef {
90
+ /** 1-based attempt number of the failure that triggers this retry. */
36
91
  attempt: number;
92
+ /** Maximum retry count configured for the job. */
93
+ maxRetries: number;
37
94
  /** When the next retry will fire (epoch ms). */
38
95
  nextRetryAt: number;
96
+ /** Error message that caused this retry. */
97
+ error: string;
39
98
  }
40
99
  /** Payload for `scheduler.job.executed`. */
41
100
  export interface SchedulerJobExecutedDetail {
@@ -64,19 +123,13 @@ export type DbEvents = {
64
123
  /** Job-queue lifecycle events emitted by the queue and its consumer. */
65
124
  export type QueueEvents = {
66
125
  /** A new job was enqueued. */
67
- 'queue.job.enqueued': (detail: {
68
- jobId: string;
69
- type: string;
70
- }) => void;
126
+ 'queue.job.enqueued': (detail: QueueJobEnqueuedDetail) => void;
71
127
  /** Consumer polling loop started. */
72
- 'queue.consumer.started': () => void;
73
- /** Consumer stopped. */
74
- 'queue.consumer.stopped': () => void;
128
+ 'queue.consumer.started': (detail: QueueConsumerStartedDetail) => void;
129
+ /** Consumer polling loop stopped. */
130
+ 'queue.consumer.stopped': (detail: QueueConsumerStoppedDetail) => void;
75
131
  /** A job completed successfully. */
76
- 'queue.job.completed': (detail: {
77
- jobId: string;
78
- type: string;
79
- }) => void;
132
+ 'queue.job.completed': (detail: QueueJobCompletedDetail) => void;
80
133
  /** A job exhausted retries and is permanently failed. */
81
134
  'queue.job.failed': (detail: QueueJobFailedDetail) => void;
82
135
  /** A job will be retried after backoff. */
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD,yCAAyC;AACzC,MAAM,WAAW,uBAAuB;IACpC,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,0BAA0B;IACvC,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAID,mEAAmE;AACnE,MAAM,MAAM,QAAQ,GAAG;IACnB,uCAAuC;IACvC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,iCAAiC;IACjC,qBAAqB,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;IACtB,8BAA8B;IAC9B,oBAAoB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,qCAAqC;IACrC,wBAAwB,EAAE,MAAM,IAAI,CAAC;IACrC,wBAAwB;IACxB,wBAAwB,EAAE,MAAM,IAAI,CAAC;IACrC,oCAAoC;IACpC,qBAAqB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACzE,yDAAyD;IACzD,kBAAkB,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC3D,2CAA2C;IAC3C,oBAAoB,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC/D,oDAAoD;IACpD,aAAa,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG;IAC1B,yDAAyD;IACzD,wBAAwB,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC1E,CAAC;AAEF,yBAAyB;AACzB,MAAM,MAAM,eAAe,GAAG;IAC1B,wDAAwD;IACxD,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
package/dist/events.js CHANGED
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Infrastructure-level event definitions for ts-infra observability.
3
3
  *
4
- * Mirrors the package-local pattern in `@gobing-ai/ts-ai-runner` (`events.ts`):
5
- * these maps define the infra-level event *contract*. ts-infra itself currently
6
- * emits only `queue.stats` (`QueueStatsAction`) and `scheduler.job.executed`
7
- * (`wrapScheduledHandler`); the remaining events are contracts for the consuming
8
- * app or higher-level wiring to emit on the same bus. Application-domain events
9
- * (history import, HTTP server, …) belong to the consuming app, not this
10
- * library. Process events belong to `@gobing-ai/ts-runtime` (the owner of
11
- * `ProcessExecutor`) and are intentionally not re-exported here.
4
+ * These maps define the infra-level event *contract* for the consumers of
5
+ * `@gobing-ai/ts-infra`. ts-infra emits `queue.*` (DBJobQueue / DBQueueConsumer),
6
+ * `scheduler.job.executed` (`wrapScheduledHandler`), and `db.connection.error`
7
+ * (DB wiring). Application-domain events (history import, HTTP server, …) belong
8
+ * to the consuming app, not this library. Process events belong to
9
+ * `@gobing-ai/ts-runtime` (the owner of `ProcessExecutor`) and are intentionally
10
+ * not re-exported here.
11
+ *
12
+ * **Metadata-only invariant:** every `queue.*` detail object is correlator-grade —
13
+ * it carries job identity, timing, and retry counters only, and NEVER embeds the
14
+ * job business payload `T` (which may contain prompts, tokens, PII, or large
15
+ * blobs). Inspect job bodies via the Jobs DAO when needed.
12
16
  *
13
17
  * Consume via `EventBus<InfraEvents>` or compose individual maps into a wider
14
18
  * app event map.
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, QueueEvents, QueueJobFailedDetail, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, } from './events';
3
+ export type { ApiClientEvents, ApiRequestErrorDetail, DbConnectionErrorDetail, DbEvents, InfraEvents, QueueConsumerStartedDetail, QueueConsumerStoppedDetail, QueueEvents, QueueJobCompletedDetail, QueueJobEnqueuedDetail, QueueJobFailedDetail, QueueJobRef, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, } from './events';
4
4
  export type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig, QueueStats, } from './job-queue/index';
5
5
  export { getLogger, type InitLoggerOptions, initializeLogger, type Logger, type LogLevel, setLoggerMuted, } from './logger';
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,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,0BAA0B,GAC7B,MAAM,UAAU,CAAC;AAElB,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,SAAS,EACT,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,cAAc,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACH,cAAc,EACd,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Internal drain primitives shared by adapters that bound a shutdown wait on
3
+ * in-flight work (DB queue consumer, Node scheduler).
4
+ *
5
+ * WHY: two adapters had near-identical "await this in-flight thing but not
6
+ * forever" logic. Centralizing the bound keeps the two shapes from drifting
7
+ * and documents the contract once. Not re-exported from the package barrel —
8
+ * consumers depend on adapter behaviour, not this helper.
9
+ */
10
+ /**
11
+ * Resolve when `promise` settles or when `deadline` passes, whichever comes first.
12
+ *
13
+ * Never rejects: a rejected promise ends the wait the same way a fulfilled one
14
+ * does. `deadline` is an absolute epoch-ms timestamp (not a duration) so a single
15
+ * bound can be shared across several awaited promises without compounding.
16
+ */
17
+ export declare function settleWithin(promise: Promise<void>, deadline: number): Promise<void>;
18
+ //# sourceMappingURL=drain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../src/internals/drain.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpF"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Internal drain primitives shared by adapters that bound a shutdown wait on
3
+ * in-flight work (DB queue consumer, Node scheduler).
4
+ *
5
+ * WHY: two adapters had near-identical "await this in-flight thing but not
6
+ * forever" logic. Centralizing the bound keeps the two shapes from drifting
7
+ * and documents the contract once. Not re-exported from the package barrel —
8
+ * consumers depend on adapter behaviour, not this helper.
9
+ */
10
+ /**
11
+ * Resolve when `promise` settles or when `deadline` passes, whichever comes first.
12
+ *
13
+ * Never rejects: a rejected promise ends the wait the same way a fulfilled one
14
+ * does. `deadline` is an absolute epoch-ms timestamp (not a duration) so a single
15
+ * bound can be shared across several awaited promises without compounding.
16
+ */
17
+ export function settleWithin(promise, deadline) {
18
+ const remaining = deadline - Date.now();
19
+ if (remaining <= 0)
20
+ return Promise.resolve();
21
+ const { promise: settled, resolve } = Promise.withResolvers();
22
+ const timer = setTimeout(resolve, remaining);
23
+ const done = () => {
24
+ clearTimeout(timer);
25
+ resolve();
26
+ };
27
+ promise.then(done, done);
28
+ return settled;
29
+ }
@@ -29,6 +29,7 @@ export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
29
29
  private timer;
30
30
  private running;
31
31
  private inFlight;
32
+ private pollPromise;
32
33
  constructor(dao: QueueJobDao, config?: QueueConsumerConfig);
33
34
  register(type: string, handler: JobHandler<T>): void;
34
35
  start(): Promise<void>;
@@ -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,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAS7C,OAAO,KAAK,EAAE,cAAc,EAAO,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7G,kFAAkF;AAClF,qBAAa,UAAU,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAEnD,QAAQ,CAAC,GAAG,EAAE,WAAW;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADf,GAAG,EAAE,WAAW,EACR,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,YAAA;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAW3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAED,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAe7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;gBAGA,GAAG,EAAE,WAAW,EACjC,MAAM,GAAE,mBAAwB;IAYpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAMF,IAAI;YAgBJ,UAAU;YAqCV,WAAW;CA4B5B"}
1
+ {"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,3 +1,4 @@
1
+ import { settleWithin } from '../internals/drain.js';
1
2
  import { getLogger } from '../logger.js';
2
3
  import { getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, } from '../telemetry/metrics.js';
3
4
  import { addSpanAttributes, traceAsync } from '../telemetry/tracing.js';
@@ -18,7 +19,7 @@ export class DBJobQueue {
18
19
  async enqueue(type, payload, options) {
19
20
  const id = await this.dao.enqueue(type, payload, options);
20
21
  getQueueJobEnqueuedTotal().add(1, { type });
21
- await this.events?.emit('queue.job.enqueued', { jobId: id, type });
22
+ await this.events?.emit('queue.job.enqueued', enqueuedDetail(id, type, options));
22
23
  return id;
23
24
  }
24
25
  async enqueueBatch(jobs) {
@@ -26,7 +27,8 @@ export class DBJobQueue {
26
27
  getQueueJobEnqueuedTotal().add(jobs.length);
27
28
  if (this.events) {
28
29
  for (const [index, jobId] of ids.entries()) {
29
- await this.events.emit('queue.job.enqueued', { jobId, type: jobs[index]?.type ?? 'unknown' });
30
+ const job = jobs[index];
31
+ await this.events.emit('queue.job.enqueued', enqueuedDetail(jobId, job?.type ?? 'unknown', job));
30
32
  }
31
33
  }
32
34
  return ids;
@@ -35,6 +37,20 @@ export class DBJobQueue {
35
37
  return this.dao.getStats();
36
38
  }
37
39
  }
40
+ /**
41
+ * Build the `queue.job.enqueued` correlator detail, filling optional retry/delay/TTL
42
+ * fields only when supplied. Shared by single and batch enqueue so both emit one shape.
43
+ */
44
+ function enqueuedDetail(jobId, type, options) {
45
+ const detail = { jobId, type, enqueuedAt: Date.now() };
46
+ if (options && options.maxRetries !== undefined)
47
+ detail.maxRetries = options.maxRetries;
48
+ if (options && options.delay !== undefined)
49
+ detail.delayMs = options.delay;
50
+ if (options && options.ttlMs !== undefined)
51
+ detail.ttlMs = options.ttlMs;
52
+ return detail;
53
+ }
38
54
  /** DB-backed queue consumer with polling, retry, and visibility-timeout handling. */
39
55
  export class DBQueueConsumer {
40
56
  dao;
@@ -50,6 +66,7 @@ export class DBQueueConsumer {
50
66
  timer = null;
51
67
  running = false;
52
68
  inFlight = 0;
69
+ pollPromise = null;
53
70
  constructor(dao, config = {}) {
54
71
  this.dao = dao;
55
72
  this.pollInterval = nonNegativeFiniteConfig('pollInterval', config.pollInterval ?? 1_000);
@@ -69,7 +86,13 @@ export class DBQueueConsumer {
69
86
  return;
70
87
  this.running = true;
71
88
  this.schedule(0);
72
- await this.events?.emit('queue.consumer.started');
89
+ await this.events?.emit('queue.consumer.started', {
90
+ startedAt: Date.now(),
91
+ pollInterval: this.pollInterval,
92
+ batchSize: this.batchSize,
93
+ maxConcurrency: this.maxConcurrency,
94
+ visibilityTimeout: this.visibilityTimeout,
95
+ });
73
96
  }
74
97
  async stop() {
75
98
  const wasRunning = this.running;
@@ -79,11 +102,27 @@ export class DBQueueConsumer {
79
102
  this.timer = null;
80
103
  }
81
104
  const deadline = Date.now() + this.drainTimeoutMs;
105
+ // Wait for an already-running poll cycle before consulting `inFlight`.
106
+ // `inFlight` is only incremented once claimReady() has resolved, so a stop()
107
+ // landing between the timer firing and that increment would see 0, exit the
108
+ // drain loop immediately, and report `drained: true` while the cycle went on
109
+ // to claim and process jobs after stop() resolved.
110
+ const pending = this.pollPromise;
111
+ if (pending !== null) {
112
+ await settleWithin(pending, deadline);
113
+ }
82
114
  while (this.inFlight > 0 && Date.now() < deadline) {
83
115
  await sleep(10);
84
116
  }
85
- if (wasRunning)
86
- await this.events?.emit('queue.consumer.stopped');
117
+ if (wasRunning) {
118
+ const detail = {
119
+ stoppedAt: Date.now(),
120
+ drainTimeoutMs: this.drainTimeoutMs,
121
+ inFlightAtStop: this.inFlight,
122
+ drained: this.inFlight === 0,
123
+ };
124
+ await this.events?.emit('queue.consumer.stopped', detail);
125
+ }
87
126
  }
88
127
  async stats() {
89
128
  return this.dao.getStats();
@@ -114,7 +153,13 @@ export class DBQueueConsumer {
114
153
  }
115
154
  schedule(delay) {
116
155
  this.timer = setTimeout(() => {
117
- void this.poll();
156
+ // Retained so stop() can await the cycle it interrupts; poll() contains its
157
+ // own errors, so this promise settles rather than rejecting.
158
+ const cycle = this.poll().finally(() => {
159
+ if (this.pollPromise === cycle)
160
+ this.pollPromise = null;
161
+ });
162
+ this.pollPromise = cycle;
118
163
  }, delay);
119
164
  }
120
165
  async poll() {
@@ -144,7 +189,7 @@ export class DBQueueConsumer {
144
189
  catch (error) {
145
190
  // Corrupt payload — the job can never parse; route it through the
146
191
  // retry/fail path instead of rejecting the whole batch.
147
- await this.failOrRetry(record, error);
192
+ await this.failOrRetry(record, error, 0);
148
193
  return;
149
194
  }
150
195
  return traceAsync('queue.job.process', async () => {
@@ -155,46 +200,60 @@ export class DBQueueConsumer {
155
200
  });
156
201
  const handler = this.handlers.get(job.type);
157
202
  if (handler === undefined) {
158
- await this.failOrRetry(job, new Error(`No handler registered for job type "${job.type}"`));
203
+ await this.failOrRetry(job, new Error(`No handler registered for job type "${job.type}"`), 0);
159
204
  return;
160
205
  }
161
206
  const startMs = performance.now();
162
207
  try {
163
208
  await handler(job);
164
209
  await this.dao.markCompleted(job.id);
210
+ const durationMs = performance.now() - startMs;
165
211
  getQueueJobCompletedTotal().add(1, { type: job.type });
166
- getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
167
- await this.events?.emit('queue.job.completed', { jobId: job.id, type: job.type });
212
+ getQueueJobProcessingDuration().record(durationMs, { type: job.type });
213
+ const completed = {
214
+ jobId: job.id,
215
+ type: job.type,
216
+ durationMs: Number.isFinite(durationMs) ? durationMs : 0,
217
+ attempt: job.attempts,
218
+ };
219
+ await this.events?.emit('queue.job.completed', completed);
168
220
  }
169
221
  catch (error) {
170
- getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
171
- await this.failOrRetry(job, error);
222
+ const durationMs = performance.now() - startMs;
223
+ getQueueJobProcessingDuration().record(durationMs, { type: job.type });
224
+ await this.failOrRetry(job, error, Number.isFinite(durationMs) ? durationMs : 0);
172
225
  }
173
226
  });
174
227
  }
175
- async failOrRetry(job, error) {
228
+ async failOrRetry(job, error, durationMs) {
176
229
  const attempts = job.attempts + 1;
177
230
  const message = error instanceof Error ? error.message : String(error);
178
231
  if (attempts >= job.maxRetries) {
179
232
  await this.dao.markFailed(job.id, attempts, message);
180
233
  getQueueJobFailedTotal().add(1, { type: job.type });
181
- await this.events?.emit('queue.job.failed', {
234
+ const failed = {
182
235
  jobId: job.id,
183
236
  type: job.type,
184
237
  error: message,
185
238
  attempt: attempts,
186
- });
239
+ maxRetries: job.maxRetries,
240
+ durationMs,
241
+ };
242
+ await this.events?.emit('queue.job.failed', failed);
187
243
  return;
188
244
  }
189
245
  const delay = Math.min(this.maxDelay, this.baseDelay * 2 ** Math.max(0, attempts - 1));
190
246
  const nextRetryAt = Date.now() + delay;
191
247
  await this.dao.markForRetry(job.id, attempts, message, nextRetryAt);
192
- await this.events?.emit('queue.job.retrying', {
248
+ const retrying = {
193
249
  jobId: job.id,
194
250
  type: job.type,
195
251
  attempt: attempts,
252
+ maxRetries: job.maxRetries,
196
253
  nextRetryAt,
197
- });
254
+ error: message,
255
+ };
256
+ await this.events?.emit('queue.job.retrying', retrying);
198
257
  }
199
258
  }
200
259
  function toJob(record) {
@@ -213,7 +272,9 @@ function toJob(record) {
213
272
  };
214
273
  }
215
274
  function sleep(ms) {
216
- return new Promise((resolve) => setTimeout(resolve, ms));
275
+ const { promise, resolve } = Promise.withResolvers();
276
+ setTimeout(resolve, ms);
277
+ return promise;
217
278
  }
218
279
  function positiveIntegerConfig(name, value) {
219
280
  if (!Number.isInteger(value) || value <= 0) {
@@ -13,6 +13,7 @@ export interface Job<T = unknown> {
13
13
  payload: T;
14
14
  status: 'pending' | 'processing' | 'completed' | 'failed';
15
15
  attempts: number;
16
+ /** Total attempts allowed, not retries *after* the first — a job fails once `attempts >= maxRetries`. */
16
17
  maxRetries: number;
17
18
  createdAt: number;
18
19
  updatedAt: number;
@@ -22,6 +23,7 @@ export interface Job<T = unknown> {
22
23
  }
23
24
  /** Options for enqueuing a job: retry policy, delay, and TTL. */
24
25
  export interface EnqueueOptions {
26
+ /** Total attempts allowed (default 3) — `maxRetries: 1` runs the job once with no retry. */
25
27
  maxRetries?: number;
26
28
  delay?: number;
27
29
  ttlMs?: number;
@@ -52,11 +54,14 @@ export interface QueueConsumerConfig {
52
54
  visibilityTimeout?: number;
53
55
  baseDelay?: number;
54
56
  maxDelay?: number;
57
+ /** Upper bound (ms, default 30_000) on how long `stop()` waits for in-flight work to drain. */
55
58
  drainTimeoutMs?: number;
56
59
  /**
57
- * Optional bus for queue lifecycle events: `queue.consumer.started` /
58
- * `queue.consumer.stopped` and `queue.job.completed` / `queue.job.failed` /
59
- * `queue.job.retrying`. Omitting it leaves the consumer silent (default).
60
+ * Optional bus for queue lifecycle events with correlator-grade detail payloads:
61
+ * `queue.consumer.started` / `queue.consumer.stopped` (config snapshot + drain
62
+ * outcome), `queue.job.enqueued` / `queue.job.completed` / `queue.job.failed` /
63
+ * `queue.job.retrying`. Details carry job identity, timing, and retry counters
64
+ * only — never the business payload `T`. Omitting it leaves the consumer silent.
60
65
  */
61
66
  events?: EventBus<QueueEvents>;
62
67
  }
@@ -64,6 +69,11 @@ export interface QueueConsumerConfig {
64
69
  export interface QueueConsumer<T = unknown> {
65
70
  register(type: string, handler: JobHandler<T>): void;
66
71
  start(): Promise<void>;
72
+ /**
73
+ * Stop polling and drain work already in flight, including a poll cycle that has
74
+ * claimed nothing yet. Resolves once the drain completes or `drainTimeoutMs`
75
+ * elapses — whichever comes first, so a hung handler cannot block shutdown.
76
+ */
67
77
  stop(): Promise<void>;
68
78
  stats(): Promise<QueueStats>;
69
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,OAAO;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,OAAO;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC"}
@@ -18,6 +18,13 @@ export declare class CloudflareSchedulerAdapter implements SchedulerAdapter {
18
18
  constructor();
19
19
  register(cron: string, action: ScheduledAction): void;
20
20
  start(): Promise<void>;
21
+ /**
22
+ * No-op drain. Cloudflare Workers fire Cron Triggers externally — this adapter
23
+ * owns no timer to cancel — and `handleScheduledEvent` already bounds each
24
+ * in-flight action via `ctx.waitUntil()`, the runtime's own drain. So there is
25
+ * nothing for stop() to await here; clearing registrations is the full contract
26
+ * (ADR-024).
27
+ */
21
28
  stop(): Promise<void>;
22
29
  /**
23
30
  * Handle a Cloudflare Workers Cron Trigger event.
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/scheduler/cloudflare.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE,UAAU,gBAAgB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,UAAU,cAAc;IACpB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAI9D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAI/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;CAmB3E"}
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/scheduler/cloudflare.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE,UAAU,gBAAgB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,UAAU,cAAc;IACpB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAI9D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAI/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;;;;;OAMG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;CAmB3E"}
@@ -19,6 +19,13 @@ export class CloudflareSchedulerAdapter {
19
19
  // Cloudflare Workers use cron triggers defined in wrangler.toml.
20
20
  // The `scheduled()` handler should call `handleScheduledEvent()`.
21
21
  }
22
+ /**
23
+ * No-op drain. Cloudflare Workers fire Cron Triggers externally — this adapter
24
+ * owns no timer to cancel — and `handleScheduledEvent` already bounds each
25
+ * in-flight action via `ctx.waitUntil()`, the runtime's own drain. So there is
26
+ * nothing for stop() to await here; clearing registrations is the full contract
27
+ * (ADR-024).
28
+ */
22
29
  async stop() {
23
30
  this.entries.clear();
24
31
  }