light-async-queue 1.0.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.
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/dist/src/dlq/DeadLetterQueue.d.ts +34 -0
- package/dist/src/dlq/DeadLetterQueue.d.ts.map +1 -0
- package/dist/src/dlq/DeadLetterQueue.js +60 -0
- package/dist/src/dlq/DeadLetterQueue.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +13 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/queue/Backoff.d.ts +24 -0
- package/dist/src/queue/Backoff.d.ts.map +1 -0
- package/dist/src/queue/Backoff.js +37 -0
- package/dist/src/queue/Backoff.js.map +1 -0
- package/dist/src/queue/Job.d.ts +44 -0
- package/dist/src/queue/Job.d.ts.map +1 -0
- package/dist/src/queue/Job.js +89 -0
- package/dist/src/queue/Job.js.map +1 -0
- package/dist/src/queue/Queue.d.ts +67 -0
- package/dist/src/queue/Queue.d.ts.map +1 -0
- package/dist/src/queue/Queue.js +261 -0
- package/dist/src/queue/Queue.js.map +1 -0
- package/dist/src/queue/Scheduler.d.ts +30 -0
- package/dist/src/queue/Scheduler.d.ts.map +1 -0
- package/dist/src/queue/Scheduler.js +62 -0
- package/dist/src/queue/Scheduler.js.map +1 -0
- package/dist/src/storage/FileStore.d.ts +55 -0
- package/dist/src/storage/FileStore.d.ts.map +1 -0
- package/dist/src/storage/FileStore.js +247 -0
- package/dist/src/storage/FileStore.js.map +1 -0
- package/dist/src/storage/MemoryStore.d.ts +21 -0
- package/dist/src/storage/MemoryStore.d.ts.map +1 -0
- package/dist/src/storage/MemoryStore.js +55 -0
- package/dist/src/storage/MemoryStore.js.map +1 -0
- package/dist/src/types.d.ts +126 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/worker/Worker.d.ts +36 -0
- package/dist/src/worker/Worker.d.ts.map +1 -0
- package/dist/src/worker/Worker.js +170 -0
- package/dist/src/worker/Worker.js.map +1 -0
- package/dist/src/worker/childProcessor.d.ts +2 -0
- package/dist/src/worker/childProcessor.d.ts.map +1 -0
- package/dist/src/worker/childProcessor.js +70 -0
- package/dist/src/worker/childProcessor.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Job class representing a single unit of work in the queue
|
|
4
|
+
*/
|
|
5
|
+
export class Job {
|
|
6
|
+
id;
|
|
7
|
+
payload;
|
|
8
|
+
attempts;
|
|
9
|
+
maxAttempts;
|
|
10
|
+
status;
|
|
11
|
+
nextRunAt;
|
|
12
|
+
createdAt;
|
|
13
|
+
updatedAt;
|
|
14
|
+
constructor(payload, maxAttempts, nextRunAt) {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
this.id = randomUUID();
|
|
17
|
+
this.payload = payload;
|
|
18
|
+
this.attempts = 0;
|
|
19
|
+
this.maxAttempts = maxAttempts;
|
|
20
|
+
this.status = 'pending';
|
|
21
|
+
this.nextRunAt = nextRunAt ?? now;
|
|
22
|
+
this.createdAt = now;
|
|
23
|
+
this.updatedAt = now;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a Job instance from stored data
|
|
27
|
+
*/
|
|
28
|
+
static fromData(data) {
|
|
29
|
+
const job = Object.create(Job.prototype);
|
|
30
|
+
Object.assign(job, data);
|
|
31
|
+
return job;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Convert job to plain data object for storage
|
|
35
|
+
*/
|
|
36
|
+
toData() {
|
|
37
|
+
return {
|
|
38
|
+
id: this.id,
|
|
39
|
+
payload: this.payload,
|
|
40
|
+
attempts: this.attempts,
|
|
41
|
+
maxAttempts: this.maxAttempts,
|
|
42
|
+
status: this.status,
|
|
43
|
+
nextRunAt: this.nextRunAt,
|
|
44
|
+
createdAt: this.createdAt,
|
|
45
|
+
updatedAt: this.updatedAt,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Mark job as processing
|
|
50
|
+
*/
|
|
51
|
+
markProcessing() {
|
|
52
|
+
this.status = 'processing';
|
|
53
|
+
this.updatedAt = Date.now();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mark job as completed
|
|
57
|
+
*/
|
|
58
|
+
markCompleted() {
|
|
59
|
+
this.status = 'completed';
|
|
60
|
+
this.updatedAt = Date.now();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Mark job as failed and increment attempts
|
|
64
|
+
*/
|
|
65
|
+
markFailed(nextRunAt) {
|
|
66
|
+
this.attempts += 1;
|
|
67
|
+
this.status = this.attempts >= this.maxAttempts ? 'failed' : 'pending';
|
|
68
|
+
if (nextRunAt !== undefined) {
|
|
69
|
+
this.nextRunAt = nextRunAt;
|
|
70
|
+
}
|
|
71
|
+
this.updatedAt = Date.now();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if job has exceeded max attempts
|
|
75
|
+
*/
|
|
76
|
+
hasExceededMaxAttempts() {
|
|
77
|
+
return this.attempts >= this.maxAttempts;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reset job for reprocessing (used when recovering from DLQ)
|
|
81
|
+
*/
|
|
82
|
+
reset() {
|
|
83
|
+
this.attempts = 0;
|
|
84
|
+
this.status = 'pending';
|
|
85
|
+
this.nextRunAt = Date.now();
|
|
86
|
+
this.updatedAt = Date.now();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=Job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Job.js","sourceRoot":"","sources":["../../../src/queue/Job.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,OAAO,GAAG;IACE,EAAE,CAAS;IACpB,OAAO,CAAU;IACjB,QAAQ,CAAS;IACjB,WAAW,CAAS;IACpB,MAAM,CAAY;IAClB,SAAS,CAAS;IACT,SAAS,CAAS;IAC3B,SAAS,CAAS;IAEzB,YAAY,OAAgB,EAAE,WAAmB,EAAE,SAAkB;QACnE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,EAAE,GAAG,UAAU,EAAE,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,SAAS,IAAI,GAAG,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAa;QAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO;YACL,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,aAAa;QACX,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,SAAkB;QAC3B,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,sBAAsB;QACpB,OAAO,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { QueueConfig, JobProcessor, JobData } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Main Queue class - orchestrates job processing
|
|
4
|
+
*/
|
|
5
|
+
export declare class Queue {
|
|
6
|
+
private config;
|
|
7
|
+
private storage;
|
|
8
|
+
private scheduler;
|
|
9
|
+
private dlq;
|
|
10
|
+
private backoff;
|
|
11
|
+
private processor;
|
|
12
|
+
private workers;
|
|
13
|
+
private activeJobs;
|
|
14
|
+
private isShuttingDown;
|
|
15
|
+
private isInitialized;
|
|
16
|
+
constructor(config: QueueConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the queue
|
|
19
|
+
*/
|
|
20
|
+
private initialize;
|
|
21
|
+
/**
|
|
22
|
+
* Set the job processor function
|
|
23
|
+
*/
|
|
24
|
+
process(processor: JobProcessor): void;
|
|
25
|
+
/**
|
|
26
|
+
* Add a job to the queue
|
|
27
|
+
*/
|
|
28
|
+
add(payload: unknown): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Handle a job that's ready to process
|
|
31
|
+
*/
|
|
32
|
+
private handleJobReady;
|
|
33
|
+
/**
|
|
34
|
+
* Handle job failure with retry logic
|
|
35
|
+
*/
|
|
36
|
+
private handleJobFailure;
|
|
37
|
+
/**
|
|
38
|
+
* Get an available worker or create a new one
|
|
39
|
+
*/
|
|
40
|
+
private getAvailableWorker;
|
|
41
|
+
/**
|
|
42
|
+
* Get all failed jobs from DLQ
|
|
43
|
+
*/
|
|
44
|
+
getFailedJobs(): Promise<JobData[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Reprocess a failed job from DLQ
|
|
47
|
+
*/
|
|
48
|
+
reprocessFailed(jobId: string): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Get queue statistics
|
|
51
|
+
*/
|
|
52
|
+
getStats(): Promise<{
|
|
53
|
+
active: number;
|
|
54
|
+
pending: number;
|
|
55
|
+
failed: number;
|
|
56
|
+
completed: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Set up graceful shutdown handlers
|
|
60
|
+
*/
|
|
61
|
+
private setupGracefulShutdown;
|
|
62
|
+
/**
|
|
63
|
+
* Gracefully shutdown the queue
|
|
64
|
+
*/
|
|
65
|
+
shutdown(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=Queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Queue.d.ts","sourceRoot":"","sources":["../../../src/queue/Queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAoB,OAAO,EAAE,MAAM,aAAa,CAAC;AASnF;;GAEG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,aAAa,CAAU;gBAEnB,MAAM,EAAE,WAAW;IAkC/B;;OAEG;YACW,UAAU;IASxB;;OAEG;IACH,OAAO,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI;IAItC;;OAEG;IACG,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB5C;;OAEG;YACW,cAAc;IAsD5B;;OAEG;YACW,gBAAgB;IAiB9B;;OAEG;YACW,kBAAkB;IAkChC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAOzC;;OAEG;IACG,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBtD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IAgBF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA2BhC"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { Job } from './Job.js';
|
|
2
|
+
import { Backoff } from './Backoff.js';
|
|
3
|
+
import { Scheduler } from './Scheduler.js';
|
|
4
|
+
import { Worker } from '../worker/Worker.js';
|
|
5
|
+
import { DeadLetterQueue } from '../dlq/DeadLetterQueue.js';
|
|
6
|
+
import { MemoryStore } from '../storage/MemoryStore.js';
|
|
7
|
+
import { FileStore } from '../storage/FileStore.js';
|
|
8
|
+
/**
|
|
9
|
+
* Main Queue class - orchestrates job processing
|
|
10
|
+
*/
|
|
11
|
+
export class Queue {
|
|
12
|
+
config;
|
|
13
|
+
storage;
|
|
14
|
+
scheduler;
|
|
15
|
+
dlq;
|
|
16
|
+
backoff;
|
|
17
|
+
processor;
|
|
18
|
+
workers;
|
|
19
|
+
activeJobs;
|
|
20
|
+
isShuttingDown;
|
|
21
|
+
isInitialized;
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.processor = null;
|
|
25
|
+
this.workers = [];
|
|
26
|
+
this.activeJobs = new Map();
|
|
27
|
+
this.isShuttingDown = false;
|
|
28
|
+
this.isInitialized = false;
|
|
29
|
+
// Initialize storage based on config
|
|
30
|
+
if (config.storage === 'file') {
|
|
31
|
+
if (!config.filePath) {
|
|
32
|
+
throw new Error('filePath is required when storage is "file"');
|
|
33
|
+
}
|
|
34
|
+
this.storage = new FileStore(config.filePath);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.storage = new MemoryStore();
|
|
38
|
+
}
|
|
39
|
+
// Initialize other components
|
|
40
|
+
this.scheduler = new Scheduler(this.storage);
|
|
41
|
+
this.dlq = new DeadLetterQueue(this.storage);
|
|
42
|
+
this.backoff = new Backoff(config.retry.backoff);
|
|
43
|
+
// Set up scheduler event handler
|
|
44
|
+
this.scheduler.on('job-ready', (jobData) => {
|
|
45
|
+
this.handleJobReady(jobData).catch(error => {
|
|
46
|
+
console.error('[Queue] Error handling job:', error);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// Set up graceful shutdown
|
|
50
|
+
this.setupGracefulShutdown();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Initialize the queue
|
|
54
|
+
*/
|
|
55
|
+
async initialize() {
|
|
56
|
+
if (this.isInitialized) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await this.storage.initialize();
|
|
60
|
+
this.isInitialized = true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Set the job processor function
|
|
64
|
+
*/
|
|
65
|
+
process(processor) {
|
|
66
|
+
this.processor = processor;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add a job to the queue
|
|
70
|
+
*/
|
|
71
|
+
async add(payload) {
|
|
72
|
+
if (!this.isInitialized) {
|
|
73
|
+
await this.initialize();
|
|
74
|
+
}
|
|
75
|
+
if (this.isShuttingDown) {
|
|
76
|
+
throw new Error('Queue is shutting down, cannot accept new jobs');
|
|
77
|
+
}
|
|
78
|
+
const job = new Job(payload, this.config.retry.maxAttempts);
|
|
79
|
+
await this.storage.addJob(job.toData());
|
|
80
|
+
// Start scheduler if not already running
|
|
81
|
+
if (!this.scheduler.getIsRunning()) {
|
|
82
|
+
this.scheduler.start();
|
|
83
|
+
}
|
|
84
|
+
return job.id;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Handle a job that's ready to process
|
|
88
|
+
*/
|
|
89
|
+
async handleJobReady(jobData) {
|
|
90
|
+
if (this.isShuttingDown) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Check concurrency limit
|
|
94
|
+
if (this.activeJobs.size >= this.config.concurrency) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Check if job is already being processed
|
|
98
|
+
if (this.activeJobs.has(jobData.id)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!this.processor) {
|
|
102
|
+
console.error('[Queue] No processor function set');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const job = Job.fromData(jobData);
|
|
106
|
+
// Mark job as processing
|
|
107
|
+
job.markProcessing();
|
|
108
|
+
this.activeJobs.set(job.id, job);
|
|
109
|
+
await this.storage.updateJob(job.toData());
|
|
110
|
+
// Get or create a worker
|
|
111
|
+
const worker = await this.getAvailableWorker();
|
|
112
|
+
try {
|
|
113
|
+
// Execute job in worker
|
|
114
|
+
const result = await worker.execute(job.toData());
|
|
115
|
+
if (result.success) {
|
|
116
|
+
// Job succeeded
|
|
117
|
+
job.markCompleted();
|
|
118
|
+
await this.storage.updateJob(job.toData());
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Job failed
|
|
122
|
+
await this.handleJobFailure(job, result.error || 'Unknown error');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// Worker execution error
|
|
127
|
+
await this.handleJobFailure(job, error instanceof Error ? error.message : String(error));
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
// Remove from active jobs
|
|
131
|
+
this.activeJobs.delete(job.id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Handle job failure with retry logic
|
|
136
|
+
*/
|
|
137
|
+
async handleJobFailure(job, error) {
|
|
138
|
+
console.error(`[Queue] Job ${job.id} failed:`, error);
|
|
139
|
+
// Calculate next run time with backoff
|
|
140
|
+
const nextRunAt = this.backoff.getNextRunAt(job.attempts + 1);
|
|
141
|
+
job.markFailed(nextRunAt);
|
|
142
|
+
if (job.hasExceededMaxAttempts()) {
|
|
143
|
+
// Move to dead letter queue
|
|
144
|
+
console.log(`[Queue] Job ${job.id} exceeded max attempts, moving to DLQ`);
|
|
145
|
+
await this.dlq.add(job);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Update job for retry
|
|
149
|
+
await this.storage.updateJob(job.toData());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get an available worker or create a new one
|
|
154
|
+
*/
|
|
155
|
+
async getAvailableWorker() {
|
|
156
|
+
// Find an idle worker
|
|
157
|
+
for (const worker of this.workers) {
|
|
158
|
+
if (!worker.isBusy()) {
|
|
159
|
+
return worker;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Create a new worker if under concurrency limit
|
|
163
|
+
if (this.workers.length < this.config.concurrency) {
|
|
164
|
+
if (!this.processor) {
|
|
165
|
+
throw new Error('Processor function not set');
|
|
166
|
+
}
|
|
167
|
+
const worker = new Worker(this.processor);
|
|
168
|
+
await worker.initialize();
|
|
169
|
+
this.workers.push(worker);
|
|
170
|
+
return worker;
|
|
171
|
+
}
|
|
172
|
+
// Wait for a worker to become available
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
const checkInterval = setInterval(() => {
|
|
175
|
+
for (const worker of this.workers) {
|
|
176
|
+
if (!worker.isBusy()) {
|
|
177
|
+
clearInterval(checkInterval);
|
|
178
|
+
resolve(worker);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}, 100);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get all failed jobs from DLQ
|
|
187
|
+
*/
|
|
188
|
+
async getFailedJobs() {
|
|
189
|
+
if (!this.isInitialized) {
|
|
190
|
+
await this.initialize();
|
|
191
|
+
}
|
|
192
|
+
return this.dlq.getAll();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Reprocess a failed job from DLQ
|
|
196
|
+
*/
|
|
197
|
+
async reprocessFailed(jobId) {
|
|
198
|
+
if (!this.isInitialized) {
|
|
199
|
+
await this.initialize();
|
|
200
|
+
}
|
|
201
|
+
const job = await this.dlq.remove(jobId);
|
|
202
|
+
if (!job) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
// Add back to queue
|
|
206
|
+
await this.storage.addJob(job.toData());
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get queue statistics
|
|
211
|
+
*/
|
|
212
|
+
async getStats() {
|
|
213
|
+
if (!this.isInitialized) {
|
|
214
|
+
await this.initialize();
|
|
215
|
+
}
|
|
216
|
+
const allJobs = await this.storage.getAllJobs();
|
|
217
|
+
const failedJobs = await this.dlq.getAll();
|
|
218
|
+
return {
|
|
219
|
+
active: this.activeJobs.size,
|
|
220
|
+
pending: allJobs.filter(j => j.status === 'pending').length,
|
|
221
|
+
failed: failedJobs.length,
|
|
222
|
+
completed: allJobs.filter(j => j.status === 'completed').length,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Set up graceful shutdown handlers
|
|
227
|
+
*/
|
|
228
|
+
setupGracefulShutdown() {
|
|
229
|
+
const shutdown = async () => {
|
|
230
|
+
console.log('[Queue] Graceful shutdown initiated...');
|
|
231
|
+
await this.shutdown();
|
|
232
|
+
process.exit(0);
|
|
233
|
+
};
|
|
234
|
+
process.on('SIGINT', shutdown);
|
|
235
|
+
process.on('SIGTERM', shutdown);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Gracefully shutdown the queue
|
|
239
|
+
*/
|
|
240
|
+
async shutdown() {
|
|
241
|
+
if (this.isShuttingDown) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this.isShuttingDown = true;
|
|
245
|
+
// Stop accepting new jobs
|
|
246
|
+
this.scheduler.stop();
|
|
247
|
+
// Wait for active jobs to complete
|
|
248
|
+
console.log(`[Queue] Waiting for ${this.activeJobs.size} active jobs to complete...`);
|
|
249
|
+
while (this.activeJobs.size > 0) {
|
|
250
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
251
|
+
}
|
|
252
|
+
// Terminate all workers
|
|
253
|
+
console.log('[Queue] Terminating workers...');
|
|
254
|
+
await Promise.all(this.workers.map(worker => worker.terminate()));
|
|
255
|
+
// Close storage
|
|
256
|
+
console.log('[Queue] Closing storage...');
|
|
257
|
+
await this.storage.close();
|
|
258
|
+
console.log('[Queue] Shutdown complete');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=Queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Queue.js","sourceRoot":"","sources":["../../../src/queue/Queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { StorageInterface } from '../types.js';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
/**
|
|
4
|
+
* Scheduler that periodically checks for pending jobs
|
|
5
|
+
* Runs every 200ms and emits events for jobs ready to process
|
|
6
|
+
*/
|
|
7
|
+
export declare class Scheduler extends EventEmitter {
|
|
8
|
+
private storage;
|
|
9
|
+
private interval;
|
|
10
|
+
private isRunning;
|
|
11
|
+
private readonly tickInterval;
|
|
12
|
+
constructor(storage: StorageInterface);
|
|
13
|
+
/**
|
|
14
|
+
* Start the scheduler
|
|
15
|
+
*/
|
|
16
|
+
start(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Stop the scheduler
|
|
19
|
+
*/
|
|
20
|
+
stop(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Single tick - check for pending jobs
|
|
23
|
+
*/
|
|
24
|
+
private tick;
|
|
25
|
+
/**
|
|
26
|
+
* Check if scheduler is running
|
|
27
|
+
*/
|
|
28
|
+
getIsRunning(): boolean;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=Scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduler.d.ts","sourceRoot":"","sources":["../../../src/queue/Scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;GAGG;AACH,qBAAa,SAAU,SAAQ,YAAY;IACzC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAEhC,OAAO,EAAE,gBAAgB;IAOrC;;OAEG;IACH,KAAK,IAAI,IAAI;IAab;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;YACW,IAAI;IAclB;;OAEG;IACH,YAAY,IAAI,OAAO;CAGxB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
/**
|
|
3
|
+
* Scheduler that periodically checks for pending jobs
|
|
4
|
+
* Runs every 200ms and emits events for jobs ready to process
|
|
5
|
+
*/
|
|
6
|
+
export class Scheduler extends EventEmitter {
|
|
7
|
+
storage;
|
|
8
|
+
interval;
|
|
9
|
+
isRunning;
|
|
10
|
+
tickInterval = 200; // 200ms
|
|
11
|
+
constructor(storage) {
|
|
12
|
+
super();
|
|
13
|
+
this.storage = storage;
|
|
14
|
+
this.interval = null;
|
|
15
|
+
this.isRunning = false;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Start the scheduler
|
|
19
|
+
*/
|
|
20
|
+
start() {
|
|
21
|
+
if (this.isRunning) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
this.isRunning = true;
|
|
25
|
+
this.interval = setInterval(() => {
|
|
26
|
+
this.tick().catch(error => {
|
|
27
|
+
this.emit('error', error);
|
|
28
|
+
});
|
|
29
|
+
}, this.tickInterval);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Stop the scheduler
|
|
33
|
+
*/
|
|
34
|
+
stop() {
|
|
35
|
+
if (this.interval) {
|
|
36
|
+
clearInterval(this.interval);
|
|
37
|
+
this.interval = null;
|
|
38
|
+
}
|
|
39
|
+
this.isRunning = false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Single tick - check for pending jobs
|
|
43
|
+
*/
|
|
44
|
+
async tick() {
|
|
45
|
+
if (!this.isRunning) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const pendingJobs = await this.storage.getPendingJobs(now);
|
|
50
|
+
for (const job of pendingJobs) {
|
|
51
|
+
// Emit job-ready event for each pending job
|
|
52
|
+
this.emit('job-ready', job);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if scheduler is running
|
|
57
|
+
*/
|
|
58
|
+
getIsRunning() {
|
|
59
|
+
return this.isRunning;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=Scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduler.js","sourceRoot":"","sources":["../../../src/queue/Scheduler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,YAAY;IACjC,OAAO,CAAmB;IAC1B,QAAQ,CAAwB;IAChC,SAAS,CAAU;IACV,YAAY,GAAW,GAAG,CAAC,CAAC,QAAQ;IAErD,YAAY,OAAyB;QACnC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;gBACxB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAE3D,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,4CAA4C;YAC5C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StorageInterface, JobData } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* File-based storage implementation with crash recovery
|
|
4
|
+
* Uses append-only log for durability
|
|
5
|
+
*/
|
|
6
|
+
export declare class FileStore implements StorageInterface {
|
|
7
|
+
private filePath;
|
|
8
|
+
private deadLetterPath;
|
|
9
|
+
private jobs;
|
|
10
|
+
private deadLetterJobs;
|
|
11
|
+
private writeStream;
|
|
12
|
+
private dlqWriteStream;
|
|
13
|
+
constructor(filePath: string);
|
|
14
|
+
/**
|
|
15
|
+
* Initialize storage and perform crash recovery
|
|
16
|
+
*/
|
|
17
|
+
initialize(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Ensure parent directory exists
|
|
20
|
+
*/
|
|
21
|
+
private ensureDirectory;
|
|
22
|
+
/**
|
|
23
|
+
* Load jobs from log file
|
|
24
|
+
*/
|
|
25
|
+
private loadJobsFromFile;
|
|
26
|
+
/**
|
|
27
|
+
* Perform crash recovery:
|
|
28
|
+
* - Any job with status "processing" is marked as "pending"
|
|
29
|
+
* - Increment attempts by 1
|
|
30
|
+
* - Set nextRunAt to now
|
|
31
|
+
*/
|
|
32
|
+
private performCrashRecovery;
|
|
33
|
+
/**
|
|
34
|
+
* Rewrite the entire job file (used after crash recovery)
|
|
35
|
+
*/
|
|
36
|
+
private rewriteJobFile;
|
|
37
|
+
/**
|
|
38
|
+
* Append a job to the log file atomically
|
|
39
|
+
*/
|
|
40
|
+
private appendToLog;
|
|
41
|
+
/**
|
|
42
|
+
* Append a job to the dead letter queue log
|
|
43
|
+
*/
|
|
44
|
+
private appendToDLQ;
|
|
45
|
+
addJob(job: JobData): Promise<void>;
|
|
46
|
+
updateJob(job: JobData): Promise<void>;
|
|
47
|
+
getPendingJobs(now: number): Promise<JobData[]>;
|
|
48
|
+
getJob(id: string): Promise<JobData | null>;
|
|
49
|
+
getAllJobs(): Promise<JobData[]>;
|
|
50
|
+
moveToDeadLetter(job: JobData): Promise<void>;
|
|
51
|
+
getFailedJobs(): Promise<JobData[]>;
|
|
52
|
+
removeFromDeadLetter(jobId: string): Promise<void>;
|
|
53
|
+
close(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=FileStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileStore.d.ts","sourceRoot":"","sources":["../../../src/storage/FileStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKxD;;;GAGG;AACH,qBAAa,SAAU,YAAW,gBAAgB;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,cAAc,CAAqB;gBAE/B,QAAQ,EAAE,MAAM;IAS5B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC;;OAEG;YACW,eAAe;IAS7B;;OAEG;YACW,gBAAgB;IAyC9B;;;;;OAKG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,cAAc;IAU5B;;OAEG;YACW,WAAW;IAkBzB;;OAEG;YACW,WAAW;IAkBnB,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnC,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtC,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAa/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAK3C,UAAU,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAIhC,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,aAAa,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAInC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBlD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB7B"}
|