@nicnocquee/dataqueue 1.25.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.
- package/ai/build-docs-content.ts +96 -0
- package/ai/build-llms-full.ts +42 -0
- package/ai/docs-content.json +278 -0
- package/ai/rules/advanced.md +132 -0
- package/ai/rules/basic.md +159 -0
- package/ai/rules/react-dashboard.md +83 -0
- package/ai/skills/dataqueue-advanced/SKILL.md +320 -0
- package/ai/skills/dataqueue-core/SKILL.md +234 -0
- package/ai/skills/dataqueue-react/SKILL.md +189 -0
- package/dist/cli.cjs +1149 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +66 -1
- package/dist/cli.d.ts +66 -1
- package/dist/cli.js +1146 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +3157 -1237
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +613 -23
- package/dist/index.d.ts +613 -23
- package/dist/index.js +3156 -1238
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +186 -0
- package/dist/mcp-server.cjs.map +1 -0
- package/dist/mcp-server.d.cts +32 -0
- package/dist/mcp-server.d.ts +32 -0
- package/dist/mcp-server.js +175 -0
- package/dist/mcp-server.js.map +1 -0
- package/migrations/1781200000004_create_cron_schedules_table.sql +33 -0
- package/migrations/1781200000005_add_retry_config_to_job_queue.sql +17 -0
- package/package.json +24 -21
- package/src/backend.ts +170 -5
- package/src/backends/postgres.ts +992 -63
- package/src/backends/redis-scripts.ts +358 -26
- package/src/backends/redis.test.ts +1363 -0
- package/src/backends/redis.ts +993 -35
- package/src/cli.test.ts +82 -6
- package/src/cli.ts +73 -10
- package/src/cron.test.ts +126 -0
- package/src/cron.ts +40 -0
- package/src/db-util.ts +1 -1
- package/src/index.test.ts +682 -0
- package/src/index.ts +209 -34
- package/src/init-command.test.ts +449 -0
- package/src/init-command.ts +709 -0
- package/src/install-mcp-command.test.ts +216 -0
- package/src/install-mcp-command.ts +185 -0
- package/src/install-rules-command.test.ts +218 -0
- package/src/install-rules-command.ts +233 -0
- package/src/install-skills-command.test.ts +176 -0
- package/src/install-skills-command.ts +124 -0
- package/src/mcp-server.test.ts +162 -0
- package/src/mcp-server.ts +231 -0
- package/src/processor.ts +36 -97
- package/src/queue.test.ts +465 -0
- package/src/queue.ts +34 -252
- package/src/supervisor.test.ts +340 -0
- package/src/supervisor.ts +162 -0
- package/src/types.ts +388 -12
- package/LICENSE +0 -21
package/src/index.ts
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createWaitpoint,
|
|
3
|
-
completeWaitpoint,
|
|
4
|
-
getWaitpoint,
|
|
5
|
-
expireTimedOutWaitpoints,
|
|
6
|
-
} from './queue.js';
|
|
7
1
|
import { createProcessor } from './processor.js';
|
|
2
|
+
import { createSupervisor } from './supervisor.js';
|
|
8
3
|
import {
|
|
9
4
|
JobQueueConfig,
|
|
10
5
|
JobQueue,
|
|
11
6
|
JobOptions,
|
|
7
|
+
AddJobOptions,
|
|
12
8
|
ProcessorOptions,
|
|
9
|
+
SupervisorOptions,
|
|
13
10
|
JobHandlers,
|
|
14
11
|
JobType,
|
|
15
12
|
PostgresJobQueueConfig,
|
|
16
13
|
RedisJobQueueConfig,
|
|
14
|
+
CronScheduleOptions,
|
|
15
|
+
CronScheduleStatus,
|
|
16
|
+
EditCronScheduleOptions,
|
|
17
17
|
} from './types.js';
|
|
18
|
-
import { QueueBackend } from './backend.js';
|
|
18
|
+
import { QueueBackend, CronScheduleInput } from './backend.js';
|
|
19
19
|
import { setLogContext } from './log-context.js';
|
|
20
20
|
import { createPool } from './db-util.js';
|
|
21
21
|
import { PostgresBackend } from './backends/postgres.js';
|
|
22
22
|
import { RedisBackend } from './backends/redis.js';
|
|
23
|
+
import { getNextCronOccurrence, validateCronExpression } from './cron.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Initialize the job queue system.
|
|
26
27
|
*
|
|
27
28
|
* Defaults to PostgreSQL when `backend` is omitted.
|
|
29
|
+
* For PostgreSQL, provide either `databaseConfig` or `pool` (bring your own).
|
|
30
|
+
* For Redis, provide either `redisConfig` or `client` (bring your own).
|
|
28
31
|
*/
|
|
29
32
|
export const initJobQueue = <PayloadMap = any>(
|
|
30
33
|
config: JobQueueConfig,
|
|
@@ -33,35 +36,111 @@ export const initJobQueue = <PayloadMap = any>(
|
|
|
33
36
|
setLogContext(config.verbose ?? false);
|
|
34
37
|
|
|
35
38
|
let backend: QueueBackend;
|
|
36
|
-
let pool: import('pg').Pool | undefined;
|
|
37
39
|
|
|
38
40
|
if (backendType === 'postgres') {
|
|
39
41
|
const pgConfig = config as PostgresJobQueueConfig;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
if (pgConfig.pool) {
|
|
43
|
+
backend = new PostgresBackend(pgConfig.pool);
|
|
44
|
+
} else if (pgConfig.databaseConfig) {
|
|
45
|
+
const pool = createPool(pgConfig.databaseConfig);
|
|
46
|
+
backend = new PostgresBackend(pool);
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'PostgreSQL backend requires either "databaseConfig" or "pool" to be provided.',
|
|
50
|
+
);
|
|
51
|
+
}
|
|
42
52
|
} else if (backendType === 'redis') {
|
|
43
|
-
const redisConfig =
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
const redisConfig = config as RedisJobQueueConfig;
|
|
54
|
+
if (redisConfig.client) {
|
|
55
|
+
backend = new RedisBackend(
|
|
56
|
+
redisConfig.client as any,
|
|
57
|
+
redisConfig.keyPrefix,
|
|
58
|
+
);
|
|
59
|
+
} else if (redisConfig.redisConfig) {
|
|
60
|
+
backend = new RedisBackend(redisConfig.redisConfig);
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'Redis backend requires either "redisConfig" or "client" to be provided.',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
46
66
|
} else {
|
|
47
67
|
throw new Error(`Unknown backend: ${backendType}`);
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Enqueue due cron jobs. Shared by the public API and the processor hook.
|
|
72
|
+
*/
|
|
73
|
+
const enqueueDueCronJobsImpl = async (): Promise<number> => {
|
|
74
|
+
const dueSchedules = await backend.getDueCronSchedules();
|
|
75
|
+
let count = 0;
|
|
76
|
+
|
|
77
|
+
for (const schedule of dueSchedules) {
|
|
78
|
+
// Overlap check: skip if allowOverlap is false and last job is still active
|
|
79
|
+
if (!schedule.allowOverlap && schedule.lastJobId !== null) {
|
|
80
|
+
const lastJob = await backend.getJob(schedule.lastJobId);
|
|
81
|
+
if (
|
|
82
|
+
lastJob &&
|
|
83
|
+
(lastJob.status === 'pending' ||
|
|
84
|
+
lastJob.status === 'processing' ||
|
|
85
|
+
lastJob.status === 'waiting')
|
|
86
|
+
) {
|
|
87
|
+
// Still active — advance nextRunAt but don't enqueue
|
|
88
|
+
const nextRunAt = getNextCronOccurrence(
|
|
89
|
+
schedule.cronExpression,
|
|
90
|
+
schedule.timezone,
|
|
91
|
+
);
|
|
92
|
+
await backend.updateCronScheduleAfterEnqueue(
|
|
93
|
+
schedule.id,
|
|
94
|
+
new Date(),
|
|
95
|
+
schedule.lastJobId,
|
|
96
|
+
nextRunAt,
|
|
97
|
+
);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Enqueue a new job instance
|
|
103
|
+
const jobId = await backend.addJob<any, any>({
|
|
104
|
+
jobType: schedule.jobType,
|
|
105
|
+
payload: schedule.payload,
|
|
106
|
+
maxAttempts: schedule.maxAttempts,
|
|
107
|
+
priority: schedule.priority,
|
|
108
|
+
timeoutMs: schedule.timeoutMs ?? undefined,
|
|
109
|
+
forceKillOnTimeout: schedule.forceKillOnTimeout,
|
|
110
|
+
tags: schedule.tags,
|
|
111
|
+
retryDelay: schedule.retryDelay ?? undefined,
|
|
112
|
+
retryBackoff: schedule.retryBackoff ?? undefined,
|
|
113
|
+
retryDelayMax: schedule.retryDelayMax ?? undefined,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Advance to next occurrence
|
|
117
|
+
const nextRunAt = getNextCronOccurrence(
|
|
118
|
+
schedule.cronExpression,
|
|
119
|
+
schedule.timezone,
|
|
54
120
|
);
|
|
121
|
+
await backend.updateCronScheduleAfterEnqueue(
|
|
122
|
+
schedule.id,
|
|
123
|
+
new Date(),
|
|
124
|
+
jobId,
|
|
125
|
+
nextRunAt,
|
|
126
|
+
);
|
|
127
|
+
count++;
|
|
55
128
|
}
|
|
56
|
-
|
|
129
|
+
|
|
130
|
+
return count;
|
|
57
131
|
};
|
|
58
132
|
|
|
59
133
|
// Return the job queue API
|
|
60
134
|
return {
|
|
61
135
|
// Job queue operations
|
|
62
136
|
addJob: withLogContext(
|
|
63
|
-
(job: JobOptions<PayloadMap, any
|
|
64
|
-
backend.addJob<PayloadMap, any>(job),
|
|
137
|
+
(job: JobOptions<PayloadMap, any>, options?: AddJobOptions) =>
|
|
138
|
+
backend.addJob<PayloadMap, any>(job, options),
|
|
139
|
+
config.verbose ?? false,
|
|
140
|
+
),
|
|
141
|
+
addJobs: withLogContext(
|
|
142
|
+
(jobs: JobOptions<PayloadMap, any>[], options?: AddJobOptions) =>
|
|
143
|
+
backend.addJobs<PayloadMap, any>(jobs, options),
|
|
65
144
|
config.verbose ?? false,
|
|
66
145
|
),
|
|
67
146
|
getJob: withLogContext(
|
|
@@ -94,9 +173,10 @@ export const initJobQueue = <PayloadMap = any>(
|
|
|
94
173
|
config.verbose ?? false,
|
|
95
174
|
),
|
|
96
175
|
retryJob: (jobId: number) => backend.retryJob(jobId),
|
|
97
|
-
cleanupOldJobs: (daysToKeep?: number) =>
|
|
98
|
-
|
|
99
|
-
|
|
176
|
+
cleanupOldJobs: (daysToKeep?: number, batchSize?: number) =>
|
|
177
|
+
backend.cleanupOldJobs(daysToKeep, batchSize),
|
|
178
|
+
cleanupOldJobEvents: (daysToKeep?: number, batchSize?: number) =>
|
|
179
|
+
backend.cleanupOldJobEvents(daysToKeep, batchSize),
|
|
100
180
|
cancelJob: withLogContext(
|
|
101
181
|
(jobId: number) => backend.cancelJob(jobId),
|
|
102
182
|
config.verbose ?? false,
|
|
@@ -153,11 +233,18 @@ export const initJobQueue = <PayloadMap = any>(
|
|
|
153
233
|
config.verbose ?? false,
|
|
154
234
|
),
|
|
155
235
|
|
|
156
|
-
// Job processing
|
|
236
|
+
// Job processing — automatically enqueues due cron jobs before each batch
|
|
157
237
|
createProcessor: (
|
|
158
238
|
handlers: JobHandlers<PayloadMap>,
|
|
159
239
|
options?: ProcessorOptions,
|
|
160
|
-
) =>
|
|
240
|
+
) =>
|
|
241
|
+
createProcessor<PayloadMap>(backend, handlers, options, async () => {
|
|
242
|
+
await enqueueDueCronJobsImpl();
|
|
243
|
+
}),
|
|
244
|
+
|
|
245
|
+
// Background supervisor — automated maintenance
|
|
246
|
+
createSupervisor: (options?: SupervisorOptions) =>
|
|
247
|
+
createSupervisor(backend, options),
|
|
161
248
|
|
|
162
249
|
// Job events
|
|
163
250
|
getJobEvents: withLogContext(
|
|
@@ -165,34 +252,121 @@ export const initJobQueue = <PayloadMap = any>(
|
|
|
165
252
|
config.verbose ?? false,
|
|
166
253
|
),
|
|
167
254
|
|
|
168
|
-
// Wait / Token support (
|
|
255
|
+
// Wait / Token support (works with all backends)
|
|
169
256
|
createToken: withLogContext(
|
|
170
257
|
(options?: import('./types.js').CreateTokenOptions) =>
|
|
171
|
-
createWaitpoint(
|
|
258
|
+
backend.createWaitpoint(null, options),
|
|
172
259
|
config.verbose ?? false,
|
|
173
260
|
),
|
|
174
261
|
completeToken: withLogContext(
|
|
175
|
-
(tokenId: string, data?: any) =>
|
|
176
|
-
completeWaitpoint(requirePool(), tokenId, data),
|
|
262
|
+
(tokenId: string, data?: any) => backend.completeWaitpoint(tokenId, data),
|
|
177
263
|
config.verbose ?? false,
|
|
178
264
|
),
|
|
179
265
|
getToken: withLogContext(
|
|
180
|
-
(tokenId: string) => getWaitpoint(
|
|
266
|
+
(tokenId: string) => backend.getWaitpoint(tokenId),
|
|
181
267
|
config.verbose ?? false,
|
|
182
268
|
),
|
|
183
269
|
expireTimedOutTokens: withLogContext(
|
|
184
|
-
() => expireTimedOutWaitpoints(
|
|
270
|
+
() => backend.expireTimedOutWaitpoints(),
|
|
271
|
+
config.verbose ?? false,
|
|
272
|
+
),
|
|
273
|
+
|
|
274
|
+
// Cron schedule operations
|
|
275
|
+
addCronJob: withLogContext(
|
|
276
|
+
<T extends JobType<PayloadMap>>(
|
|
277
|
+
options: CronScheduleOptions<PayloadMap, T>,
|
|
278
|
+
) => {
|
|
279
|
+
if (!validateCronExpression(options.cronExpression)) {
|
|
280
|
+
return Promise.reject(
|
|
281
|
+
new Error(`Invalid cron expression: "${options.cronExpression}"`),
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
const nextRunAt = getNextCronOccurrence(
|
|
285
|
+
options.cronExpression,
|
|
286
|
+
options.timezone ?? 'UTC',
|
|
287
|
+
);
|
|
288
|
+
const input: CronScheduleInput = {
|
|
289
|
+
scheduleName: options.scheduleName,
|
|
290
|
+
cronExpression: options.cronExpression,
|
|
291
|
+
jobType: options.jobType as string,
|
|
292
|
+
payload: options.payload,
|
|
293
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
294
|
+
priority: options.priority ?? 0,
|
|
295
|
+
timeoutMs: options.timeoutMs ?? null,
|
|
296
|
+
forceKillOnTimeout: options.forceKillOnTimeout ?? false,
|
|
297
|
+
tags: options.tags,
|
|
298
|
+
timezone: options.timezone ?? 'UTC',
|
|
299
|
+
allowOverlap: options.allowOverlap ?? false,
|
|
300
|
+
nextRunAt,
|
|
301
|
+
retryDelay: options.retryDelay ?? null,
|
|
302
|
+
retryBackoff: options.retryBackoff ?? null,
|
|
303
|
+
retryDelayMax: options.retryDelayMax ?? null,
|
|
304
|
+
};
|
|
305
|
+
return backend.addCronSchedule(input);
|
|
306
|
+
},
|
|
307
|
+
config.verbose ?? false,
|
|
308
|
+
),
|
|
309
|
+
getCronJob: withLogContext(
|
|
310
|
+
(id: number) => backend.getCronSchedule(id),
|
|
311
|
+
config.verbose ?? false,
|
|
312
|
+
),
|
|
313
|
+
getCronJobByName: withLogContext(
|
|
314
|
+
(name: string) => backend.getCronScheduleByName(name),
|
|
315
|
+
config.verbose ?? false,
|
|
316
|
+
),
|
|
317
|
+
listCronJobs: withLogContext(
|
|
318
|
+
(status?: CronScheduleStatus) => backend.listCronSchedules(status),
|
|
319
|
+
config.verbose ?? false,
|
|
320
|
+
),
|
|
321
|
+
removeCronJob: withLogContext(
|
|
322
|
+
(id: number) => backend.removeCronSchedule(id),
|
|
323
|
+
config.verbose ?? false,
|
|
324
|
+
),
|
|
325
|
+
pauseCronJob: withLogContext(
|
|
326
|
+
(id: number) => backend.pauseCronSchedule(id),
|
|
327
|
+
config.verbose ?? false,
|
|
328
|
+
),
|
|
329
|
+
resumeCronJob: withLogContext(
|
|
330
|
+
(id: number) => backend.resumeCronSchedule(id),
|
|
331
|
+
config.verbose ?? false,
|
|
332
|
+
),
|
|
333
|
+
editCronJob: withLogContext(
|
|
334
|
+
async (id: number, updates: EditCronScheduleOptions) => {
|
|
335
|
+
if (
|
|
336
|
+
updates.cronExpression !== undefined &&
|
|
337
|
+
!validateCronExpression(updates.cronExpression)
|
|
338
|
+
) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Invalid cron expression: "${updates.cronExpression}"`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
let nextRunAt: Date | null | undefined;
|
|
344
|
+
if (
|
|
345
|
+
updates.cronExpression !== undefined ||
|
|
346
|
+
updates.timezone !== undefined
|
|
347
|
+
) {
|
|
348
|
+
const existing = await backend.getCronSchedule(id);
|
|
349
|
+
const expr = updates.cronExpression ?? existing?.cronExpression ?? '';
|
|
350
|
+
const tz = updates.timezone ?? existing?.timezone ?? 'UTC';
|
|
351
|
+
nextRunAt = getNextCronOccurrence(expr, tz);
|
|
352
|
+
}
|
|
353
|
+
await backend.editCronSchedule(id, updates, nextRunAt);
|
|
354
|
+
},
|
|
355
|
+
config.verbose ?? false,
|
|
356
|
+
),
|
|
357
|
+
enqueueDueCronJobs: withLogContext(
|
|
358
|
+
() => enqueueDueCronJobsImpl(),
|
|
185
359
|
config.verbose ?? false,
|
|
186
360
|
),
|
|
187
361
|
|
|
188
362
|
// Advanced access
|
|
189
363
|
getPool: () => {
|
|
190
|
-
if (
|
|
364
|
+
if (!(backend instanceof PostgresBackend)) {
|
|
191
365
|
throw new Error(
|
|
192
366
|
'getPool() is only available with the PostgreSQL backend.',
|
|
193
367
|
);
|
|
194
368
|
}
|
|
195
|
-
return
|
|
369
|
+
return backend.getPool();
|
|
196
370
|
},
|
|
197
371
|
getRedisClient: () => {
|
|
198
372
|
if (backendType !== 'redis') {
|
|
@@ -213,9 +387,10 @@ const withLogContext =
|
|
|
213
387
|
};
|
|
214
388
|
|
|
215
389
|
export * from './types.js';
|
|
216
|
-
export { QueueBackend } from './backend.js';
|
|
390
|
+
export { QueueBackend, CronScheduleInput } from './backend.js';
|
|
217
391
|
export { PostgresBackend } from './backends/postgres.js';
|
|
218
392
|
export {
|
|
219
393
|
validateHandlerSerializable,
|
|
220
394
|
testHandlerSerialization,
|
|
221
395
|
} from './handler-validation.js';
|
|
396
|
+
export { getNextCronOccurrence, validateCronExpression } from './cron.js';
|