@nicnocquee/dataqueue 1.24.0 → 1.26.0-beta.20260223195940

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.
Files changed (72) hide show
  1. package/README.md +44 -0
  2. package/ai/build-docs-content.ts +96 -0
  3. package/ai/build-llms-full.ts +42 -0
  4. package/ai/docs-content.json +278 -0
  5. package/ai/rules/advanced.md +132 -0
  6. package/ai/rules/basic.md +159 -0
  7. package/ai/rules/react-dashboard.md +83 -0
  8. package/ai/skills/dataqueue-advanced/SKILL.md +320 -0
  9. package/ai/skills/dataqueue-core/SKILL.md +234 -0
  10. package/ai/skills/dataqueue-react/SKILL.md +189 -0
  11. package/dist/cli.cjs +1149 -14
  12. package/dist/cli.cjs.map +1 -1
  13. package/dist/cli.d.cts +66 -1
  14. package/dist/cli.d.ts +66 -1
  15. package/dist/cli.js +1146 -13
  16. package/dist/cli.js.map +1 -1
  17. package/dist/index.cjs +4630 -928
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +1033 -15
  20. package/dist/index.d.ts +1033 -15
  21. package/dist/index.js +4626 -929
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp-server.cjs +186 -0
  24. package/dist/mcp-server.cjs.map +1 -0
  25. package/dist/mcp-server.d.cts +32 -0
  26. package/dist/mcp-server.d.ts +32 -0
  27. package/dist/mcp-server.js +175 -0
  28. package/dist/mcp-server.js.map +1 -0
  29. package/migrations/1751131910825_add_timeout_seconds_to_job_queue.sql +2 -2
  30. package/migrations/1751186053000_add_job_events_table.sql +12 -8
  31. package/migrations/1751984773000_add_tags_to_job_queue.sql +1 -1
  32. package/migrations/1765809419000_add_force_kill_on_timeout_to_job_queue.sql +1 -1
  33. package/migrations/1771100000000_add_idempotency_key_to_job_queue.sql +7 -0
  34. package/migrations/1781200000000_add_wait_support.sql +12 -0
  35. package/migrations/1781200000001_create_waitpoints_table.sql +18 -0
  36. package/migrations/1781200000002_add_performance_indexes.sql +34 -0
  37. package/migrations/1781200000003_add_progress_to_job_queue.sql +7 -0
  38. package/migrations/1781200000004_create_cron_schedules_table.sql +33 -0
  39. package/migrations/1781200000005_add_retry_config_to_job_queue.sql +17 -0
  40. package/package.json +40 -23
  41. package/src/backend.ts +328 -0
  42. package/src/backends/postgres.ts +2040 -0
  43. package/src/backends/redis-scripts.ts +865 -0
  44. package/src/backends/redis.test.ts +1906 -0
  45. package/src/backends/redis.ts +1792 -0
  46. package/src/cli.test.ts +82 -6
  47. package/src/cli.ts +73 -10
  48. package/src/cron.test.ts +126 -0
  49. package/src/cron.ts +40 -0
  50. package/src/db-util.ts +4 -2
  51. package/src/index.test.ts +688 -1
  52. package/src/index.ts +277 -39
  53. package/src/init-command.test.ts +449 -0
  54. package/src/init-command.ts +709 -0
  55. package/src/install-mcp-command.test.ts +216 -0
  56. package/src/install-mcp-command.ts +185 -0
  57. package/src/install-rules-command.test.ts +218 -0
  58. package/src/install-rules-command.ts +233 -0
  59. package/src/install-skills-command.test.ts +176 -0
  60. package/src/install-skills-command.ts +124 -0
  61. package/src/mcp-server.test.ts +162 -0
  62. package/src/mcp-server.ts +231 -0
  63. package/src/processor.test.ts +559 -18
  64. package/src/processor.ts +456 -49
  65. package/src/queue.test.ts +682 -6
  66. package/src/queue.ts +135 -944
  67. package/src/supervisor.test.ts +340 -0
  68. package/src/supervisor.ts +162 -0
  69. package/src/test-util.ts +32 -0
  70. package/src/types.ts +726 -17
  71. package/src/wait.test.ts +698 -0
  72. package/LICENSE +0 -21
