@happyvertical/jobs 0.80.0 → 0.80.2

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/dist/index.js CHANGED
@@ -1,252 +1,229 @@
1
+ import { a as custom, c as linear, i as DEFAULT_RETRY_STRATEGY, l as noRetry, n as priorityToNumber, o as exponential, s as fromConfig, t as BaseJobStore } from "./chunks/base-store-DIasEzL0.js";
1
2
  import { BullJobStore } from "./adapters/bull.js";
2
3
  import { BullMQJobStore } from "./adapters/bullmq.js";
3
4
  import { CloudTasksJobStore } from "./adapters/cloud-tasks.js";
4
5
  import { PostgresJobStore } from "./adapters/postgres.js";
5
6
  import { SqliteJobStore } from "./adapters/sqlite.js";
6
7
  import { SQSJobStore } from "./adapters/sqs.js";
7
- import { f as fromConfig } from "./chunks/base-store-DlNksWvQ.js";
8
- import { B, D, c, e, l, n, p } from "./chunks/base-store-DlNksWvQ.js";
9
- import { EventEmitter } from "node:events";
10
8
  import { createId } from "@happyvertical/utils";
11
- const DEFAULT_CONFIG = {
12
- id: "",
13
- concurrency: 5,
14
- queues: ["default"],
15
- pollInterval: 1e3,
16
- heartbeatInterval: 3e4,
17
- shutdownTimeout: 3e4
9
+ import { EventEmitter } from "node:events";
10
+ //#region src/worker.ts
11
+ /**
12
+ * Default worker configuration
13
+ */
14
+ var DEFAULT_CONFIG = {
15
+ id: "",
16
+ concurrency: 5,
17
+ queues: ["default"],
18
+ pollInterval: 1e3,
19
+ heartbeatInterval: 3e4,
20
+ shutdownTimeout: 3e4
18
21
  };
19
- class JobWorker extends EventEmitter {
20
- id;
21
- store;
22
- handler;
23
- config;
24
- running = false;
25
- activeJobs = /* @__PURE__ */ new Map();
26
- pollTimer = null;
27
- heartbeatTimer = null;
28
- shutdownPromise = null;
29
- constructor(store, handler, config = {}) {
30
- super();
31
- this.store = store;
32
- this.handler = handler;
33
- this.config = {
34
- ...DEFAULT_CONFIG,
35
- ...config,
36
- id: config.id || `worker_${createId().slice(0, 8)}`
37
- };
38
- this.id = this.config.id;
39
- }
40
- /**
41
- * Start processing jobs
42
- */
43
- async start() {
44
- if (this.running) return;
45
- this.running = true;
46
- this.store.subscribe(async (event) => {
47
- if (event.type === "job.ready" && this.running) {
48
- await this.poll();
49
- }
50
- });
51
- this.startPolling();
52
- this.startHeartbeat();
53
- this.emit("worker:started");
54
- }
55
- /**
56
- * Stop processing jobs (graceful shutdown)
57
- */
58
- async stop() {
59
- if (!this.running) return;
60
- if (this.shutdownPromise) return this.shutdownPromise;
61
- this.running = false;
62
- if (this.pollTimer) {
63
- clearTimeout(this.pollTimer);
64
- this.pollTimer = null;
65
- }
66
- if (this.heartbeatTimer) {
67
- clearInterval(this.heartbeatTimer);
68
- this.heartbeatTimer = null;
69
- }
70
- this.shutdownPromise = this.waitForActiveJobs();
71
- try {
72
- await this.shutdownPromise;
73
- } finally {
74
- this.shutdownPromise = null;
75
- this.emit("worker:stopped");
76
- }
77
- }
78
- /**
79
- * Check if worker is running
80
- */
81
- isRunning() {
82
- return this.running;
83
- }
84
- /**
85
- * Get count of active jobs
86
- */
87
- activeJobCount() {
88
- return this.activeJobs.size;
89
- }
90
- /**
91
- * Start the polling loop
92
- */
93
- startPolling() {
94
- const scheduleNextPoll = () => {
95
- if (!this.running) return;
96
- if (!this.store.waitForUpdate) {
97
- this.pollTimer = setTimeout(poll, this.config.pollInterval);
98
- return;
99
- }
100
- this.pollTimer = setTimeout(() => {
101
- void waitThenPoll();
102
- }, 0);
103
- };
104
- const waitThenPoll = async () => {
105
- try {
106
- await this.store.waitForUpdate?.(this.config.pollInterval);
107
- } catch (error) {
108
- this.emit("worker:error", error);
109
- }
110
- if (this.running) {
111
- await poll();
112
- }
113
- };
114
- const poll = async () => {
115
- if (!this.running) return;
116
- try {
117
- await this.poll();
118
- } catch (error) {
119
- this.emit("worker:error", error);
120
- }
121
- scheduleNextPoll();
122
- };
123
- poll();
124
- }
125
- /**
126
- * Poll for and process jobs
127
- */
128
- async poll() {
129
- const available = this.config.concurrency - this.activeJobs.size;
130
- if (available <= 0) return;
131
- const jobs = await this.store.dequeue(
132
- this.config.queues,
133
- available,
134
- this.id
135
- );
136
- for (const job of jobs) {
137
- this.processJob(job);
138
- }
139
- }
140
- /**
141
- * Process a single job
142
- */
143
- async processJob(job) {
144
- this.activeJobs.set(job.id, job);
145
- this.emit("job:started", job);
146
- let timeoutId = null;
147
- try {
148
- const timeoutPromise = new Promise((_, reject) => {
149
- timeoutId = setTimeout(() => {
150
- reject(new Error(`Job timeout after ${job.timeout}ms`));
151
- }, job.timeout);
152
- });
153
- const result = await Promise.race([this.handler(job), timeoutPromise]);
154
- await this.store.update(job.id, {
155
- status: "completed",
156
- completedAt: /* @__PURE__ */ new Date(),
157
- resultPointer: result.resultPointer ?? null
158
- });
159
- this.emit("job:completed", job, result.result);
160
- } catch (error) {
161
- await this.handleJobError(job, error);
162
- } finally {
163
- if (timeoutId) {
164
- clearTimeout(timeoutId);
165
- }
166
- this.activeJobs.delete(job.id);
167
- }
168
- }
169
- /**
170
- * Handle job execution error
171
- */
172
- async handleJobError(job, error) {
173
- const strategy = fromConfig(job.retryStrategy);
174
- const decision = strategy.shouldRetry(job.attempts, error);
175
- if (decision.shouldRetry && job.attempts < job.maxAttempts) {
176
- const nextRunAt = new Date(Date.now() + decision.delay);
177
- await this.store.update(job.id, {
178
- status: "pending",
179
- lastError: error.message,
180
- runAt: nextRunAt,
181
- workerId: null,
182
- workerHeartbeat: null
183
- });
184
- this.emit("job:retrying", job, error, decision.delay);
185
- } else {
186
- await this.store.update(job.id, {
187
- status: "failed",
188
- completedAt: /* @__PURE__ */ new Date(),
189
- lastError: error.message
190
- });
191
- this.emit("job:failed", job, error);
192
- }
193
- }
194
- /**
195
- * Start heartbeat loop to keep jobs alive
196
- */
197
- startHeartbeat() {
198
- this.heartbeatTimer = setInterval(async () => {
199
- for (const [jobId] of this.activeJobs) {
200
- try {
201
- await this.store.heartbeat(jobId, this.id);
202
- } catch (error) {
203
- console.warn(`Heartbeat failed for job ${jobId}:`, error);
204
- }
205
- }
206
- }, this.config.heartbeatInterval);
207
- }
208
- /**
209
- * Wait for active jobs to complete with timeout
210
- */
211
- async waitForActiveJobs() {
212
- if (this.activeJobs.size === 0) return;
213
- return new Promise((resolve) => {
214
- const checkInterval = setInterval(() => {
215
- if (this.activeJobs.size === 0) {
216
- clearInterval(checkInterval);
217
- clearTimeout(timeout);
218
- resolve();
219
- }
220
- }, 100);
221
- const timeout = setTimeout(() => {
222
- clearInterval(checkInterval);
223
- console.warn(
224
- `Shutdown timeout: ${this.activeJobs.size} jobs still active`
225
- );
226
- resolve();
227
- }, this.config.shutdownTimeout);
228
- });
229
- }
230
- }
22
+ /**
23
+ * Job worker that processes jobs from a store
24
+ */
25
+ var JobWorker = class extends EventEmitter {
26
+ id;
27
+ store;
28
+ handler;
29
+ config;
30
+ running = false;
31
+ activeJobs = /* @__PURE__ */ new Map();
32
+ pollTimer = null;
33
+ heartbeatTimer = null;
34
+ shutdownPromise = null;
35
+ constructor(store, handler, config = {}) {
36
+ super();
37
+ this.store = store;
38
+ this.handler = handler;
39
+ this.config = {
40
+ ...DEFAULT_CONFIG,
41
+ ...config,
42
+ id: config.id || `worker_${createId().slice(0, 8)}`
43
+ };
44
+ this.id = this.config.id;
45
+ }
46
+ /**
47
+ * Start processing jobs
48
+ */
49
+ async start() {
50
+ if (this.running) return;
51
+ this.running = true;
52
+ this.store.subscribe(async (event) => {
53
+ if (event.type === "job.ready" && this.running) await this.poll();
54
+ });
55
+ this.startPolling();
56
+ this.startHeartbeat();
57
+ this.emit("worker:started");
58
+ }
59
+ /**
60
+ * Stop processing jobs (graceful shutdown)
61
+ */
62
+ async stop() {
63
+ if (!this.running) return;
64
+ if (this.shutdownPromise) return this.shutdownPromise;
65
+ this.running = false;
66
+ if (this.pollTimer) {
67
+ clearTimeout(this.pollTimer);
68
+ this.pollTimer = null;
69
+ }
70
+ if (this.heartbeatTimer) {
71
+ clearInterval(this.heartbeatTimer);
72
+ this.heartbeatTimer = null;
73
+ }
74
+ this.shutdownPromise = this.waitForActiveJobs();
75
+ try {
76
+ await this.shutdownPromise;
77
+ } finally {
78
+ this.shutdownPromise = null;
79
+ this.emit("worker:stopped");
80
+ }
81
+ }
82
+ /**
83
+ * Check if worker is running
84
+ */
85
+ isRunning() {
86
+ return this.running;
87
+ }
88
+ /**
89
+ * Get count of active jobs
90
+ */
91
+ activeJobCount() {
92
+ return this.activeJobs.size;
93
+ }
94
+ /**
95
+ * Start the polling loop
96
+ */
97
+ startPolling() {
98
+ const scheduleNextPoll = () => {
99
+ if (!this.running) return;
100
+ if (!this.store.waitForUpdate) {
101
+ this.pollTimer = setTimeout(poll, this.config.pollInterval);
102
+ return;
103
+ }
104
+ this.pollTimer = setTimeout(() => {
105
+ waitThenPoll();
106
+ }, 0);
107
+ };
108
+ const waitThenPoll = async () => {
109
+ try {
110
+ await this.store.waitForUpdate?.(this.config.pollInterval);
111
+ } catch (error) {
112
+ this.emit("worker:error", error);
113
+ }
114
+ if (this.running) await poll();
115
+ };
116
+ const poll = async () => {
117
+ if (!this.running) return;
118
+ try {
119
+ await this.poll();
120
+ } catch (error) {
121
+ this.emit("worker:error", error);
122
+ }
123
+ scheduleNextPoll();
124
+ };
125
+ poll();
126
+ }
127
+ /**
128
+ * Poll for and process jobs
129
+ */
130
+ async poll() {
131
+ const available = this.config.concurrency - this.activeJobs.size;
132
+ if (available <= 0) return;
133
+ const jobs = await this.store.dequeue(this.config.queues, available, this.id);
134
+ for (const job of jobs) this.processJob(job);
135
+ }
136
+ /**
137
+ * Process a single job
138
+ */
139
+ async processJob(job) {
140
+ this.activeJobs.set(job.id, job);
141
+ this.emit("job:started", job);
142
+ let timeoutId = null;
143
+ try {
144
+ const timeoutPromise = new Promise((_, reject) => {
145
+ timeoutId = setTimeout(() => {
146
+ reject(/* @__PURE__ */ new Error(`Job timeout after ${job.timeout}ms`));
147
+ }, job.timeout);
148
+ });
149
+ const result = await Promise.race([this.handler(job), timeoutPromise]);
150
+ await this.store.update(job.id, {
151
+ status: "completed",
152
+ completedAt: /* @__PURE__ */ new Date(),
153
+ resultPointer: result.resultPointer ?? null
154
+ });
155
+ this.emit("job:completed", job, result.result);
156
+ } catch (error) {
157
+ await this.handleJobError(job, error);
158
+ } finally {
159
+ if (timeoutId) clearTimeout(timeoutId);
160
+ this.activeJobs.delete(job.id);
161
+ }
162
+ }
163
+ /**
164
+ * Handle job execution error
165
+ */
166
+ async handleJobError(job, error) {
167
+ const decision = fromConfig(job.retryStrategy).shouldRetry(job.attempts, error);
168
+ if (decision.shouldRetry && job.attempts < job.maxAttempts) {
169
+ const nextRunAt = new Date(Date.now() + decision.delay);
170
+ await this.store.update(job.id, {
171
+ status: "pending",
172
+ lastError: error.message,
173
+ runAt: nextRunAt,
174
+ workerId: null,
175
+ workerHeartbeat: null
176
+ });
177
+ this.emit("job:retrying", job, error, decision.delay);
178
+ } else {
179
+ await this.store.update(job.id, {
180
+ status: "failed",
181
+ completedAt: /* @__PURE__ */ new Date(),
182
+ lastError: error.message
183
+ });
184
+ this.emit("job:failed", job, error);
185
+ }
186
+ }
187
+ /**
188
+ * Start heartbeat loop to keep jobs alive
189
+ */
190
+ startHeartbeat() {
191
+ this.heartbeatTimer = setInterval(async () => {
192
+ for (const [jobId] of this.activeJobs) try {
193
+ await this.store.heartbeat(jobId, this.id);
194
+ } catch (error) {
195
+ console.warn(`Heartbeat failed for job ${jobId}:`, error);
196
+ }
197
+ }, this.config.heartbeatInterval);
198
+ }
199
+ /**
200
+ * Wait for active jobs to complete with timeout
201
+ */
202
+ async waitForActiveJobs() {
203
+ if (this.activeJobs.size === 0) return;
204
+ return new Promise((resolve) => {
205
+ const checkInterval = setInterval(() => {
206
+ if (this.activeJobs.size === 0) {
207
+ clearInterval(checkInterval);
208
+ clearTimeout(timeout);
209
+ resolve();
210
+ }
211
+ }, 100);
212
+ const timeout = setTimeout(() => {
213
+ clearInterval(checkInterval);
214
+ console.warn(`Shutdown timeout: ${this.activeJobs.size} jobs still active`);
215
+ resolve();
216
+ }, this.config.shutdownTimeout);
217
+ });
218
+ }
219
+ };
220
+ /**
221
+ * Create a job worker
222
+ */
231
223
  function createWorker(store, handler, config) {
232
- return new JobWorker(store, handler, config);
224
+ return new JobWorker(store, handler, config);
233
225
  }
234
- export {
235
- B as BaseJobStore,
236
- BullJobStore,
237
- BullMQJobStore,
238
- CloudTasksJobStore,
239
- D as DEFAULT_RETRY_STRATEGY,
240
- JobWorker,
241
- PostgresJobStore,
242
- SQSJobStore,
243
- SqliteJobStore,
244
- createWorker,
245
- c as custom,
246
- e as exponential,
247
- fromConfig,
248
- l as linear,
249
- n as noRetry,
250
- p as priorityToNumber
251
- };
252
- //# sourceMappingURL=index.js.map
226
+ //#endregion
227
+ export { BaseJobStore, BullJobStore, BullMQJobStore, CloudTasksJobStore, DEFAULT_RETRY_STRATEGY, JobWorker, PostgresJobStore, SQSJobStore, SqliteJobStore, createWorker, custom, exponential, fromConfig, linear, noRetry, priorityToNumber };
228
+
229
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/worker.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { createId } from '@happyvertical/utils';\nimport { fromConfig } from './retry.js';\nimport type {\n Worker as IWorker,\n Job,\n JobHandler,\n JobStore,\n WorkerConfig,\n} from './types.js';\n\n/**\n * Worker events\n */\nexport interface WorkerEvents {\n 'job:started': (job: Job) => void;\n 'job:completed': (job: Job, result: unknown) => void;\n 'job:failed': (job: Job, error: Error) => void;\n 'job:retrying': (job: Job, error: Error, delay: number) => void;\n 'worker:started': () => void;\n 'worker:stopped': () => void;\n 'worker:error': (error: Error) => void;\n}\n\n/**\n * Default worker configuration\n */\nconst DEFAULT_CONFIG: Required<WorkerConfig> = {\n id: '',\n concurrency: 5,\n queues: ['default'],\n pollInterval: 1000,\n heartbeatInterval: 30000,\n shutdownTimeout: 30000,\n};\n\n/**\n * Job worker that processes jobs from a store\n */\nexport class JobWorker extends EventEmitter implements IWorker {\n readonly id: string;\n private readonly store: JobStore;\n private readonly handler: JobHandler;\n private readonly config: Required<WorkerConfig>;\n private running = false;\n private activeJobs = new Map<string, Job>();\n private pollTimer: NodeJS.Timeout | null = null;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private shutdownPromise: Promise<void> | null = null;\n\n constructor(store: JobStore, handler: JobHandler, config: WorkerConfig = {}) {\n super();\n this.store = store;\n this.handler = handler;\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n id: config.id || `worker_${createId().slice(0, 8)}`,\n };\n this.id = this.config.id;\n }\n\n /**\n * Start processing jobs\n */\n async start(): Promise<void> {\n if (this.running) return;\n\n this.running = true;\n\n // Subscribe to store events for push-based notifications\n this.store.subscribe(async (event) => {\n if (event.type === 'job.ready' && this.running) {\n // Immediately try to dequeue when a job becomes ready\n await this.poll();\n }\n });\n\n // Start polling loop\n this.startPolling();\n\n // Start heartbeat loop\n this.startHeartbeat();\n\n this.emit('worker:started');\n }\n\n /**\n * Stop processing jobs (graceful shutdown)\n */\n async stop(): Promise<void> {\n if (!this.running) return;\n if (this.shutdownPromise) return this.shutdownPromise;\n\n this.running = false;\n\n // Stop timers\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n\n // Wait for active jobs to complete (with timeout)\n this.shutdownPromise = this.waitForActiveJobs();\n\n try {\n await this.shutdownPromise;\n } finally {\n this.shutdownPromise = null;\n this.emit('worker:stopped');\n }\n }\n\n /**\n * Check if worker is running\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get count of active jobs\n */\n activeJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Start the polling loop\n */\n private startPolling(): void {\n const scheduleNextPoll = () => {\n if (!this.running) return;\n\n if (!this.store.waitForUpdate) {\n this.pollTimer = setTimeout(poll, this.config.pollInterval);\n return;\n }\n\n this.pollTimer = setTimeout(() => {\n void waitThenPoll();\n }, 0);\n };\n\n const waitThenPoll = async () => {\n try {\n await this.store.waitForUpdate?.(this.config.pollInterval);\n } catch (error) {\n this.emit('worker:error', error as Error);\n }\n\n if (this.running) {\n await poll();\n }\n };\n\n const poll = async () => {\n if (!this.running) return;\n\n try {\n await this.poll();\n } catch (error) {\n this.emit('worker:error', error as Error);\n }\n\n // Schedule next poll\n scheduleNextPoll();\n };\n\n // Start immediately\n poll();\n }\n\n /**\n * Poll for and process jobs\n */\n private async poll(): Promise<void> {\n // Calculate how many jobs we can take\n const available = this.config.concurrency - this.activeJobs.size;\n if (available <= 0) return;\n\n // Dequeue jobs\n const jobs = await this.store.dequeue(\n this.config.queues,\n available,\n this.id,\n );\n\n // Process each job concurrently\n for (const job of jobs) {\n this.processJob(job);\n }\n }\n\n /**\n * Process a single job\n */\n private async processJob(job: Job): Promise<void> {\n this.activeJobs.set(job.id, job);\n this.emit('job:started', job);\n\n let timeoutId: NodeJS.Timeout | null = null;\n\n try {\n // Set up timeout with cleanup capability\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`Job timeout after ${job.timeout}ms`));\n }, job.timeout);\n });\n\n // Execute handler with timeout\n const result = await Promise.race([this.handler(job), timeoutPromise]);\n\n // Job completed successfully\n await this.store.update(job.id, {\n status: 'completed',\n completedAt: new Date(),\n resultPointer: result.resultPointer ?? null,\n });\n\n this.emit('job:completed', job, result.result);\n } catch (error) {\n await this.handleJobError(job, error as Error);\n } finally {\n // Clean up timeout timer to prevent memory leak\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n this.activeJobs.delete(job.id);\n }\n }\n\n /**\n * Handle job execution error\n */\n private async handleJobError(job: Job, error: Error): Promise<void> {\n const strategy = fromConfig(job.retryStrategy);\n const decision = strategy.shouldRetry(job.attempts, error);\n\n if (decision.shouldRetry && job.attempts < job.maxAttempts) {\n // Schedule retry\n const nextRunAt = new Date(Date.now() + decision.delay);\n\n await this.store.update(job.id, {\n status: 'pending',\n lastError: error.message,\n runAt: nextRunAt,\n workerId: null,\n workerHeartbeat: null,\n });\n\n this.emit('job:retrying', job, error, decision.delay);\n } else {\n // Job failed permanently\n await this.store.update(job.id, {\n status: 'failed',\n completedAt: new Date(),\n lastError: error.message,\n });\n\n this.emit('job:failed', job, error);\n }\n }\n\n /**\n * Start heartbeat loop to keep jobs alive\n */\n private startHeartbeat(): void {\n this.heartbeatTimer = setInterval(async () => {\n for (const [jobId] of this.activeJobs) {\n try {\n await this.store.heartbeat(jobId, this.id);\n } catch (error) {\n // Log heartbeat errors but don't interrupt job processing\n console.warn(`Heartbeat failed for job ${jobId}:`, error);\n }\n }\n }, this.config.heartbeatInterval);\n }\n\n /**\n * Wait for active jobs to complete with timeout\n */\n private async waitForActiveJobs(): Promise<void> {\n if (this.activeJobs.size === 0) return;\n\n return new Promise((resolve) => {\n const checkInterval = setInterval(() => {\n if (this.activeJobs.size === 0) {\n clearInterval(checkInterval);\n clearTimeout(timeout);\n resolve();\n }\n }, 100);\n\n const timeout = setTimeout(() => {\n clearInterval(checkInterval);\n console.warn(\n `Shutdown timeout: ${this.activeJobs.size} jobs still active`,\n );\n resolve();\n }, this.config.shutdownTimeout);\n });\n }\n}\n\n/**\n * Create a job worker\n */\nexport function createWorker(\n store: JobStore,\n handler: JobHandler,\n config?: WorkerConfig,\n): JobWorker {\n return new JobWorker(store, handler, config);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/worker.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { createId } from '@happyvertical/utils';\nimport { fromConfig } from './retry.js';\nimport type {\n Worker as IWorker,\n Job,\n JobHandler,\n JobStore,\n WorkerConfig,\n} from './types.js';\n\n/**\n * Worker events\n */\nexport interface WorkerEvents {\n 'job:started': (job: Job) => void;\n 'job:completed': (job: Job, result: unknown) => void;\n 'job:failed': (job: Job, error: Error) => void;\n 'job:retrying': (job: Job, error: Error, delay: number) => void;\n 'worker:started': () => void;\n 'worker:stopped': () => void;\n 'worker:error': (error: Error) => void;\n}\n\n/**\n * Default worker configuration\n */\nconst DEFAULT_CONFIG: Required<WorkerConfig> = {\n id: '',\n concurrency: 5,\n queues: ['default'],\n pollInterval: 1000,\n heartbeatInterval: 30000,\n shutdownTimeout: 30000,\n};\n\n/**\n * Job worker that processes jobs from a store\n */\nexport class JobWorker extends EventEmitter implements IWorker {\n readonly id: string;\n private readonly store: JobStore;\n private readonly handler: JobHandler;\n private readonly config: Required<WorkerConfig>;\n private running = false;\n private activeJobs = new Map<string, Job>();\n private pollTimer: NodeJS.Timeout | null = null;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private shutdownPromise: Promise<void> | null = null;\n\n constructor(store: JobStore, handler: JobHandler, config: WorkerConfig = {}) {\n super();\n this.store = store;\n this.handler = handler;\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n id: config.id || `worker_${createId().slice(0, 8)}`,\n };\n this.id = this.config.id;\n }\n\n /**\n * Start processing jobs\n */\n async start(): Promise<void> {\n if (this.running) return;\n\n this.running = true;\n\n // Subscribe to store events for push-based notifications\n this.store.subscribe(async (event) => {\n if (event.type === 'job.ready' && this.running) {\n // Immediately try to dequeue when a job becomes ready\n await this.poll();\n }\n });\n\n // Start polling loop\n this.startPolling();\n\n // Start heartbeat loop\n this.startHeartbeat();\n\n this.emit('worker:started');\n }\n\n /**\n * Stop processing jobs (graceful shutdown)\n */\n async stop(): Promise<void> {\n if (!this.running) return;\n if (this.shutdownPromise) return this.shutdownPromise;\n\n this.running = false;\n\n // Stop timers\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n\n // Wait for active jobs to complete (with timeout)\n this.shutdownPromise = this.waitForActiveJobs();\n\n try {\n await this.shutdownPromise;\n } finally {\n this.shutdownPromise = null;\n this.emit('worker:stopped');\n }\n }\n\n /**\n * Check if worker is running\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get count of active jobs\n */\n activeJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Start the polling loop\n */\n private startPolling(): void {\n const scheduleNextPoll = () => {\n if (!this.running) return;\n\n if (!this.store.waitForUpdate) {\n this.pollTimer = setTimeout(poll, this.config.pollInterval);\n return;\n }\n\n this.pollTimer = setTimeout(() => {\n void waitThenPoll();\n }, 0);\n };\n\n const waitThenPoll = async () => {\n try {\n await this.store.waitForUpdate?.(this.config.pollInterval);\n } catch (error) {\n this.emit('worker:error', error as Error);\n }\n\n if (this.running) {\n await poll();\n }\n };\n\n const poll = async () => {\n if (!this.running) return;\n\n try {\n await this.poll();\n } catch (error) {\n this.emit('worker:error', error as Error);\n }\n\n // Schedule next poll\n scheduleNextPoll();\n };\n\n // Start immediately\n poll();\n }\n\n /**\n * Poll for and process jobs\n */\n private async poll(): Promise<void> {\n // Calculate how many jobs we can take\n const available = this.config.concurrency - this.activeJobs.size;\n if (available <= 0) return;\n\n // Dequeue jobs\n const jobs = await this.store.dequeue(\n this.config.queues,\n available,\n this.id,\n );\n\n // Process each job concurrently\n for (const job of jobs) {\n this.processJob(job);\n }\n }\n\n /**\n * Process a single job\n */\n private async processJob(job: Job): Promise<void> {\n this.activeJobs.set(job.id, job);\n this.emit('job:started', job);\n\n let timeoutId: NodeJS.Timeout | null = null;\n\n try {\n // Set up timeout with cleanup capability\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`Job timeout after ${job.timeout}ms`));\n }, job.timeout);\n });\n\n // Execute handler with timeout\n const result = await Promise.race([this.handler(job), timeoutPromise]);\n\n // Job completed successfully\n await this.store.update(job.id, {\n status: 'completed',\n completedAt: new Date(),\n resultPointer: result.resultPointer ?? null,\n });\n\n this.emit('job:completed', job, result.result);\n } catch (error) {\n await this.handleJobError(job, error as Error);\n } finally {\n // Clean up timeout timer to prevent memory leak\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n this.activeJobs.delete(job.id);\n }\n }\n\n /**\n * Handle job execution error\n */\n private async handleJobError(job: Job, error: Error): Promise<void> {\n const strategy = fromConfig(job.retryStrategy);\n const decision = strategy.shouldRetry(job.attempts, error);\n\n if (decision.shouldRetry && job.attempts < job.maxAttempts) {\n // Schedule retry\n const nextRunAt = new Date(Date.now() + decision.delay);\n\n await this.store.update(job.id, {\n status: 'pending',\n lastError: error.message,\n runAt: nextRunAt,\n workerId: null,\n workerHeartbeat: null,\n });\n\n this.emit('job:retrying', job, error, decision.delay);\n } else {\n // Job failed permanently\n await this.store.update(job.id, {\n status: 'failed',\n completedAt: new Date(),\n lastError: error.message,\n });\n\n this.emit('job:failed', job, error);\n }\n }\n\n /**\n * Start heartbeat loop to keep jobs alive\n */\n private startHeartbeat(): void {\n this.heartbeatTimer = setInterval(async () => {\n for (const [jobId] of this.activeJobs) {\n try {\n await this.store.heartbeat(jobId, this.id);\n } catch (error) {\n // Log heartbeat errors but don't interrupt job processing\n console.warn(`Heartbeat failed for job ${jobId}:`, error);\n }\n }\n }, this.config.heartbeatInterval);\n }\n\n /**\n * Wait for active jobs to complete with timeout\n */\n private async waitForActiveJobs(): Promise<void> {\n if (this.activeJobs.size === 0) return;\n\n return new Promise((resolve) => {\n const checkInterval = setInterval(() => {\n if (this.activeJobs.size === 0) {\n clearInterval(checkInterval);\n clearTimeout(timeout);\n resolve();\n }\n }, 100);\n\n const timeout = setTimeout(() => {\n clearInterval(checkInterval);\n console.warn(\n `Shutdown timeout: ${this.activeJobs.size} jobs still active`,\n );\n resolve();\n }, this.config.shutdownTimeout);\n });\n }\n}\n\n/**\n * Create a job worker\n */\nexport function createWorker(\n store: JobStore,\n handler: JobHandler,\n config?: WorkerConfig,\n): JobWorker {\n return new JobWorker(store, handler, config);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/jobs",
3
- "version": "0.80.0",
3
+ "version": "0.80.2",
4
4
  "description": "Job queue abstraction with multiple backend adapters (SQLite, PostgreSQL, Bull, SQS)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -68,8 +68,8 @@
68
68
  "author": "willgriffin@gmail.com",
69
69
  "license": "ISC",
70
70
  "dependencies": {
71
- "@happyvertical/sql": "0.80.0",
72
- "@happyvertical/utils": "0.80.0"
71
+ "@happyvertical/sql": "0.80.2",
72
+ "@happyvertical/utils": "0.80.2"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@aws-sdk/client-sqs": "^3.1034.0",
@@ -77,11 +77,11 @@
77
77
  "@types/bull": "^4.10.4",
78
78
  "@types/node": "25.0.10",
79
79
  "bull": "^4.16.5",
80
- "bullmq": "^5.76.0",
81
- "typescript": "^5.9.3",
82
- "vite": "7.3.2",
80
+ "bullmq": "^5.80.4",
81
+ "typescript": "5.9.3",
82
+ "vite": "8.1.4",
83
83
  "vite-plugin-dts": "4.5.4",
84
- "vitest": "^4.1.5"
84
+ "vitest": "4.1.10"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "@aws-sdk/client-sqs": ">=3.1034.0",