@igniter-js/jobs 0.1.0

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.
@@ -0,0 +1,571 @@
1
+ 'use strict';
2
+
3
+ var core = require('@igniter-js/core');
4
+
5
+ // src/errors/igniter-jobs.error.ts
6
+ var IgniterJobsError = class _IgniterJobsError extends core.IgniterError {
7
+ constructor(options) {
8
+ super({
9
+ code: options.code,
10
+ message: options.message,
11
+ statusCode: options.statusCode ?? 500,
12
+ causer: "@igniter-js/jobs",
13
+ cause: options.cause,
14
+ details: options.details,
15
+ logger: options.logger
16
+ });
17
+ this.code = options.code;
18
+ this.details = options.details;
19
+ this.name = "IgniterJobsError";
20
+ if (Error.captureStackTrace) {
21
+ Error.captureStackTrace(this, _IgniterJobsError);
22
+ }
23
+ }
24
+ /**
25
+ * Convert error to a plain object for serialization.
26
+ */
27
+ toJSON() {
28
+ return {
29
+ name: this.name,
30
+ code: this.code,
31
+ message: this.message,
32
+ statusCode: this.statusCode,
33
+ details: this.details,
34
+ stack: this.stack
35
+ };
36
+ }
37
+ };
38
+
39
+ // src/adapters/memory.adapter.ts
40
+ var MemoryAdapter = class _MemoryAdapter {
41
+ constructor() {
42
+ this.queues = /* @__PURE__ */ new Map();
43
+ this.eventHandlers = /* @__PURE__ */ new Map();
44
+ this.workers = /* @__PURE__ */ new Map();
45
+ this.processingInterval = null;
46
+ }
47
+ /**
48
+ * Create a new memory adapter.
49
+ */
50
+ static create() {
51
+ return new _MemoryAdapter();
52
+ }
53
+ /**
54
+ * Get the underlying client (null for memory adapter).
55
+ */
56
+ get client() {
57
+ return null;
58
+ }
59
+ /**
60
+ * Get or create a queue.
61
+ */
62
+ getOrCreateQueue(name) {
63
+ if (!this.queues.has(name)) {
64
+ this.queues.set(name, {
65
+ name,
66
+ isPaused: false,
67
+ jobs: /* @__PURE__ */ new Map(),
68
+ pausedJobTypes: /* @__PURE__ */ new Set()
69
+ });
70
+ }
71
+ return this.queues.get(name);
72
+ }
73
+ /**
74
+ * Generate a unique job ID.
75
+ */
76
+ generateJobId() {
77
+ return `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
78
+ }
79
+ /**
80
+ * Emit an event to subscribers.
81
+ */
82
+ async emitEvent(type, data) {
83
+ for (const [pattern, handlers] of this.eventHandlers) {
84
+ if (this.matchesPattern(pattern, type)) {
85
+ for (const handler of handlers) {
86
+ try {
87
+ await handler({
88
+ type,
89
+ data,
90
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
91
+ });
92
+ } catch {
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Check if a pattern matches an event type.
100
+ */
101
+ matchesPattern(pattern, eventType) {
102
+ if (pattern === "*") return true;
103
+ const regex = new RegExp(
104
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
105
+ );
106
+ return regex.test(eventType);
107
+ }
108
+ // ==========================================
109
+ // JOB OPERATIONS
110
+ // ==========================================
111
+ async dispatch(params) {
112
+ const queue = this.getOrCreateQueue(params.queue);
113
+ const jobId = params.jobId || this.generateJobId();
114
+ const job = {
115
+ id: jobId,
116
+ name: params.name,
117
+ queue: params.queue,
118
+ data: params.data,
119
+ state: params.delay ? "delayed" : "waiting",
120
+ progress: 0,
121
+ attempts: 0,
122
+ maxAttempts: params.attempts ?? 3,
123
+ timestamp: Date.now(),
124
+ delay: params.delay,
125
+ priority: params.priority ?? 0,
126
+ scope: params.scope,
127
+ actor: params.actor,
128
+ logs: [],
129
+ scheduledAt: params.delay ? Date.now() + params.delay : void 0
130
+ };
131
+ queue.jobs.set(jobId, job);
132
+ await this.emitEvent(`${params.queue}:${params.name}:enqueued`, {
133
+ jobId,
134
+ name: params.name,
135
+ queue: params.queue
136
+ });
137
+ this.startProcessing();
138
+ return jobId;
139
+ }
140
+ async schedule(params) {
141
+ const delay = params.at ? params.at.getTime() - Date.now() : params.delay ?? 0;
142
+ return this.dispatch({
143
+ ...params,
144
+ delay
145
+ });
146
+ }
147
+ async getJob(queue, jobId) {
148
+ const q = this.getOrCreateQueue(queue);
149
+ const job = q.jobs.get(jobId);
150
+ if (!job) return null;
151
+ return {
152
+ id: job.id,
153
+ name: job.name,
154
+ queue: job.queue,
155
+ state: job.state,
156
+ data: job.data,
157
+ result: job.result,
158
+ error: job.error,
159
+ progress: job.progress,
160
+ attempts: job.attempts,
161
+ timestamp: job.timestamp,
162
+ processedOn: job.processedOn,
163
+ finishedOn: job.finishedOn,
164
+ delay: job.delay,
165
+ priority: job.priority,
166
+ scope: job.scope,
167
+ actor: job.actor
168
+ };
169
+ }
170
+ async getJobState(queue, jobId) {
171
+ const q = this.getOrCreateQueue(queue);
172
+ const job = q.jobs.get(jobId);
173
+ return job?.state ?? null;
174
+ }
175
+ async getJobProgress(queue, jobId) {
176
+ const q = this.getOrCreateQueue(queue);
177
+ const job = q.jobs.get(jobId);
178
+ return job?.progress ?? 0;
179
+ }
180
+ async getJobLogs(queue, jobId) {
181
+ const q = this.getOrCreateQueue(queue);
182
+ const job = q.jobs.get(jobId);
183
+ return job?.logs ?? [];
184
+ }
185
+ async retryJob(queue, jobId) {
186
+ const q = this.getOrCreateQueue(queue);
187
+ const job = q.jobs.get(jobId);
188
+ if (!job) {
189
+ throw new IgniterJobsError({
190
+ code: "JOBS_JOB_NOT_FOUND",
191
+ message: `Job "${jobId}" not found`,
192
+ statusCode: 404
193
+ });
194
+ }
195
+ job.state = "waiting";
196
+ job.error = void 0;
197
+ job.attempts = 0;
198
+ }
199
+ async removeJob(queue, jobId) {
200
+ const q = this.getOrCreateQueue(queue);
201
+ q.jobs.delete(jobId);
202
+ }
203
+ async promoteJob(queue, jobId) {
204
+ const q = this.getOrCreateQueue(queue);
205
+ const job = q.jobs.get(jobId);
206
+ if (!job) {
207
+ throw new IgniterJobsError({
208
+ code: "JOBS_JOB_NOT_FOUND",
209
+ message: `Job "${jobId}" not found`,
210
+ statusCode: 404
211
+ });
212
+ }
213
+ if (job.state === "delayed") {
214
+ job.state = "waiting";
215
+ job.scheduledAt = void 0;
216
+ job.delay = void 0;
217
+ }
218
+ }
219
+ async moveJob(queue, jobId, state, reason) {
220
+ const q = this.getOrCreateQueue(queue);
221
+ const job = q.jobs.get(jobId);
222
+ if (!job) {
223
+ throw new IgniterJobsError({
224
+ code: "JOBS_JOB_NOT_FOUND",
225
+ message: `Job "${jobId}" not found`,
226
+ statusCode: 404
227
+ });
228
+ }
229
+ job.state = state;
230
+ job.finishedOn = Date.now();
231
+ if (state === "failed") {
232
+ job.error = reason;
233
+ } else {
234
+ job.result = reason;
235
+ }
236
+ }
237
+ async retryJobs(queue, jobIds) {
238
+ await Promise.all(jobIds.map((id) => this.retryJob(queue, id)));
239
+ }
240
+ async removeJobs(queue, jobIds) {
241
+ await Promise.all(jobIds.map((id) => this.removeJob(queue, id)));
242
+ }
243
+ // ==========================================
244
+ // QUEUE OPERATIONS
245
+ // ==========================================
246
+ async getQueue(queue) {
247
+ const q = this.getOrCreateQueue(queue);
248
+ const counts = await this.getJobCounts(queue);
249
+ return {
250
+ name: queue,
251
+ isPaused: q.isPaused,
252
+ jobCounts: counts
253
+ };
254
+ }
255
+ async pauseQueue(queue) {
256
+ const q = this.getOrCreateQueue(queue);
257
+ q.isPaused = true;
258
+ }
259
+ async resumeQueue(queue) {
260
+ const q = this.getOrCreateQueue(queue);
261
+ q.isPaused = false;
262
+ }
263
+ async drainQueue(queue) {
264
+ const q = this.getOrCreateQueue(queue);
265
+ let count = 0;
266
+ for (const [id, job] of q.jobs) {
267
+ if (job.state === "waiting" || job.state === "delayed") {
268
+ q.jobs.delete(id);
269
+ count++;
270
+ }
271
+ }
272
+ return count;
273
+ }
274
+ async cleanQueue(queue, options) {
275
+ const q = this.getOrCreateQueue(queue);
276
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
277
+ const now = Date.now();
278
+ let count = 0;
279
+ let removed = 0;
280
+ for (const [id, job] of q.jobs) {
281
+ if (options.limit && removed >= options.limit) break;
282
+ if (statuses.includes(job.state)) {
283
+ const age = now - job.timestamp;
284
+ if (!options.olderThan || age >= options.olderThan) {
285
+ q.jobs.delete(id);
286
+ removed++;
287
+ count++;
288
+ }
289
+ }
290
+ }
291
+ return count;
292
+ }
293
+ async obliterateQueue(queue, options) {
294
+ this.queues.delete(queue);
295
+ }
296
+ async retryAllFailed(queue) {
297
+ const q = this.getOrCreateQueue(queue);
298
+ let count = 0;
299
+ for (const job of q.jobs.values()) {
300
+ if (job.state === "failed") {
301
+ job.state = "waiting";
302
+ job.error = void 0;
303
+ job.attempts = 0;
304
+ count++;
305
+ }
306
+ }
307
+ return count;
308
+ }
309
+ async getJobCounts(queue) {
310
+ const q = this.getOrCreateQueue(queue);
311
+ const counts = {
312
+ waiting: 0,
313
+ active: 0,
314
+ completed: 0,
315
+ failed: 0,
316
+ delayed: 0,
317
+ paused: 0
318
+ };
319
+ for (const job of q.jobs.values()) {
320
+ counts[job.state]++;
321
+ }
322
+ return counts;
323
+ }
324
+ async listJobs(queue, options) {
325
+ const q = this.getOrCreateQueue(queue);
326
+ const results = [];
327
+ const statuses = options?.status || ["waiting", "active", "completed", "failed", "delayed", "paused"];
328
+ for (const job of q.jobs.values()) {
329
+ if (statuses.includes(job.state)) {
330
+ results.push({
331
+ id: job.id,
332
+ name: job.name,
333
+ queue: job.queue,
334
+ state: job.state,
335
+ data: job.data,
336
+ result: job.result,
337
+ error: job.error,
338
+ progress: job.progress,
339
+ attempts: job.attempts,
340
+ timestamp: job.timestamp,
341
+ processedOn: job.processedOn,
342
+ finishedOn: job.finishedOn,
343
+ scope: job.scope,
344
+ actor: job.actor
345
+ });
346
+ }
347
+ }
348
+ const start = options?.start ?? 0;
349
+ const end = options?.end ?? results.length;
350
+ return results.slice(start, end);
351
+ }
352
+ // ==========================================
353
+ // PAUSE/RESUME JOB TYPES
354
+ // ==========================================
355
+ async pauseJobType(queue, jobName) {
356
+ const q = this.getOrCreateQueue(queue);
357
+ q.pausedJobTypes.add(jobName);
358
+ }
359
+ async resumeJobType(queue, jobName) {
360
+ const q = this.getOrCreateQueue(queue);
361
+ q.pausedJobTypes.delete(jobName);
362
+ }
363
+ // ==========================================
364
+ // EVENTS
365
+ // ==========================================
366
+ async subscribe(pattern, handler) {
367
+ if (!this.eventHandlers.has(pattern)) {
368
+ this.eventHandlers.set(pattern, /* @__PURE__ */ new Set());
369
+ }
370
+ this.eventHandlers.get(pattern).add(handler);
371
+ return async () => {
372
+ this.eventHandlers.get(pattern)?.delete(handler);
373
+ };
374
+ }
375
+ // ==========================================
376
+ // WORKERS
377
+ // ==========================================
378
+ async createWorker(config, handler) {
379
+ const workerId = `worker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
380
+ this.workers.set(workerId, {
381
+ handler,
382
+ config,
383
+ running: true,
384
+ paused: false
385
+ });
386
+ const startTime = Date.now();
387
+ let processed = 0;
388
+ let failed = 0;
389
+ let completed = 0;
390
+ return {
391
+ id: workerId,
392
+ pause: async () => {
393
+ const worker = this.workers.get(workerId);
394
+ if (worker) worker.paused = true;
395
+ },
396
+ resume: async () => {
397
+ const worker = this.workers.get(workerId);
398
+ if (worker) worker.paused = false;
399
+ },
400
+ close: async () => {
401
+ this.workers.delete(workerId);
402
+ },
403
+ isRunning: () => {
404
+ const worker = this.workers.get(workerId);
405
+ return worker?.running && !worker?.paused || false;
406
+ },
407
+ isPaused: () => {
408
+ return this.workers.get(workerId)?.paused || false;
409
+ },
410
+ getMetrics: async () => ({
411
+ processed,
412
+ failed,
413
+ completed,
414
+ active: 0,
415
+ uptime: Date.now() - startTime
416
+ })
417
+ };
418
+ }
419
+ /**
420
+ * Start the internal job processing loop.
421
+ */
422
+ startProcessing() {
423
+ if (this.processingInterval) return;
424
+ this.processingInterval = setInterval(async () => {
425
+ await this.processJobs();
426
+ }, 100);
427
+ }
428
+ /**
429
+ * Process pending jobs.
430
+ */
431
+ async processJobs() {
432
+ for (const [_, worker] of this.workers) {
433
+ if (!worker.running || worker.paused) continue;
434
+ for (const queueName of worker.config.queues) {
435
+ const queue = this.queues.get(queueName);
436
+ if (!queue || queue.isPaused) continue;
437
+ for (const job of queue.jobs.values()) {
438
+ if (job.state !== "waiting") continue;
439
+ if (queue.pausedJobTypes.has(job.name)) continue;
440
+ if (job.scheduledAt && job.scheduledAt > Date.now()) continue;
441
+ job.state = "active";
442
+ job.processedOn = Date.now();
443
+ job.attempts++;
444
+ try {
445
+ const result = await worker.handler({
446
+ id: job.id,
447
+ name: job.name,
448
+ queue: job.queue,
449
+ data: job.data,
450
+ attempt: job.attempts,
451
+ timestamp: job.timestamp,
452
+ scope: job.scope,
453
+ actor: job.actor,
454
+ log: async (level, message) => {
455
+ job.logs.push({
456
+ timestamp: /* @__PURE__ */ new Date(),
457
+ message,
458
+ level
459
+ });
460
+ },
461
+ updateProgress: async (progress) => {
462
+ job.progress = progress;
463
+ }
464
+ });
465
+ job.state = "completed";
466
+ job.result = result;
467
+ job.finishedOn = Date.now();
468
+ await this.emitEvent(`${job.queue}:${job.name}:completed`, {
469
+ jobId: job.id,
470
+ result
471
+ });
472
+ } catch (error) {
473
+ const errorMessage = error instanceof Error ? error.message : String(error);
474
+ if (job.attempts < job.maxAttempts) {
475
+ job.state = "waiting";
476
+ await this.emitEvent(`${job.queue}:${job.name}:retrying`, {
477
+ jobId: job.id,
478
+ error: errorMessage,
479
+ attempt: job.attempts
480
+ });
481
+ } else {
482
+ job.state = "failed";
483
+ job.error = errorMessage;
484
+ job.finishedOn = Date.now();
485
+ await this.emitEvent(`${job.queue}:${job.name}:failed`, {
486
+ jobId: job.id,
487
+ error: errorMessage
488
+ });
489
+ }
490
+ }
491
+ }
492
+ }
493
+ }
494
+ }
495
+ // ==========================================
496
+ // SEARCH
497
+ // ==========================================
498
+ async searchJobs(filter) {
499
+ const results = [];
500
+ const queuesToSearch = filter.queue ? [filter.queue] : Array.from(this.queues.keys());
501
+ for (const queueName of queuesToSearch) {
502
+ const queue = this.queues.get(queueName);
503
+ if (!queue) continue;
504
+ for (const job of queue.jobs.values()) {
505
+ if (filter.status && !filter.status.includes(job.state)) continue;
506
+ if (filter.jobName && job.name !== filter.jobName) continue;
507
+ if (filter.scopeId && job.scope?.id !== filter.scopeId) continue;
508
+ if (filter.actorId && job.actor?.id !== filter.actorId) continue;
509
+ if (filter.dateRange?.from && job.timestamp < filter.dateRange.from.getTime()) continue;
510
+ if (filter.dateRange?.to && job.timestamp > filter.dateRange.to.getTime()) continue;
511
+ results.push({
512
+ id: job.id,
513
+ name: job.name,
514
+ queue: job.queue,
515
+ state: job.state,
516
+ data: job.data,
517
+ result: job.result,
518
+ error: job.error,
519
+ progress: job.progress,
520
+ attempts: job.attempts,
521
+ timestamp: job.timestamp,
522
+ processedOn: job.processedOn,
523
+ finishedOn: job.finishedOn,
524
+ scope: job.scope,
525
+ actor: job.actor
526
+ });
527
+ }
528
+ }
529
+ if (filter.orderBy) {
530
+ const [field, direction] = filter.orderBy.split(":");
531
+ results.sort((a, b) => {
532
+ const aVal = field === "createdAt" ? a.timestamp : a[field];
533
+ const bVal = field === "createdAt" ? b.timestamp : b[field];
534
+ return direction === "asc" ? aVal - bVal : bVal - aVal;
535
+ });
536
+ }
537
+ const offset = filter.offset ?? 0;
538
+ const limit = filter.limit ?? 100;
539
+ return results.slice(offset, offset + limit);
540
+ }
541
+ async searchQueues(filter) {
542
+ const results = [];
543
+ for (const [queueName, queue] of this.queues) {
544
+ if (filter.name && !queueName.includes(filter.name)) continue;
545
+ if (filter.isPaused !== void 0 && queue.isPaused !== filter.isPaused) continue;
546
+ const counts = await this.getJobCounts(queueName);
547
+ results.push({
548
+ name: queueName,
549
+ isPaused: queue.isPaused,
550
+ jobCounts: counts
551
+ });
552
+ }
553
+ return results;
554
+ }
555
+ // ==========================================
556
+ // LIFECYCLE
557
+ // ==========================================
558
+ async shutdown() {
559
+ if (this.processingInterval) {
560
+ clearInterval(this.processingInterval);
561
+ this.processingInterval = null;
562
+ }
563
+ this.workers.clear();
564
+ this.queues.clear();
565
+ this.eventHandlers.clear();
566
+ }
567
+ };
568
+
569
+ exports.MemoryAdapter = MemoryAdapter;
570
+ //# sourceMappingURL=memory.adapter.js.map
571
+ //# sourceMappingURL=memory.adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors/igniter-jobs.error.ts","../../src/adapters/memory.adapter.ts"],"names":["IgniterError"],"mappings":"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","file":"memory.adapter.js","sourcesContent":["/**\n * @fileoverview Error class and error codes for @igniter-js/jobs\n * @module @igniter-js/jobs/errors\n */\n\nimport { IgniterError, type IgniterLogger } from '@igniter-js/core'\n\n/**\n * All possible error codes for IgniterJobs.\n * Use these codes for programmatic error handling.\n *\n * @example\n * ```typescript\n * try {\n * await jobs.email.sendWelcome.dispatch(input)\n * } catch (error) {\n * if (error instanceof IgniterJobsError) {\n * switch (error.code) {\n * case 'JOBS_ADAPTER_REQUIRED':\n * // Handle missing adapter\n * break\n * case 'JOBS_DISPATCH_FAILED':\n * // Handle dispatch failure\n * break\n * }\n * }\n * }\n * ```\n */\nexport const IGNITER_JOBS_ERROR_CODES = {\n // Configuration errors\n JOBS_ADAPTER_REQUIRED: 'JOBS_ADAPTER_REQUIRED',\n JOBS_CONTEXT_REQUIRED: 'JOBS_CONTEXT_REQUIRED',\n JOBS_QUEUE_REQUIRED: 'JOBS_QUEUE_REQUIRED',\n JOBS_CONFIGURATION_INVALID: 'JOBS_CONFIGURATION_INVALID',\n\n // Queue errors\n JOBS_QUEUE_NOT_FOUND: 'JOBS_QUEUE_NOT_FOUND',\n JOBS_QUEUE_ALREADY_EXISTS: 'JOBS_QUEUE_ALREADY_EXISTS',\n JOBS_QUEUE_NAME_INVALID: 'JOBS_QUEUE_NAME_INVALID',\n JOBS_QUEUE_PAUSE_FAILED: 'JOBS_QUEUE_PAUSE_FAILED',\n JOBS_QUEUE_RESUME_FAILED: 'JOBS_QUEUE_RESUME_FAILED',\n JOBS_QUEUE_DRAIN_FAILED: 'JOBS_QUEUE_DRAIN_FAILED',\n JOBS_QUEUE_CLEAN_FAILED: 'JOBS_QUEUE_CLEAN_FAILED',\n JOBS_QUEUE_OBLITERATE_FAILED: 'JOBS_QUEUE_OBLITERATE_FAILED',\n\n // Job definition errors\n JOBS_JOB_NOT_FOUND: 'JOBS_JOB_NOT_FOUND',\n JOBS_JOB_ALREADY_EXISTS: 'JOBS_JOB_ALREADY_EXISTS',\n JOBS_JOB_NAME_INVALID: 'JOBS_JOB_NAME_INVALID',\n JOBS_JOB_HANDLER_REQUIRED: 'JOBS_JOB_HANDLER_REQUIRED',\n\n // Cron errors\n JOBS_CRON_NOT_FOUND: 'JOBS_CRON_NOT_FOUND',\n JOBS_CRON_ALREADY_EXISTS: 'JOBS_CRON_ALREADY_EXISTS',\n JOBS_CRON_EXPRESSION_INVALID: 'JOBS_CRON_EXPRESSION_INVALID',\n JOBS_CRON_HANDLER_REQUIRED: 'JOBS_CRON_HANDLER_REQUIRED',\n\n // Dispatch errors\n JOBS_DISPATCH_FAILED: 'JOBS_DISPATCH_FAILED',\n JOBS_SCHEDULE_FAILED: 'JOBS_SCHEDULE_FAILED',\n JOBS_INPUT_REQUIRED: 'JOBS_INPUT_REQUIRED',\n JOBS_INPUT_VALIDATION_FAILED: 'JOBS_INPUT_VALIDATION_FAILED',\n\n // Scope errors\n JOBS_SCOPE_REQUIRED: 'JOBS_SCOPE_REQUIRED',\n JOBS_SCOPE_ALREADY_DEFINED: 'JOBS_SCOPE_ALREADY_DEFINED',\n JOBS_SCOPE_INVALID: 'JOBS_SCOPE_INVALID',\n\n // Actor errors\n JOBS_ACTOR_ALREADY_DEFINED: 'JOBS_ACTOR_ALREADY_DEFINED',\n JOBS_ACTOR_INVALID: 'JOBS_ACTOR_INVALID',\n\n // Job management errors\n JOBS_GET_FAILED: 'JOBS_GET_FAILED',\n JOBS_RETRY_FAILED: 'JOBS_RETRY_FAILED',\n JOBS_REMOVE_FAILED: 'JOBS_REMOVE_FAILED',\n JOBS_PROMOTE_FAILED: 'JOBS_PROMOTE_FAILED',\n JOBS_MOVE_FAILED: 'JOBS_MOVE_FAILED',\n JOBS_STATE_FAILED: 'JOBS_STATE_FAILED',\n JOBS_PROGRESS_FAILED: 'JOBS_PROGRESS_FAILED',\n JOBS_LOGS_FAILED: 'JOBS_LOGS_FAILED',\n\n // Worker errors\n JOBS_WORKER_CREATE_FAILED: 'JOBS_WORKER_CREATE_FAILED',\n JOBS_WORKER_START_FAILED: 'JOBS_WORKER_START_FAILED',\n JOBS_WORKER_STOP_FAILED: 'JOBS_WORKER_STOP_FAILED',\n JOBS_WORKER_NOT_FOUND: 'JOBS_WORKER_NOT_FOUND',\n JOBS_WORKER_ALREADY_RUNNING: 'JOBS_WORKER_ALREADY_RUNNING',\n\n // Event/Subscribe errors\n JOBS_SUBSCRIBE_FAILED: 'JOBS_SUBSCRIBE_FAILED',\n JOBS_UNSUBSCRIBE_FAILED: 'JOBS_UNSUBSCRIBE_FAILED',\n JOBS_EVENT_EMIT_FAILED: 'JOBS_EVENT_EMIT_FAILED',\n\n // Search errors\n JOBS_SEARCH_FAILED: 'JOBS_SEARCH_FAILED',\n JOBS_SEARCH_INVALID_TARGET: 'JOBS_SEARCH_INVALID_TARGET',\n\n // Shutdown errors\n JOBS_SHUTDOWN_FAILED: 'JOBS_SHUTDOWN_FAILED',\n\n // Handler errors\n JOBS_HANDLER_FAILED: 'JOBS_HANDLER_FAILED',\n JOBS_HANDLER_TIMEOUT: 'JOBS_HANDLER_TIMEOUT',\n} as const\n\n/**\n * Type for all possible error codes.\n */\nexport type IgniterJobsErrorCode = keyof typeof IGNITER_JOBS_ERROR_CODES\n\n/**\n * Options for creating an IgniterJobsError.\n */\nexport interface IgniterJobsErrorOptions {\n /**\n * Error code from IGNITER_JOBS_ERROR_CODES.\n */\n code: IgniterJobsErrorCode\n\n /**\n * Human-readable error message.\n */\n message: string\n\n /**\n * HTTP status code (for REST API responses).\n * @default 500\n */\n statusCode?: number\n\n /**\n * Original error that caused this error.\n */\n cause?: Error\n\n /**\n * Additional details for debugging.\n */\n details?: Record<string, unknown>\n\n /**\n * Logger instance for logging the error.\n */\n logger?: IgniterLogger\n}\n\n/**\n * Custom error class for @igniter-js/jobs.\n * Extends IgniterError for consistent error handling across the Igniter ecosystem.\n *\n * @example\n * ```typescript\n * throw new IgniterJobsError({\n * code: 'JOBS_DISPATCH_FAILED',\n * message: 'Failed to dispatch job to queue',\n * statusCode: 500,\n * details: { queue: 'email', job: 'sendWelcome' },\n * })\n * ```\n */\nexport class IgniterJobsError extends IgniterError {\n /**\n * Error code for programmatic handling.\n */\n public readonly code: IgniterJobsErrorCode\n\n /**\n * Additional error details.\n */\n public readonly details?: Record<string, unknown>\n\n constructor(options: IgniterJobsErrorOptions) {\n super({\n code: options.code,\n message: options.message,\n statusCode: options.statusCode ?? 500,\n causer: '@igniter-js/jobs',\n cause: options.cause,\n details: options.details,\n logger: options.logger,\n })\n\n this.code = options.code\n this.details = options.details\n this.name = 'IgniterJobsError'\n\n // Maintain proper stack trace\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, IgniterJobsError)\n }\n }\n\n /**\n * Convert error to a plain object for serialization.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n details: this.details,\n stack: this.stack,\n }\n }\n}\n","/**\n * @fileoverview In-memory adapter for @igniter-js/jobs (testing purposes)\n * @module @igniter-js/jobs/adapters/memory\n *\n * @description\n * Provides an in-memory implementation of the IgniterJobsAdapter interface.\n * Useful for testing and development without Redis.\n *\n * @example\n * ```typescript\n * import { MemoryAdapter } from '@igniter-js/jobs/adapters'\n *\n * const adapter = MemoryAdapter.create()\n *\n * const jobs = IgniterJobs.create<AppContext>()\n * .withAdapter(adapter)\n * .build()\n * ```\n */\n\nimport type {\n IgniterJobsAdapter,\n AdapterDispatchParams,\n AdapterScheduleParams,\n AdapterWorkerConfig,\n AdapterWorkerHandle,\n AdapterJobHandler,\n IgniterJobInfo,\n IgniterJobStatus,\n IgniterJobCounts,\n IgniterJobLog,\n IgniterQueueInfo,\n IgniterQueueCleanOptions,\n IgniterJobSearchResult,\n IgniterJobEventHandler,\n IgniterJobUnsubscribeFn,\n IgniterJobScopeEntry,\n IgniterJobActorEntry,\n} from '../types'\nimport { IgniterJobsError } from '../errors'\n\n/**\n * Internal job representation.\n */\ninterface InternalJob {\n id: string\n name: string\n queue: string\n data: unknown\n state: IgniterJobStatus\n result?: unknown\n error?: string\n progress: number\n attempts: number\n maxAttempts: number\n timestamp: number\n processedOn?: number\n finishedOn?: number\n delay?: number\n priority: number\n scope?: IgniterJobScopeEntry\n actor?: IgniterJobActorEntry\n logs: IgniterJobLog[]\n scheduledAt?: number\n cron?: string\n every?: number\n}\n\n/**\n * Internal queue representation.\n */\ninterface InternalQueue {\n name: string\n isPaused: boolean\n jobs: Map<string, InternalJob>\n pausedJobTypes: Set<string>\n}\n\n/**\n * Memory-based implementation of IgniterJobsAdapter.\n * Suitable for testing and development.\n */\nexport class MemoryAdapter implements IgniterJobsAdapter {\n private readonly queues: Map<string, InternalQueue> = new Map()\n private readonly eventHandlers: Map<string, Set<IgniterJobEventHandler>> = new Map()\n private readonly workers: Map<string, { handler: AdapterJobHandler; config: AdapterWorkerConfig; running: boolean; paused: boolean }> = new Map()\n private processingInterval: ReturnType<typeof setInterval> | null = null\n\n private constructor() {}\n\n /**\n * Create a new memory adapter.\n */\n static create(): MemoryAdapter {\n return new MemoryAdapter()\n }\n\n /**\n * Get the underlying client (null for memory adapter).\n */\n get client(): null {\n return null\n }\n\n /**\n * Get or create a queue.\n */\n private getOrCreateQueue(name: string): InternalQueue {\n if (!this.queues.has(name)) {\n this.queues.set(name, {\n name,\n isPaused: false,\n jobs: new Map(),\n pausedJobTypes: new Set(),\n })\n }\n return this.queues.get(name)!\n }\n\n /**\n * Generate a unique job ID.\n */\n private generateJobId(): string {\n return `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n }\n\n /**\n * Emit an event to subscribers.\n */\n private async emitEvent(type: string, data: unknown): Promise<void> {\n for (const [pattern, handlers] of this.eventHandlers) {\n if (this.matchesPattern(pattern, type)) {\n for (const handler of handlers) {\n try {\n await handler({\n type,\n data,\n timestamp: new Date().toISOString(),\n })\n } catch {\n // Ignore handler errors\n }\n }\n }\n }\n }\n\n /**\n * Check if a pattern matches an event type.\n */\n private matchesPattern(pattern: string, eventType: string): boolean {\n if (pattern === '*') return true\n const regex = new RegExp(\n '^' + pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.') + '$'\n )\n return regex.test(eventType)\n }\n\n // ==========================================\n // JOB OPERATIONS\n // ==========================================\n\n async dispatch(params: AdapterDispatchParams): Promise<string> {\n const queue = this.getOrCreateQueue(params.queue)\n const jobId = params.jobId || this.generateJobId()\n\n const job: InternalJob = {\n id: jobId,\n name: params.name,\n queue: params.queue,\n data: params.data,\n state: params.delay ? 'delayed' : 'waiting',\n progress: 0,\n attempts: 0,\n maxAttempts: params.attempts ?? 3,\n timestamp: Date.now(),\n delay: params.delay,\n priority: params.priority ?? 0,\n scope: params.scope,\n actor: params.actor,\n logs: [],\n scheduledAt: params.delay ? Date.now() + params.delay : undefined,\n }\n\n queue.jobs.set(jobId, job)\n\n await this.emitEvent(`${params.queue}:${params.name}:enqueued`, {\n jobId,\n name: params.name,\n queue: params.queue,\n })\n\n this.startProcessing()\n\n return jobId\n }\n\n async schedule(params: AdapterScheduleParams): Promise<string> {\n const delay = params.at\n ? params.at.getTime() - Date.now()\n : params.delay ?? 0\n\n return this.dispatch({\n ...params,\n delay,\n })\n }\n\n async getJob(queue: string, jobId: string): Promise<IgniterJobInfo | null> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n\n if (!job) return null\n\n return {\n id: job.id,\n name: job.name,\n queue: job.queue,\n state: job.state,\n data: job.data,\n result: job.result,\n error: job.error,\n progress: job.progress,\n attempts: job.attempts,\n timestamp: job.timestamp,\n processedOn: job.processedOn,\n finishedOn: job.finishedOn,\n delay: job.delay,\n priority: job.priority,\n scope: job.scope,\n actor: job.actor,\n }\n }\n\n async getJobState(queue: string, jobId: string): Promise<IgniterJobStatus | null> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n return job?.state ?? null\n }\n\n async getJobProgress(queue: string, jobId: string): Promise<number> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n return job?.progress ?? 0\n }\n\n async getJobLogs(queue: string, jobId: string): Promise<IgniterJobLog[]> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n return job?.logs ?? []\n }\n\n async retryJob(queue: string, jobId: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n\n if (!job) {\n throw new IgniterJobsError({\n code: 'JOBS_JOB_NOT_FOUND',\n message: `Job \"${jobId}\" not found`,\n statusCode: 404,\n })\n }\n\n job.state = 'waiting'\n job.error = undefined\n job.attempts = 0\n }\n\n async removeJob(queue: string, jobId: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n q.jobs.delete(jobId)\n }\n\n async promoteJob(queue: string, jobId: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n\n if (!job) {\n throw new IgniterJobsError({\n code: 'JOBS_JOB_NOT_FOUND',\n message: `Job \"${jobId}\" not found`,\n statusCode: 404,\n })\n }\n\n if (job.state === 'delayed') {\n job.state = 'waiting'\n job.scheduledAt = undefined\n job.delay = undefined\n }\n }\n\n async moveJob(\n queue: string,\n jobId: string,\n state: 'failed' | 'completed',\n reason?: string\n ): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n const job = q.jobs.get(jobId)\n\n if (!job) {\n throw new IgniterJobsError({\n code: 'JOBS_JOB_NOT_FOUND',\n message: `Job \"${jobId}\" not found`,\n statusCode: 404,\n })\n }\n\n job.state = state\n job.finishedOn = Date.now()\n\n if (state === 'failed') {\n job.error = reason\n } else {\n job.result = reason\n }\n }\n\n async retryJobs(queue: string, jobIds: string[]): Promise<void> {\n await Promise.all(jobIds.map((id) => this.retryJob(queue, id)))\n }\n\n async removeJobs(queue: string, jobIds: string[]): Promise<void> {\n await Promise.all(jobIds.map((id) => this.removeJob(queue, id)))\n }\n\n // ==========================================\n // QUEUE OPERATIONS\n // ==========================================\n\n async getQueue(queue: string): Promise<IgniterQueueInfo> {\n const q = this.getOrCreateQueue(queue)\n const counts = await this.getJobCounts(queue)\n\n return {\n name: queue,\n isPaused: q.isPaused,\n jobCounts: counts,\n }\n }\n\n async pauseQueue(queue: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n q.isPaused = true\n }\n\n async resumeQueue(queue: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n q.isPaused = false\n }\n\n async drainQueue(queue: string): Promise<number> {\n const q = this.getOrCreateQueue(queue)\n let count = 0\n\n for (const [id, job] of q.jobs) {\n if (job.state === 'waiting' || job.state === 'delayed') {\n q.jobs.delete(id)\n count++\n }\n }\n\n return count\n }\n\n async cleanQueue(queue: string, options: IgniterQueueCleanOptions): Promise<number> {\n const q = this.getOrCreateQueue(queue)\n const statuses = Array.isArray(options.status) ? options.status : [options.status]\n const now = Date.now()\n let count = 0\n let removed = 0\n\n for (const [id, job] of q.jobs) {\n if (options.limit && removed >= options.limit) break\n\n if (statuses.includes(job.state as any)) {\n const age = now - job.timestamp\n if (!options.olderThan || age >= options.olderThan) {\n q.jobs.delete(id)\n removed++\n count++\n }\n }\n }\n\n return count\n }\n\n async obliterateQueue(queue: string, options?: { force?: boolean }): Promise<void> {\n this.queues.delete(queue)\n }\n\n async retryAllFailed(queue: string): Promise<number> {\n const q = this.getOrCreateQueue(queue)\n let count = 0\n\n for (const job of q.jobs.values()) {\n if (job.state === 'failed') {\n job.state = 'waiting'\n job.error = undefined\n job.attempts = 0\n count++\n }\n }\n\n return count\n }\n\n async getJobCounts(queue: string): Promise<IgniterJobCounts> {\n const q = this.getOrCreateQueue(queue)\n const counts: IgniterJobCounts = {\n waiting: 0,\n active: 0,\n completed: 0,\n failed: 0,\n delayed: 0,\n paused: 0,\n }\n\n for (const job of q.jobs.values()) {\n counts[job.state]++\n }\n\n return counts\n }\n\n async listJobs(\n queue: string,\n options?: { status?: IgniterJobStatus[]; start?: number; end?: number }\n ): Promise<IgniterJobSearchResult[]> {\n const q = this.getOrCreateQueue(queue)\n const results: IgniterJobSearchResult[] = []\n const statuses = options?.status || ['waiting', 'active', 'completed', 'failed', 'delayed', 'paused']\n\n for (const job of q.jobs.values()) {\n if (statuses.includes(job.state)) {\n results.push({\n id: job.id,\n name: job.name,\n queue: job.queue,\n state: job.state,\n data: job.data,\n result: job.result,\n error: job.error,\n progress: job.progress,\n attempts: job.attempts,\n timestamp: job.timestamp,\n processedOn: job.processedOn,\n finishedOn: job.finishedOn,\n scope: job.scope,\n actor: job.actor,\n })\n }\n }\n\n const start = options?.start ?? 0\n const end = options?.end ?? results.length\n\n return results.slice(start, end)\n }\n\n // ==========================================\n // PAUSE/RESUME JOB TYPES\n // ==========================================\n\n async pauseJobType(queue: string, jobName: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n q.pausedJobTypes.add(jobName)\n }\n\n async resumeJobType(queue: string, jobName: string): Promise<void> {\n const q = this.getOrCreateQueue(queue)\n q.pausedJobTypes.delete(jobName)\n }\n\n // ==========================================\n // EVENTS\n // ==========================================\n\n async subscribe(\n pattern: string,\n handler: IgniterJobEventHandler\n ): Promise<IgniterJobUnsubscribeFn> {\n if (!this.eventHandlers.has(pattern)) {\n this.eventHandlers.set(pattern, new Set())\n }\n\n this.eventHandlers.get(pattern)!.add(handler)\n\n return async () => {\n this.eventHandlers.get(pattern)?.delete(handler)\n }\n }\n\n // ==========================================\n // WORKERS\n // ==========================================\n\n async createWorker(\n config: AdapterWorkerConfig,\n handler: AdapterJobHandler\n ): Promise<AdapterWorkerHandle> {\n const workerId = `worker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n\n this.workers.set(workerId, {\n handler,\n config,\n running: true,\n paused: false,\n })\n\n const startTime = Date.now()\n let processed = 0\n let failed = 0\n let completed = 0\n\n return {\n id: workerId,\n\n pause: async () => {\n const worker = this.workers.get(workerId)\n if (worker) worker.paused = true\n },\n\n resume: async () => {\n const worker = this.workers.get(workerId)\n if (worker) worker.paused = false\n },\n\n close: async () => {\n this.workers.delete(workerId)\n },\n\n isRunning: () => {\n const worker = this.workers.get(workerId)\n return worker?.running && !worker?.paused || false\n },\n\n isPaused: () => {\n return this.workers.get(workerId)?.paused || false\n },\n\n getMetrics: async () => ({\n processed,\n failed,\n completed,\n active: 0,\n uptime: Date.now() - startTime,\n }),\n }\n }\n\n /**\n * Start the internal job processing loop.\n */\n private startProcessing(): void {\n if (this.processingInterval) return\n\n this.processingInterval = setInterval(async () => {\n await this.processJobs()\n }, 100)\n }\n\n /**\n * Process pending jobs.\n */\n private async processJobs(): Promise<void> {\n for (const [_, worker] of this.workers) {\n if (!worker.running || worker.paused) continue\n\n for (const queueName of worker.config.queues) {\n const queue = this.queues.get(queueName)\n if (!queue || queue.isPaused) continue\n\n // Find waiting jobs\n for (const job of queue.jobs.values()) {\n if (job.state !== 'waiting') continue\n if (queue.pausedJobTypes.has(job.name)) continue\n\n // Check if delayed job is ready\n if (job.scheduledAt && job.scheduledAt > Date.now()) continue\n\n // Process job\n job.state = 'active'\n job.processedOn = Date.now()\n job.attempts++\n\n try {\n const result = await worker.handler({\n id: job.id,\n name: job.name,\n queue: job.queue,\n data: job.data,\n attempt: job.attempts,\n timestamp: job.timestamp,\n scope: job.scope,\n actor: job.actor,\n log: async (level, message) => {\n job.logs.push({\n timestamp: new Date(),\n message,\n level,\n })\n },\n updateProgress: async (progress) => {\n job.progress = progress\n },\n })\n\n job.state = 'completed'\n job.result = result\n job.finishedOn = Date.now()\n\n await this.emitEvent(`${job.queue}:${job.name}:completed`, {\n jobId: job.id,\n result,\n })\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n if (job.attempts < job.maxAttempts) {\n job.state = 'waiting'\n await this.emitEvent(`${job.queue}:${job.name}:retrying`, {\n jobId: job.id,\n error: errorMessage,\n attempt: job.attempts,\n })\n } else {\n job.state = 'failed'\n job.error = errorMessage\n job.finishedOn = Date.now()\n\n await this.emitEvent(`${job.queue}:${job.name}:failed`, {\n jobId: job.id,\n error: errorMessage,\n })\n }\n }\n }\n }\n }\n }\n\n // ==========================================\n // SEARCH\n // ==========================================\n\n async searchJobs(filter: {\n status?: IgniterJobStatus[]\n queue?: string\n jobName?: string\n scopeId?: string\n actorId?: string\n dateRange?: { from?: Date; to?: Date }\n orderBy?: string\n limit?: number\n offset?: number\n }): Promise<IgniterJobSearchResult[]> {\n const results: IgniterJobSearchResult[] = []\n const queuesToSearch = filter.queue\n ? [filter.queue]\n : Array.from(this.queues.keys())\n\n for (const queueName of queuesToSearch) {\n const queue = this.queues.get(queueName)\n if (!queue) continue\n\n for (const job of queue.jobs.values()) {\n // Apply filters\n if (filter.status && !filter.status.includes(job.state)) continue\n if (filter.jobName && job.name !== filter.jobName) continue\n if (filter.scopeId && job.scope?.id !== filter.scopeId) continue\n if (filter.actorId && job.actor?.id !== filter.actorId) continue\n if (filter.dateRange?.from && job.timestamp < filter.dateRange.from.getTime()) continue\n if (filter.dateRange?.to && job.timestamp > filter.dateRange.to.getTime()) continue\n\n results.push({\n id: job.id,\n name: job.name,\n queue: job.queue,\n state: job.state,\n data: job.data,\n result: job.result,\n error: job.error,\n progress: job.progress,\n attempts: job.attempts,\n timestamp: job.timestamp,\n processedOn: job.processedOn,\n finishedOn: job.finishedOn,\n scope: job.scope,\n actor: job.actor,\n })\n }\n }\n\n // Apply sorting\n if (filter.orderBy) {\n const [field, direction] = filter.orderBy.split(':')\n results.sort((a, b) => {\n const aVal = field === 'createdAt' ? a.timestamp : (a as any)[field]\n const bVal = field === 'createdAt' ? b.timestamp : (b as any)[field]\n return direction === 'asc' ? aVal - bVal : bVal - aVal\n })\n }\n\n const offset = filter.offset ?? 0\n const limit = filter.limit ?? 100\n\n return results.slice(offset, offset + limit)\n }\n\n async searchQueues(filter: {\n name?: string\n isPaused?: boolean\n }): Promise<IgniterQueueInfo[]> {\n const results: IgniterQueueInfo[] = []\n\n for (const [queueName, queue] of this.queues) {\n if (filter.name && !queueName.includes(filter.name)) continue\n if (filter.isPaused !== undefined && queue.isPaused !== filter.isPaused) continue\n\n const counts = await this.getJobCounts(queueName)\n results.push({\n name: queueName,\n isPaused: queue.isPaused,\n jobCounts: counts,\n })\n }\n\n return results\n }\n\n // ==========================================\n // LIFECYCLE\n // ==========================================\n\n async shutdown(): Promise<void> {\n if (this.processingInterval) {\n clearInterval(this.processingInterval)\n this.processingInterval = null\n }\n\n this.workers.clear()\n this.queues.clear()\n this.eventHandlers.clear()\n }\n}\n"]}