@@ -0,0 +1,17 @@
1
+ -- Up Migration
2
+ ALTER TABLE job_queue ADD COLUMN IF NOT EXISTS retry_delay INT;
3
+ ALTER TABLE job_queue ADD COLUMN IF NOT EXISTS retry_backoff BOOLEAN;
4
+ ALTER TABLE job_queue ADD COLUMN IF NOT EXISTS retry_delay_max INT;
5
+
6
+ ALTER TABLE cron_schedules ADD COLUMN IF NOT EXISTS retry_delay INT;
7
+ ALTER TABLE cron_schedules ADD COLUMN IF NOT EXISTS retry_backoff BOOLEAN;
8
+ ALTER TABLE cron_schedules ADD COLUMN IF NOT EXISTS retry_delay_max INT;
9
+
10
+ -- Down Migration
11
+ ALTER TABLE job_queue DROP COLUMN IF EXISTS retry_delay;
12
+ ALTER TABLE job_queue DROP COLUMN IF EXISTS retry_backoff;
13
+ ALTER TABLE job_queue DROP COLUMN IF EXISTS retry_delay_max;
14
+
15
+ ALTER TABLE cron_schedules DROP COLUMN IF EXISTS retry_delay;
16
+ ALTER TABLE cron_schedules DROP COLUMN IF EXISTS retry_backoff;
17
+ ALTER TABLE cron_schedules DROP COLUMN IF EXISTS retry_delay_max;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nicnocquee/dataqueue",
3
- "version": "1.24.0",
4
- "description": "PostgreSQL-based job queue for Node.js applications with support for serverless environments",
3
+ "version": "1.26.0-beta.20260223195940",
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",
7
7
  "exports": {
@@ -14,32 +14,53 @@
14
14
  "files": [
15
15
  "dist/",
16
16
  "src/",
17
- "migrations/"
17
+ "migrations/",
18
+ "ai/"
18
19
  ],
20
+ "scripts": {
21
+ "prebuild": "npx tsx ai/build-docs-content.ts && npx tsx ai/build-llms-full.ts",
22
+ "build": "tsup",
23
+ "ci": "npm run build && npm run check-format && npm run check-exports && npm run lint && npm run test",
24
+ "lint": "tsc",
25
+ "test": "vitest run --reporter=verbose",
26
+ "format": "prettier --write .",
27
+ "check-format": "prettier --check .",
28
+ "check-exports": "attw --pack .",
29
+ "build:docs-content": "npx tsx ai/build-docs-content.ts && npx tsx ai/build-llms-full.ts",
30
+ "dev": "tsup --watch",
31
+ "migrate": "node-pg-migrate -d $PG_DATAQUEUE_DATABASE -m ./migrations"
32
+ },
19
33
  "keywords": [
20
34
  "nextjs",
21
35
  "postgresql",
36
+ "redis",
22
37
  "job-queue",
23
38
  "background-jobs",
24
39
  "vercel"
25
40
  ],
26
41
  "author": "Nico Prananta",
27
42
  "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/nicnocquee/dataqueue.git",
46
+ "directory": "packages/dataqueue"
47
+ },
28
48
  "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.26.0",
50
+ "croner": "^10.0.1",
29
51
  "pg": "^8.0.0",
30
52
  "pg-connection-string": "^2.9.1",
31
- "ts-node": "^10.9.2"
53
+ "zod": "^3.25.67"
32
54
  },
