@nicnocquee/dataqueue 1.30.0 → 1.32.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/dist/index.cjs +2531 -1283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +367 -17
- package/dist/index.d.ts +367 -17
- package/dist/index.js +2530 -1284
- package/dist/index.js.map +1 -1
- package/migrations/1781200000004_create_cron_schedules_table.sql +33 -0
- package/package.json +3 -2
- package/src/backend.ts +139 -4
- package/src/backends/postgres.ts +676 -30
- package/src/backends/redis-scripts.ts +197 -22
- package/src/backends/redis.test.ts +971 -0
- package/src/backends/redis.ts +789 -22
- package/src/cron.test.ts +126 -0
- package/src/cron.ts +40 -0
- package/src/index.test.ts +361 -0
- package/src/index.ts +165 -29
- package/src/processor.ts +36 -97
- package/src/queue.test.ts +29 -0
- package/src/queue.ts +19 -251
- package/src/types.ts +177 -10
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Create cron_schedules table for recurring job definitions
|
|
2
|
+
|
|
3
|
+
-- Up Migration
|
|
4
|
+
CREATE TABLE IF NOT EXISTS cron_schedules (
|
|
5
|
+
id SERIAL PRIMARY KEY,
|
|
6
|
+
schedule_name VARCHAR(255) NOT NULL UNIQUE,
|
|
7
|
+
cron_expression VARCHAR(255) NOT NULL,
|
|
8
|
+
job_type VARCHAR(255) NOT NULL,
|
|
9
|
+
payload JSONB NOT NULL DEFAULT '{}',
|
|
10
|
+
max_attempts INT DEFAULT 3,
|
|
11
|
+
priority INT DEFAULT 0,
|
|
12
|
+
timeout_ms INT,
|
|
13
|
+
force_kill_on_timeout BOOLEAN DEFAULT FALSE,
|
|
14
|
+
tags TEXT[],
|
|
15
|
+
timezone VARCHAR(100) DEFAULT 'UTC',
|
|
16
|
+
allow_overlap BOOLEAN DEFAULT FALSE,
|
|
17
|
+
status VARCHAR(50) DEFAULT 'active',
|
|
18
|
+
last_enqueued_at TIMESTAMPTZ,
|
|
19
|
+
last_job_id INT,
|
|
20
|
+
next_run_at TIMESTAMPTZ,
|
|
21
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
22
|
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_cron_schedules_status ON cron_schedules(status);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_cron_schedules_next_run_at ON cron_schedules(next_run_at);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_cron_schedules_name ON cron_schedules(schedule_name);
|
|
28
|
+
|
|
29
|
+
-- Down Migration
|
|
30
|
+
DROP INDEX IF EXISTS idx_cron_schedules_name;
|
|
31
|
+
DROP INDEX IF EXISTS idx_cron_schedules_next_run_at;
|
|
32
|
+
DROP INDEX IF EXISTS idx_cron_schedules_status;
|
|
33
|
+
DROP TABLE IF EXISTS cron_schedules;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nicnocquee/dataqueue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.0",
|
|
4
4
|
"description": "PostgreSQL or Redis-backed job queue for Node.js applications with support for serverless environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"directory": "packages/dataqueue"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"croner": "^10.0.1",
|
|
46
47
|
"pg": "^8.0.0",
|
|
47
48
|
"pg-connection-string": "^2.9.1"
|
|
48
49
|
},
|
|
@@ -53,8 +54,8 @@
|
|
|
53
54
|
"ioredis": "^5.9.3",
|
|
54
55
|
"node-pg-migrate": "^8.0.3",
|
|
55
56
|
"pnpm": "^9.0.0",
|
|
56
|
-
"ts-node": "^10.9.2",
|
|
57
57
|
"prettier": "^3.6.2",
|
|
58
|
+
"ts-node": "^10.9.2",
|
|
58
59
|
"tsup": "^8.5.0",
|
|
59
60
|
"turbo": "^1.13.0",
|
|
60
61
|
"typescript": "^5.8.3",
|
package/src/backend.ts
CHANGED
|
@@ -6,6 +6,11 @@ import {
|
|
|
6
6
|
FailureReason,
|
|
7
7
|
TagQueryMode,
|
|
8
8
|
JobType,
|
|
9
|
+
CronScheduleRecord,
|
|
10
|
+
CronScheduleStatus,
|
|
11
|
+
EditCronScheduleOptions,
|
|
12
|
+
WaitpointRecord,
|
|
13
|
+
CreateTokenOptions,
|
|
9
14
|
} from './types.js';
|
|
10
15
|
|
|
11
16
|
/**
|
|
@@ -36,6 +41,25 @@ export interface JobUpdates {
|
|
|
36
41
|
tags?: string[] | null;
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Input shape for creating a cron schedule in the backend.
|
|
46
|
+
* This is the backend-level version of CronScheduleOptions.
|
|
47
|
+
*/
|
|
48
|
+
export interface CronScheduleInput {
|
|
49
|
+
scheduleName: string;
|
|
50
|
+
cronExpression: string;
|
|
51
|
+
jobType: string;
|
|
52
|
+
payload: any;
|
|
53
|
+
maxAttempts: number;
|
|
54
|
+
priority: number;
|
|
55
|
+
timeoutMs: number | null;
|
|
56
|
+
forceKillOnTimeout: boolean;
|
|
57
|
+
tags: string[] | undefined;
|
|
58
|
+
timezone: string;
|
|
59
|
+
allowOverlap: boolean;
|
|
60
|
+
nextRunAt: Date | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
/**
|
|
40
64
|
* Abstract backend interface that both PostgreSQL and Redis implement.
|
|
41
65
|
* All storage operations go through this interface so the processor
|
|
@@ -127,11 +151,11 @@ export interface QueueBackend {
|
|
|
127
151
|
updates: JobUpdates,
|
|
128
152
|
): Promise<number>;
|
|
129
153
|
|
|
130
|
-
/** Delete completed jobs older than N days. Returns count deleted. */
|
|
131
|
-
cleanupOldJobs(daysToKeep?: number): Promise<number>;
|
|
154
|
+
/** Delete completed jobs older than N days. Deletes in batches for scale safety. Returns count deleted. */
|
|
155
|
+
cleanupOldJobs(daysToKeep?: number, batchSize?: number): Promise<number>;
|
|
132
156
|
|
|
133
|
-
/** Delete job events older than N days. Returns count deleted. */
|
|
134
|
-
cleanupOldJobEvents(daysToKeep?: number): Promise<number>;
|
|
157
|
+
/** Delete job events older than N days. Deletes in batches for scale safety. Returns count deleted. */
|
|
158
|
+
cleanupOldJobEvents(daysToKeep?: number, batchSize?: number): Promise<number>;
|
|
135
159
|
|
|
136
160
|
/** Reclaim jobs stuck in 'processing' for too long. Returns count. */
|
|
137
161
|
reclaimStuckJobs(maxProcessingTimeMinutes?: number): Promise<number>;
|
|
@@ -153,6 +177,117 @@ export interface QueueBackend {
|
|
|
153
177
|
/** Get all events for a job, ordered by createdAt ASC. */
|
|
154
178
|
getJobEvents(jobId: number): Promise<JobEvent[]>;
|
|
155
179
|
|
|
180
|
+
// ── Cron schedules ──────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/** Create a cron schedule and return its ID. */
|
|
183
|
+
addCronSchedule(input: CronScheduleInput): Promise<number>;
|
|
184
|
+
|
|
185
|
+
/** Get a cron schedule by ID, or null if not found. */
|
|
186
|
+
getCronSchedule(id: number): Promise<CronScheduleRecord | null>;
|
|
187
|
+
|
|
188
|
+
/** Get a cron schedule by its unique name, or null if not found. */
|
|
189
|
+
getCronScheduleByName(name: string): Promise<CronScheduleRecord | null>;
|
|
190
|
+
|
|
191
|
+
/** List cron schedules, optionally filtered by status. */
|
|
192
|
+
listCronSchedules(status?: CronScheduleStatus): Promise<CronScheduleRecord[]>;
|
|
193
|
+
|
|
194
|
+
/** Delete a cron schedule by ID. */
|
|
195
|
+
removeCronSchedule(id: number): Promise<void>;
|
|
196
|
+
|
|
197
|
+
/** Pause a cron schedule. */
|
|
198
|
+
pauseCronSchedule(id: number): Promise<void>;
|
|
199
|
+
|
|
200
|
+
/** Resume a cron schedule. */
|
|
201
|
+
resumeCronSchedule(id: number): Promise<void>;
|
|
202
|
+
|
|
203
|
+
/** Edit a cron schedule. */
|
|
204
|
+
editCronSchedule(
|
|
205
|
+
id: number,
|
|
206
|
+
updates: EditCronScheduleOptions,
|
|
207
|
+
nextRunAt?: Date | null,
|
|
208
|
+
): Promise<void>;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Atomically fetch all active cron schedules whose nextRunAt <= now.
|
|
212
|
+
* In PostgreSQL this uses FOR UPDATE SKIP LOCKED to prevent duplicate enqueuing.
|
|
213
|
+
*/
|
|
214
|
+
getDueCronSchedules(): Promise<CronScheduleRecord[]>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update a cron schedule after a job has been enqueued.
|
|
218
|
+
* Sets lastEnqueuedAt, lastJobId, and advances nextRunAt.
|
|
219
|
+
*/
|
|
220
|
+
updateCronScheduleAfterEnqueue(
|
|
221
|
+
id: number,
|
|
222
|
+
lastEnqueuedAt: Date,
|
|
223
|
+
lastJobId: number,
|
|
224
|
+
nextRunAt: Date | null,
|
|
225
|
+
): Promise<void>;
|
|
226
|
+
|
|
227
|
+
// ── Wait / step-data support ────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Transition a job from 'processing' to 'waiting' status.
|
|
231
|
+
* Persists step data so the handler can resume from where it left off.
|
|
232
|
+
*
|
|
233
|
+
* @param jobId - The job to pause.
|
|
234
|
+
* @param options - Wait configuration including optional waitUntil date, token ID, and step data.
|
|
235
|
+
*/
|
|
236
|
+
waitJob(
|
|
237
|
+
jobId: number,
|
|
238
|
+
options: {
|
|
239
|
+
waitUntil?: Date;
|
|
240
|
+
waitTokenId?: string;
|
|
241
|
+
stepData: Record<string, any>;
|
|
242
|
+
},
|
|
243
|
+
): Promise<void>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Persist step data for a job. Called after each `ctx.run()` step completes
|
|
247
|
+
* to save intermediate progress. Best-effort: should not throw.
|
|
248
|
+
*
|
|
249
|
+
* @param jobId - The job to update.
|
|
250
|
+
* @param stepData - The step data to persist.
|
|
251
|
+
*/
|
|
252
|
+
updateStepData(jobId: number, stepData: Record<string, any>): Promise<void>;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create a waitpoint token that can pause a job until an external signal completes it.
|
|
256
|
+
*
|
|
257
|
+
* @param jobId - The job ID to associate with the token (null if created outside a handler).
|
|
258
|
+
* @param options - Optional timeout string (e.g. '10m', '1h') and tags.
|
|
259
|
+
* @returns The created waitpoint with its unique ID.
|
|
260
|
+
*/
|
|
261
|
+
createWaitpoint(
|
|
262
|
+
jobId: number | null,
|
|
263
|
+
options?: CreateTokenOptions,
|
|
264
|
+
): Promise<{ id: string }>;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Complete a waitpoint token, optionally providing output data.
|
|
268
|
+
* Moves the associated job from 'waiting' back to 'pending' so it gets picked up.
|
|
269
|
+
*
|
|
270
|
+
* @param tokenId - The waitpoint token ID to complete.
|
|
271
|
+
* @param data - Optional data to pass to the waiting handler.
|
|
272
|
+
*/
|
|
273
|
+
completeWaitpoint(tokenId: string, data?: any): Promise<void>;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Retrieve a waitpoint token by its ID.
|
|
277
|
+
*
|
|
278
|
+
* @param tokenId - The waitpoint token ID to look up.
|
|
279
|
+
* @returns The waitpoint record, or null if not found.
|
|
280
|
+
*/
|
|
281
|
+
getWaitpoint(tokenId: string): Promise<WaitpointRecord | null>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Expire timed-out waitpoint tokens and move their associated jobs back to 'pending'.
|
|
285
|
+
* Should be called periodically (e.g., alongside reclaimStuckJobs).
|
|
286
|
+
*
|
|
287
|
+
* @returns The number of tokens that were expired.
|
|
288
|
+
*/
|
|
289
|
+
expireTimedOutWaitpoints(): Promise<number>;
|
|
290
|
+
|
|
156
291
|
// ── Internal helpers ──────────────────────────────────────────────────
|
|
157
292
|
|
|
158
293
|
/** Set a pending reason for unpicked jobs of a given type. */
|