@nicnocquee/dataqueue 1.16.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/cli.cjs +38 -0
- package/dist/index.cjs +663 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +227 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +659 -0
- package/dist/index.js.map +1 -0
- package/migrations/1751131910823_initial.sql +32 -0
- package/migrations/1751131910825_add_timeout_seconds_to_job_queue.sql +7 -0
- package/migrations/1751186053000_add_job_events_table.sql +29 -0
- package/package.json +67 -0
- package/src/db-util.ts +7 -0
- package/src/index.test.ts +282 -0
- package/src/index.ts +97 -0
- package/src/log-context.ts +20 -0
- package/src/processor.test.ts +478 -0
- package/src/processor.ts +242 -0
- package/src/queue.test.ts +502 -0
- package/src/queue.ts +547 -0
- package/src/test-util.ts +56 -0
- package/src/types.ts +247 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
|
|
3
|
+
// Utility type for job type keys
|
|
4
|
+
export type JobType<PayloadMap> = keyof PayloadMap & string;
|
|
5
|
+
|
|
6
|
+
export interface JobOptions<PayloadMap, T extends JobType<PayloadMap>> {
|
|
7
|
+
job_type: T;
|
|
8
|
+
payload: PayloadMap[T];
|
|
9
|
+
max_attempts?: number;
|
|
10
|
+
priority?: number;
|
|
11
|
+
run_at?: Date | null;
|
|
12
|
+
/**
|
|
13
|
+
* Timeout for this job in milliseconds. If not set, uses the processor default or unlimited.
|
|
14
|
+
*/
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export enum JobEventType {
|
|
19
|
+
Added = 'added',
|
|
20
|
+
Processing = 'processing',
|
|
21
|
+
Completed = 'completed',
|
|
22
|
+
Failed = 'failed',
|
|
23
|
+
Cancelled = 'cancelled',
|
|
24
|
+
Retried = 'retried',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface JobEvent {
|
|
28
|
+
id: number;
|
|
29
|
+
job_id: number;
|
|
30
|
+
event_type: JobEventType;
|
|
31
|
+
created_at: Date;
|
|
32
|
+
metadata: any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export enum FailureReason {
|
|
36
|
+
Timeout = 'timeout',
|
|
37
|
+
HandlerError = 'handler_error',
|
|
38
|
+
NoHandler = 'no_handler',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface JobRecord<PayloadMap, T extends JobType<PayloadMap>> {
|
|
42
|
+
id: number;
|
|
43
|
+
job_type: T;
|
|
44
|
+
payload: PayloadMap[T];
|
|
45
|
+
status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
|
46
|
+
created_at: Date;
|
|
47
|
+
updated_at: Date;
|
|
48
|
+
locked_at: Date | null;
|
|
49
|
+
locked_by: string | null;
|
|
50
|
+
attempts: number;
|
|
51
|
+
max_attempts: number;
|
|
52
|
+
next_attempt_at: Date | null;
|
|
53
|
+
priority: number;
|
|
54
|
+
run_at: Date;
|
|
55
|
+
pending_reason?: string | null;
|
|
56
|
+
error_history?: { message: string; timestamp: string }[];
|
|
57
|
+
/**
|
|
58
|
+
* Timeout for this job in milliseconds (null means no timeout).
|
|
59
|
+
*/
|
|
60
|
+
timeout_ms?: number | null;
|
|
61
|
+
/**
|
|
62
|
+
* The reason for the last failure, if any.
|
|
63
|
+
*/
|
|
64
|
+
failure_reason?: FailureReason | null;
|
|
65
|
+
/**
|
|
66
|
+
* The time the job was completed, if completed.
|
|
67
|
+
*/
|
|
68
|
+
completed_at: Date | null;
|
|
69
|
+
/**
|
|
70
|
+
* The time the job was first picked up for processing.
|
|
71
|
+
*/
|
|
72
|
+
started_at: Date | null;
|
|
73
|
+
/**
|
|
74
|
+
* The time the job was last retried.
|
|
75
|
+
*/
|
|
76
|
+
last_retried_at: Date | null;
|
|
77
|
+
/**
|
|
78
|
+
* The time the job last failed.
|
|
79
|
+
*/
|
|
80
|
+
last_failed_at: Date | null;
|
|
81
|
+
/**
|
|
82
|
+
* The time the job was last cancelled.
|
|
83
|
+
*/
|
|
84
|
+
last_cancelled_at: Date | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type JobHandler<PayloadMap, T extends keyof PayloadMap> = (
|
|
88
|
+
payload: PayloadMap[T],
|
|
89
|
+
signal: AbortSignal,
|
|
90
|
+
) => Promise<void>;
|
|
91
|
+
|
|
92
|
+
export type JobHandlers<PayloadMap> = {
|
|
93
|
+
[K in keyof PayloadMap]: JobHandler<PayloadMap, K>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export interface ProcessorOptions {
|
|
97
|
+
workerId?: string;
|
|
98
|
+
/**
|
|
99
|
+
* The number of jobs to process at a time.
|
|
100
|
+
* - If not provided, the processor will process 10 jobs at a time.
|
|
101
|
+
* - In serverless functions, it's better to process less jobs at a time since serverless functions are charged by the second and have a timeout.
|
|
102
|
+
*/
|
|
103
|
+
batchSize?: number;
|
|
104
|
+
/**
|
|
105
|
+
* The maximum number of jobs to process in parallel per batch.
|
|
106
|
+
* - If not provided, all jobs in the batch are processed in parallel.
|
|
107
|
+
* - Set to 1 to process jobs sequentially.
|
|
108
|
+
* - Set to a lower value to avoid resource exhaustion.
|
|
109
|
+
*/
|
|
110
|
+
concurrency?: number;
|
|
111
|
+
/**
|
|
112
|
+
* The interval in milliseconds to poll for new jobs.
|
|
113
|
+
* - If not provided, the processor will process jobs every 5 seconds when startInBackground is called.
|
|
114
|
+
* - In serverless functions, it's better to leave this empty.
|
|
115
|
+
* - If you call start instead of startInBackground, the pollInterval is ignored.
|
|
116
|
+
*/
|
|
117
|
+
pollInterval?: number;
|
|
118
|
+
onError?: (error: Error) => void;
|
|
119
|
+
verbose?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Only process jobs with this job type (string or array of strings). If omitted, all job types are processed.
|
|
122
|
+
*/
|
|
123
|
+
jobType?: string | string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface Processor {
|
|
127
|
+
/**
|
|
128
|
+
* Start the job processor in the background.
|
|
129
|
+
* - This will run periodically (every pollInterval milliseconds or 5 seconds if not provided) and process jobs (as many as batchSize) as they become available.
|
|
130
|
+
* - **You have to call the stop method to stop the processor.**
|
|
131
|
+
* - Handlers are provided per-processor when calling createProcessor.
|
|
132
|
+
* - In serverless functions, it's recommended to call start instead and await it to finish.
|
|
133
|
+
*/
|
|
134
|
+
startInBackground: () => void;
|
|
135
|
+
/**
|
|
136
|
+
* Stop the job processor that runs in the background.
|
|
137
|
+
*/
|
|
138
|
+
stop: () => void;
|
|
139
|
+
/**
|
|
140
|
+
* Check if the job processor is running.
|
|
141
|
+
*/
|
|
142
|
+
isRunning: () => boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Start the job processor synchronously.
|
|
145
|
+
* - This will process jobs (as many as batchSize) immediately and then stop. The pollInterval is ignored.
|
|
146
|
+
* - In serverless functions, it's recommended to use this instead of startInBackground.
|
|
147
|
+
* - Returns the number of jobs processed.
|
|
148
|
+
*/
|
|
149
|
+
start: () => Promise<number>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface JobQueueConfig {
|
|
153
|
+
databaseConfig: {
|
|
154
|
+
connectionString?: string;
|
|
155
|
+
host?: string;
|
|
156
|
+
port?: number;
|
|
157
|
+
database?: string;
|
|
158
|
+
user?: string;
|
|
159
|
+
password?: string;
|
|
160
|
+
ssl?: any;
|
|
161
|
+
};
|
|
162
|
+
verbose?: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface JobQueue<PayloadMap> {
|
|
166
|
+
/**
|
|
167
|
+
* Add a job to the job queue.
|
|
168
|
+
*/
|
|
169
|
+
addJob: <T extends JobType<PayloadMap>>(
|
|
170
|
+
job: JobOptions<PayloadMap, T>,
|
|
171
|
+
) => Promise<number>;
|
|
172
|
+
/**
|
|
173
|
+
* Get a job by its ID.
|
|
174
|
+
*/
|
|
175
|
+
getJob: <T extends JobType<PayloadMap>>(
|
|
176
|
+
id: number,
|
|
177
|
+
) => Promise<JobRecord<PayloadMap, T> | null>;
|
|
178
|
+
/**
|
|
179
|
+
* Get jobs by their status, with pagination.
|
|
180
|
+
* - If no limit is provided, all jobs are returned.
|
|
181
|
+
* - If no offset is provided, the first page is returned.
|
|
182
|
+
* - The jobs are returned in descending order of created_at.
|
|
183
|
+
*/
|
|
184
|
+
getJobsByStatus: <T extends JobType<PayloadMap>>(
|
|
185
|
+
status: string,
|
|
186
|
+
limit?: number,
|
|
187
|
+
offset?: number,
|
|
188
|
+
) => Promise<JobRecord<PayloadMap, T>[]>;
|
|
189
|
+
/**
|
|
190
|
+
* Get all jobs.
|
|
191
|
+
*/
|
|
192
|
+
getAllJobs: <T extends JobType<PayloadMap>>(
|
|
193
|
+
limit?: number,
|
|
194
|
+
offset?: number,
|
|
195
|
+
) => Promise<JobRecord<PayloadMap, T>[]>;
|
|
196
|
+
/**
|
|
197
|
+
* Retry a job given its ID.
|
|
198
|
+
* - This will set the job status back to 'pending', clear the locked_at and locked_by, and allow it to be picked up by other workers.
|
|
199
|
+
*/
|
|
200
|
+
retryJob: (jobId: number) => Promise<void>;
|
|
201
|
+
/**
|
|
202
|
+
* Cleanup jobs that are older than the specified number of days.
|
|
203
|
+
*/
|
|
204
|
+
cleanupOldJobs: (daysToKeep?: number) => Promise<number>;
|
|
205
|
+
/**
|
|
206
|
+
* Cancel a job given its ID.
|
|
207
|
+
* - This will set the job status to 'cancelled' and clear the locked_at and locked_by.
|
|
208
|
+
*/
|
|
209
|
+
cancelJob: (jobId: number) => Promise<void>;
|
|
210
|
+
/**
|
|
211
|
+
* Reclaim stuck jobs.
|
|
212
|
+
* - If a process (e.g., API route or worker) crashes after marking a job as 'processing' but before completing it, the job can remain stuck in the 'processing' state indefinitely. This can happen if the process is killed or encounters an unhandled error after updating the job status but before marking it as 'completed' or 'failed'.
|
|
213
|
+
* - This function will set the job status back to 'pending', clear the locked_at and locked_by, and allow it to be picked up by other workers.
|
|
214
|
+
* - The default max processing time is 10 minutes.
|
|
215
|
+
*/
|
|
216
|
+
reclaimStuckJobs: (maxProcessingTimeMinutes?: number) => Promise<number>;
|
|
217
|
+
/**
|
|
218
|
+
* Cancel all upcoming jobs that match the filters.
|
|
219
|
+
* - If no filters are provided, all upcoming jobs are cancelled.
|
|
220
|
+
* - If filters are provided, only jobs that match the filters are cancelled.
|
|
221
|
+
* - The filters are:
|
|
222
|
+
* - job_type: The job type to cancel.
|
|
223
|
+
* - priority: The priority of the job to cancel.
|
|
224
|
+
* - run_at: The time the job is scheduled to run at.
|
|
225
|
+
*/
|
|
226
|
+
cancelAllUpcomingJobs: (filters?: {
|
|
227
|
+
job_type?: string;
|
|
228
|
+
priority?: number;
|
|
229
|
+
run_at?: Date;
|
|
230
|
+
}) => Promise<number>;
|
|
231
|
+
/**
|
|
232
|
+
* Create a job processor. Handlers must be provided per-processor.
|
|
233
|
+
*/
|
|
234
|
+
createProcessor: (
|
|
235
|
+
handlers: JobHandlers<PayloadMap>,
|
|
236
|
+
options?: ProcessorOptions,
|
|
237
|
+
) => Processor;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get the job events for a job.
|
|
241
|
+
*/
|
|
242
|
+
getJobEvents: (jobId: number) => Promise<JobEvent[]>;
|
|
243
|
+
/**
|
|
244
|
+
* Get the database pool.
|
|
245
|
+
*/
|
|
246
|
+
getPool: () => Pool;
|
|
247
|
+
}
|