33
55
  "devDependencies": {
34
56
  "@arethetypeswrong/cli": "^0.18.2",
35
- "@changesets/cli": "^2.29.5",
36
57
  "@types/node": "^24.0.4",
37
58
  "@types/pg": "^8.15.4",
38
- "@vitejs/plugin-react": "^4.6.0",
39
- "jsdom": "^26.1.0",
59
+ "ioredis": "^5.9.3",
40
60
  "node-pg-migrate": "^8.0.3",
41
61
  "pnpm": "^9.0.0",
42
62
  "prettier": "^3.6.2",
63
+ "ts-node": "^10.9.2",
43
64
  "tsup": "^8.5.0",
44
65
  "turbo": "^1.13.0",
45
66
  "typescript": "^5.8.3",
@@ -47,24 +68,20 @@
47
68
  "vitest": "^3.2.4"
48
69
  },
49
70
  "peerDependencies": {
71
+ "ioredis": "^5.0.0",
50
72
  "node-pg-migrate": "^8.0.3",
51
73
  "pg": "^8.0.0"
52
74
  },
53
- "bin": {
54
- "dataqueue-cli": "./cli.cjs"
75
+ "peerDependenciesMeta": {
76
+ "ioredis": {
77
+ "optional": true
78
+ },
79
+ "node-pg-migrate": {
80
+ "optional": true
81
+ }
55
82
  },
56
- "scripts": {
57
- "build": "tsup",
58
- "ci": "npm run build && npm run check-format && npm run check-exports && npm run lint && npm run test",
59
- "lint": "tsc",
60
- "test": "vitest run --reporter=verbose",
61
- "format": "prettier --write .",
62
- "check-format": "prettier --check .",
63
- "check-exports": "attw --pack .",
64
- "local-release": "changeset version && changeset publish",
65
- "dev": "tsup --watch",
66
- "migrate": "node-pg-migrate -d $PG_DATAQUEUE_DATABASE -m ./migrations",
67
- "changeset:add": "changeset",
68
- "changeset:version": "changeset version && find .changeset -type f -name '*.md' ! -name 'README.md' -delete"
83
+ "bin": {
84
+ "dataqueue-cli": "./cli.cjs",
85
+ "dataqueue-mcp": "./dist/mcp-server.js"
69
86
  }
70
- }
87
+ }
package/src/backend.ts ADDED
@@ -0,0 +1,328 @@
1
+ import {
2
+ JobOptions,
3
+ JobRecord,
4
+ JobEvent,
5
+ JobEventType,
6
+ FailureReason,
7
+ TagQueryMode,
8
+ JobType,
9
+ CronScheduleRecord,
10
+ CronScheduleStatus,
11
+ EditCronScheduleOptions,
12
+ WaitpointRecord,
13
+ CreateTokenOptions,
14
+ AddJobOptions,
15
+ } from './types.js';
16
+
17
+ /**
18
+ * Filter options used by getJobs, cancelAllUpcomingJobs, editAllPendingJobs
19
+ */
20
+ export interface JobFilters {
21
+ jobType?: string;
22
+ priority?: number;
23
+ runAt?: Date | { gt?: Date; gte?: Date; lt?: Date; lte?: Date; eq?: Date };
24
+ tags?: { values: string[]; mode?: TagQueryMode };
25
+ /**
26
+ * Cursor for keyset pagination. When provided, only return jobs with id < cursor.
27
+ * This is more efficient than OFFSET for large datasets.
28
+ * Cannot be used together with offset.
29
+ */
30
+ cursor?: number;
31
+ }
32
+
33
+ /**
34
+ * Fields that can be updated on a job
35
+ */
36
+ export interface JobUpdates {
37
+ payload?: any;
38
+ maxAttempts?: number;
39
+ priority?: number;
40
+ runAt?: Date | null;
41
+ timeoutMs?: number | null;
42
+ tags?: string[] | null;
43
+ retryDelay?: number | null;
44
+ retryBackoff?: boolean | null;
45
+ retryDelayMax?: number | null;
46
+ }
47
+
48
+ /**
49
+ * Input shape for creating a cron schedule in the backend.
50
+ * This is the backend-level version of CronScheduleOptions.
51
+ */
52
+ export interface CronScheduleInput {
53
+ scheduleName: string;
54
+ cronExpression: string;
55
+ jobType: string;
56
+ payload: any;
57
+ maxAttempts: number;
58
+ priority: number;
59
+ timeoutMs: number | null;
60
+ forceKillOnTimeout: boolean;
61
+ tags: string[] | undefined;
62
+ timezone: string;
63
+ allowOverlap: boolean;
64
+ nextRunAt: Date | null;
65
+ retryDelay: number | null;
66
+ retryBackoff: boolean | null;
67
+ retryDelayMax: number | null;
68
+ }
69
+
70
+ /**
71
+ * Abstract backend interface that both PostgreSQL and Redis implement.
72
+ * All storage operations go through this interface so the processor
73
+ * and public API are backend-agnostic.
74
+ */
75
+ export interface QueueBackend {
76
+ // ── Job CRUD ──────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Add a job and return its numeric ID.
80
+ *
81
+ * @param job - Job configuration.
82
+ * @param options - Optional. Pass `{ db }` to run the INSERT on an external
83
+ * client (e.g., inside a transaction). PostgreSQL only.
84
+ */
85
+ addJob<PayloadMap, T extends JobType<PayloadMap>>(
86
+ job: JobOptions<PayloadMap, T>,
87
+ options?: AddJobOptions,
88
+ ): Promise<number>;
89
+
90
+ /**
91
+ * Add multiple jobs in a single operation and return their IDs.
92
+ *
93
+ * IDs are returned in the same order as the input array.
94
+ * Each job may independently have an `idempotencyKey`; duplicates
95
+ * resolve to the existing job's ID without creating a new row.
96
+ *
97
+ * @param jobs - Array of job configurations.
98
+ * @param options - Optional. Pass `{ db }` to run the INSERTs on an external
99
+ * client (e.g., inside a transaction). PostgreSQL only.
100
+ */
101
+ addJobs<PayloadMap, T extends JobType<PayloadMap>>(
102
+ jobs: JobOptions<PayloadMap, T>[],
103
+ options?: AddJobOptions,
104
+ ): Promise<number[]>;
105
+
106
+ /** Get a single job by ID, or null if not found. */
107
+ getJob<PayloadMap, T extends JobType<PayloadMap>>(
108
+ id: number,
109
+ ): Promise<JobRecord<PayloadMap, T> | null>;
110
+
111
+ /** Get jobs filtered by status, ordered by createdAt DESC. */
112
+ getJobsByStatus<PayloadMap, T extends JobType<PayloadMap>>(
113
+ status: string,
114
+ limit?: number,
115
+ offset?: number,
116
+ ): Promise<JobRecord<PayloadMap, T>[]>;
117
+
118
+ /** Get all jobs, ordered by createdAt DESC. */
119
+ getAllJobs<PayloadMap, T extends JobType<PayloadMap>>(
120
+ limit?: number,
121
+ offset?: number,
122
+ ): Promise<JobRecord<PayloadMap, T>[]>;
123
+
124
+ /** Get jobs matching arbitrary filters, ordered by createdAt DESC. */
125
+ getJobs<PayloadMap, T extends JobType<PayloadMap>>(
126
+ filters?: JobFilters,
127
+ limit?: number,
128
+ offset?: number,
129
+ ): Promise<JobRecord<PayloadMap, T>[]>;
130
+
131
+ /** Get jobs by tag(s) with query mode. */
132
+ getJobsByTags<PayloadMap, T extends JobType<PayloadMap>>(
133
+ tags: string[],
134
+ mode?: TagQueryMode,
135
+ limit?: number,
136
+ offset?: number,
137
+ ): Promise<JobRecord<PayloadMap, T>[]>;
138
+
139
+ // ── Processing lifecycle ──────────────────────────────────────────────
140
+
141
+ /**
142
+ * Atomically claim a batch of ready jobs for the given worker.
143
+ * Equivalent to SELECT … FOR UPDATE SKIP LOCKED in Postgres.
144
+ */
145
+ getNextBatch<PayloadMap, T extends JobType<PayloadMap>>(
146
+ workerId: string,
147
+ batchSize?: number,
148
+ jobType?: string | string[],
149
+ ): Promise<JobRecord<PayloadMap, T>[]>;
150
+
151
+ /** Mark a job as completed. */
152
+ completeJob(jobId: number): Promise<void>;
153
+
154
+ /** Mark a job as failed with error info and schedule retry. */
155
+ failJob(
156
+ jobId: number,
157
+ error: Error,
158
+ failureReason?: FailureReason,
159
+ ): Promise<void>;
160
+
161
+ /** Update locked_at to keep the job alive (heartbeat). */
162
+ prolongJob(jobId: number): Promise<void>;
163
+
164
+ // ── Job management ────────────────────────────────────────────────────
165
+
166
+ /** Retry a failed/cancelled job immediately. */
167
+ retryJob(jobId: number): Promise<void>;
168
+
169
+ /** Cancel a pending job. */
170
+ cancelJob(jobId: number): Promise<void>;
171
+
172
+ /** Cancel all pending jobs matching optional filters. Returns count. */
173
+ cancelAllUpcomingJobs(filters?: JobFilters): Promise<number>;
174
+
175
+ /** Edit a single pending job. */
176
+ editJob(jobId: number, updates: JobUpdates): Promise<void>;
177
+
178
+ /** Edit all pending jobs matching filters. Returns count. */
179
+ editAllPendingJobs(
180
+ filters: JobFilters | undefined,
181
+ updates: JobUpdates,
182
+ ): Promise<number>;
183
+
184
+ /** Delete completed jobs older than N days. Deletes in batches for scale safety. Returns count deleted. */
185
+ cleanupOldJobs(daysToKeep?: number, batchSize?: number): Promise<number>;
186
+
187
+ /** Delete job events older than N days. Deletes in batches for scale safety. Returns count deleted. */
188
+ cleanupOldJobEvents(daysToKeep?: number, batchSize?: number): Promise<number>;
189
+
190
+ /** Reclaim jobs stuck in 'processing' for too long. Returns count. */
191
+ reclaimStuckJobs(maxProcessingTimeMinutes?: number): Promise<number>;
192
+
193
+ // ── Progress ──────────────────────────────────────────────────────────
194
+
195
+ /** Update the progress percentage (0-100) for a job. */
196
+ updateProgress(jobId: number, progress: number): Promise<void>;
197
+
198
+ // ── Events ────────────────────────────────────────────────────────────
199
+
200
+ /** Record a job event. Should not throw. */
201
+ recordJobEvent(
202
+ jobId: number,
203
+ eventType: JobEventType,
204
+ metadata?: any,
205
+ ): Promise<void>;
206
+
207
+ /** Get all events for a job, ordered by createdAt ASC. */
208
+ getJobEvents(jobId: number): Promise<JobEvent[]>;
209
+
210
+ // ── Cron schedules ──────────────────────────────────────────────────
211
+
212
+ /** Create a cron schedule and return its ID. */
213
+ addCronSchedule(input: CronScheduleInput): Promise<number>;
214
+
215
+ /** Get a cron schedule by ID, or null if not found. */
216
+ getCronSchedule(id: number): Promise<CronScheduleRecord | null>;
217
+
218
+ /** Get a cron schedule by its unique name, or null if not found. */
219
+ getCronScheduleByName(name: string): Promise<CronScheduleRecord | null>;
220
+
221
+ /** List cron schedules, optionally filtered by status. */
222
+ listCronSchedules(status?: CronScheduleStatus): Promise<CronScheduleRecord[]>;
223
+
224
+ /** Delete a cron schedule by ID. */
225
+ removeCronSchedule(id: number): Promise<void>;
226
+
227
+ /** Pause a cron schedule. */
228
+ pauseCronSchedule(id: number): Promise<void>;
229
+
230
+ /** Resume a cron schedule. */
231
+ resumeCronSchedule(id: number): Promise<void>;
232
+
233
+ /** Edit a cron schedule. */
234
+ editCronSchedule(
235
+ id: number,
236
+ updates: EditCronScheduleOptions,
237
+ nextRunAt?: Date | null,
238
+ ): Promise<void>;
239
+
240
+ /**
241
+ * Atomically fetch all active cron schedules whose nextRunAt <= now.
242
+ * In PostgreSQL this uses FOR UPDATE SKIP LOCKED to prevent duplicate enqueuing.
243
+ */
244
+ getDueCronSchedules(): Promise<CronScheduleRecord[]>;
245
+
246
+ /**
247
+ * Update a cron schedule after a job has been enqueued.
248
+ * Sets lastEnqueuedAt, lastJobId, and advances nextRunAt.
249
+ */
250
+ updateCronScheduleAfterEnqueue(
251
+ id: number,
252
+ lastEnqueuedAt: Date,
253
+ lastJobId: number,
254
+ nextRunAt: Date | null,
255
+ ): Promise<void>;
256
+
257
+ // ── Wait / step-data support ────────────────────────────────────────
258
+
259
+ /**
260
+ * Transition a job from 'processing' to 'waiting' status.
261
+ * Persists step data so the handler can resume from where it left off.
262
+ *
263
+ * @param jobId - The job to pause.
264
+ * @param options - Wait configuration including optional waitUntil date, token ID, and step data.
265
+ */
266
+ waitJob(
267
+ jobId: number,
268
+ options: {
269
+ waitUntil?: Date;
270
+ waitTokenId?: string;
271
+ stepData: Record<string, any>;
272
+ },
273
+ ): Promise<void>;
274
+
275
+ /**
276
+ * Persist step data for a job. Called after each `ctx.run()` step completes
277
+ * to save intermediate progress. Best-effort: should not throw.
278
+ *
279
+ * @param jobId - The job to update.
280
+ * @param stepData - The step data to persist.
281
+ */
282
+ updateStepData(jobId: number, stepData: Record<string, any>): Promise<void>;
283
+
284
+ /**
285
+ * Create a waitpoint token that can pause a job until an external signal completes it.
286
+ *
287
+ * @param jobId - The job ID to associate with the token (null if created outside a handler).
288
+ * @param options - Optional timeout string (e.g. '10m', '1h') and tags.
289
+ * @returns The created waitpoint with its unique ID.
290
+ */
291
+ createWaitpoint(
292
+ jobId: number | null,
293
+ options?: CreateTokenOptions,
294
+ ): Promise<{ id: string }>;
295
+
296
+ /**
297
+ * Complete a waitpoint token, optionally providing output data.
298
+ * Moves the associated job from 'waiting' back to 'pending' so it gets picked up.
299
+ *
300
+ * @param tokenId - The waitpoint token ID to complete.
301
+ * @param data - Optional data to pass to the waiting handler.
302
+ */
303
+ completeWaitpoint(tokenId: string, data?: any): Promise<void>;
304
+
305
+ /**
306
+ * Retrieve a waitpoint token by its ID.
307
+ *
308
+ * @param tokenId - The waitpoint token ID to look up.
309
+ * @returns The waitpoint record, or null if not found.
310
+ */
311
+ getWaitpoint(tokenId: string): Promise<WaitpointRecord | null>;
312
+
313
+ /**
314
+ * Expire timed-out waitpoint tokens and move their associated jobs back to 'pending'.
315
+ * Should be called periodically (e.g., alongside reclaimStuckJobs).
316
+ *
317
+ * @returns The number of tokens that were expired.
318
+ */
319
+ expireTimedOutWaitpoints(): Promise<number>;
320
+
321
+ // ── Internal helpers ──────────────────────────────────────────────────
322
+
323
+ /** Set a pending reason for unpicked jobs of a given type. */
324
+ setPendingReasonForUnpickedJobs(
325
+ reason: string,
326
+ jobType?: string | string[],
327
+ ): Promise<void>;
328
+ }