@nicnocquee/dataqueue 1.33.0 → 1.35.0-beta.20260224075710

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 (54) hide show
  1. package/ai/build-docs-content.ts +96 -0
  2. package/ai/build-llms-full.ts +42 -0
  3. package/ai/docs-content.json +290 -0
  4. package/ai/rules/advanced.md +170 -0
  5. package/ai/rules/basic.md +159 -0
  6. package/ai/rules/react-dashboard.md +87 -0
  7. package/ai/skills/dataqueue-advanced/SKILL.md +370 -0
  8. package/ai/skills/dataqueue-core/SKILL.md +235 -0
  9. package/ai/skills/dataqueue-react/SKILL.md +201 -0
  10. package/dist/cli.cjs +577 -32
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.d.cts +52 -2
  13. package/dist/cli.d.ts +52 -2
  14. package/dist/cli.js +575 -32
  15. package/dist/cli.js.map +1 -1
  16. package/dist/index.cjs +937 -108
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +358 -11
  19. package/dist/index.d.ts +358 -11
  20. package/dist/index.js +937 -108
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp-server.cjs +186 -0
  23. package/dist/mcp-server.cjs.map +1 -0
  24. package/dist/mcp-server.d.cts +32 -0
  25. package/dist/mcp-server.d.ts +32 -0
  26. package/dist/mcp-server.js +175 -0
  27. package/dist/mcp-server.js.map +1 -0
  28. package/migrations/1781200000005_add_retry_config_to_job_queue.sql +17 -0
  29. package/migrations/1781200000006_add_output_to_job_queue.sql +3 -0
  30. package/package.json +10 -4
  31. package/src/backend.ts +36 -3
  32. package/src/backends/postgres.ts +344 -42
  33. package/src/backends/redis-scripts.ts +173 -8
  34. package/src/backends/redis.test.ts +668 -0
  35. package/src/backends/redis.ts +244 -15
  36. package/src/cli.test.ts +65 -0
  37. package/src/cli.ts +56 -19
  38. package/src/db-util.ts +1 -1
  39. package/src/index.test.ts +811 -12
  40. package/src/index.ts +106 -14
  41. package/src/install-mcp-command.test.ts +216 -0
  42. package/src/install-mcp-command.ts +185 -0
  43. package/src/install-rules-command.test.ts +218 -0
  44. package/src/install-rules-command.ts +233 -0
  45. package/src/install-skills-command.test.ts +176 -0
  46. package/src/install-skills-command.ts +124 -0
  47. package/src/mcp-server.test.ts +162 -0
  48. package/src/mcp-server.ts +231 -0
  49. package/src/processor.ts +133 -49
  50. package/src/queue.test.ts +477 -0
  51. package/src/queue.ts +20 -3
  52. package/src/supervisor.test.ts +340 -0
  53. package/src/supervisor.ts +177 -0
  54. package/src/types.ts +318 -3
@@ -31,7 +31,8 @@ const SCORE_RANGE = '1000000000000000'; // 1e15
31
31
  * ADD JOB
32
32
  * KEYS: [prefix]
33
33
  * ARGV: [jobType, payloadJson, maxAttempts, priority, runAtMs, timeoutMs,
34
- * forceKillOnTimeout, tagsJson, idempotencyKey, nowMs]
34
+ * forceKillOnTimeout, tagsJson, idempotencyKey, nowMs,
35
+ * retryDelay, retryBackoff, retryDelayMax]
35
36
  * Returns: job ID (number)
36
37
  */
37
38
  export const ADD_JOB_SCRIPT = `
@@ -46,6 +47,9 @@ local forceKillOnTimeout = ARGV[7]
46
47
  local tagsJson = ARGV[8] -- "null" or JSON array string
47
48
  local idempotencyKey = ARGV[9] -- "null" string if not set
48
49
  local nowMs = tonumber(ARGV[10])
50
+ local retryDelay = ARGV[11] -- "null" or seconds string
51
+ local retryBackoff = ARGV[12] -- "null" or "true"/"false"
52
+ local retryDelayMax = ARGV[13] -- "null" or seconds string
49
53
 
50
54
  -- Idempotency check
51
55
  if idempotencyKey ~= "null" then
@@ -89,7 +93,10 @@ redis.call('HMSET', jobKey,
89
93
  'idempotencyKey', idempotencyKey,
90
94
  'waitUntil', 'null',
91
95
  'waitTokenId', 'null',
92
- 'stepData', 'null'
96
+ 'stepData', 'null',
97
+ 'retryDelay', retryDelay,
98
+ 'retryBackoff', retryBackoff,
99
+ 'retryDelayMax', retryDelayMax
93
100
  )
94
101
 
95
102
  -- Status index
@@ -131,6 +138,129 @@ end
131
138
  return id
132
139
  `;
133
140
 
141
+ /**
142
+ * ADD JOBS (batch)
143
+ * KEYS: [prefix]
144
+ * ARGV: [jobsJson, nowMs]
145
+ * jobsJson is a JSON array of objects, each with:
146
+ * jobType, payload (already JSON string), maxAttempts, priority,
147
+ * runAtMs, timeoutMs, forceKillOnTimeout, tags (JSON or "null"),
148
+ * idempotencyKey
149
+ * Returns: array of job IDs (one per input job, in order)
150
+ */
151
+ export const ADD_JOBS_SCRIPT = `
152
+ local prefix = KEYS[1]
153
+ local jobsJson = ARGV[1]
154
+ local nowMs = tonumber(ARGV[2])
155
+
156
+ local jobs = cjson.decode(jobsJson)
157
+ local results = {}
158
+
159
+ for i, job in ipairs(jobs) do
160
+ local jobType = job.jobType
161
+ local payloadJson = job.payload
162
+ local maxAttempts = tonumber(job.maxAttempts)
163
+ local priority = tonumber(job.priority)
164
+ local runAtMs = tostring(job.runAtMs)
165
+ local timeoutMs = tostring(job.timeoutMs)
166
+ local forceKillOnTimeout = tostring(job.forceKillOnTimeout)
167
+ local tagsJson = tostring(job.tags)
168
+ local idempotencyKey = tostring(job.idempotencyKey)
169
+ local retryDelay = tostring(job.retryDelay)
170
+ local retryBackoff = tostring(job.retryBackoff)
171
+ local retryDelayMax = tostring(job.retryDelayMax)
172
+
173
+ -- Idempotency check
174
+ local skip = false
175
+ if idempotencyKey ~= "null" then
176
+ local existing = redis.call('GET', prefix .. 'idempotency:' .. idempotencyKey)
177
+ if existing then
178
+ results[i] = tonumber(existing)
179
+ skip = true
180
+ end
181
+ end
182
+
183
+ if not skip then
184
+ -- Generate ID
185
+ local id = redis.call('INCR', prefix .. 'id_seq')
186
+ local jobKey = prefix .. 'job:' .. id
187
+ local runAt = runAtMs ~= "0" and tonumber(runAtMs) or nowMs
188
+
189
+ -- Store the job hash
190
+ redis.call('HMSET', jobKey,
191
+ 'id', id,
192
+ 'jobType', jobType,
193
+ 'payload', payloadJson,
194
+ 'status', 'pending',
195
+ 'maxAttempts', maxAttempts,
196
+ 'attempts', 0,
197
+ 'priority', priority,
198
+ 'runAt', runAt,
199
+ 'timeoutMs', timeoutMs,
200
+ 'forceKillOnTimeout', forceKillOnTimeout,
201
+ 'createdAt', nowMs,
202
+ 'updatedAt', nowMs,
203
+ 'lockedAt', 'null',
204
+ 'lockedBy', 'null',
205
+ 'nextAttemptAt', 'null',
206
+ 'pendingReason', 'null',
207
+ 'errorHistory', '[]',
208
+ 'failureReason', 'null',
209
+ 'completedAt', 'null',
210
+ 'startedAt', 'null',
211
+ 'lastRetriedAt', 'null',
212
+ 'lastFailedAt', 'null',
213
+ 'lastCancelledAt', 'null',
214
+ 'tags', tagsJson,
215
+ 'idempotencyKey', idempotencyKey,
216
+ 'waitUntil', 'null',
217
+ 'waitTokenId', 'null',
218
+ 'stepData', 'null',
219
+ 'retryDelay', retryDelay,
220
+ 'retryBackoff', retryBackoff,
221
+ 'retryDelayMax', retryDelayMax
222
+ )
223
+
224
+ -- Status index
225
+ redis.call('SADD', prefix .. 'status:pending', id)
226
+
227
+ -- Type index
228
+ redis.call('SADD', prefix .. 'type:' .. jobType, id)
229
+
230
+ -- Tag indexes
231
+ if tagsJson ~= "null" then
232
+ local tags = cjson.decode(tagsJson)
233
+ for _, tag in ipairs(tags) do
234
+ redis.call('SADD', prefix .. 'tag:' .. tag, id)
235
+ end
236
+ for _, tag in ipairs(tags) do
237
+ redis.call('SADD', prefix .. 'job:' .. id .. ':tags', tag)
238
+ end
239
+ end
240
+
241
+ -- Idempotency mapping
242
+ if idempotencyKey ~= "null" then
243
+ redis.call('SET', prefix .. 'idempotency:' .. idempotencyKey, id)
244
+ end
245
+
246
+ -- All-jobs sorted set
247
+ redis.call('ZADD', prefix .. 'all', nowMs, id)
248
+
249
+ -- Queue or delayed
250
+ if runAt <= nowMs then
251
+ local score = priority * ${SCORE_RANGE} + (${SCORE_RANGE} - nowMs)
252
+ redis.call('ZADD', prefix .. 'queue', score, id)
253
+ else
254
+ redis.call('ZADD', prefix .. 'delayed', runAt, id)
255
+ end
256
+
257
+ results[i] = id
258
+ end
259
+ end
260
+
261
+ return results
262
+ `;
263
+
134
264
  /**
135
265
  * GET NEXT BATCH
136
266
  * Atomically: move ready delayed/retry jobs into queue, then pop N jobs.
@@ -291,22 +421,30 @@ return results
291
421
  /**
292
422
  * COMPLETE JOB
293
423
  * KEYS: [prefix]
294
- * ARGV: [jobId, nowMs]
424
+ * ARGV: [jobId, nowMs, outputJson]
295
425
  */
296
426
  export const COMPLETE_JOB_SCRIPT = `
297
427
  local prefix = KEYS[1]
298
428
  local jobId = ARGV[1]
299
429
  local nowMs = ARGV[2]
430
+ local outputJson = ARGV[3]
300
431
  local jk = prefix .. 'job:' .. jobId
301
432
 
302
- redis.call('HMSET', jk,
433
+ local fields = {
303
434
  'status', 'completed',
304
435
  'updatedAt', nowMs,
305
436
  'completedAt', nowMs,
306
437
  'stepData', 'null',
307
438
  'waitUntil', 'null',
308
439
  'waitTokenId', 'null'
309
- )
440
+ }
441
+
442
+ if outputJson ~= '__NONE__' then
443
+ fields[#fields + 1] = 'output'
444
+ fields[#fields + 1] = outputJson
445
+ end
446
+
447
+ redis.call('HMSET', jk, unpack(fields))
310
448
  redis.call('SREM', prefix .. 'status:processing', jobId)
311
449
  redis.call('SADD', prefix .. 'status:completed', jobId)
312
450
 
@@ -330,11 +468,38 @@ local jk = prefix .. 'job:' .. jobId
330
468
  local attempts = tonumber(redis.call('HGET', jk, 'attempts'))
331
469
  local maxAttempts = tonumber(redis.call('HGET', jk, 'maxAttempts'))
332
470
 
333
- -- Compute next_attempt_at: 2^attempts minutes from now
471
+ -- Read per-job retry config (may be "null")
472
+ local rdRaw = redis.call('HGET', jk, 'retryDelay')
473
+ local rbRaw = redis.call('HGET', jk, 'retryBackoff')
474
+ local rmRaw = redis.call('HGET', jk, 'retryDelayMax')
475
+
334
476
  local nextAttemptAt = 'null'
335
477
  if attempts < maxAttempts then
336
- local delayMs = math.pow(2, attempts) * 60000
337
- nextAttemptAt = nowMs + delayMs
478
+ local allNull = (rdRaw == 'null' or rdRaw == false)
479
+ and (rbRaw == 'null' or rbRaw == false)
480
+ and (rmRaw == 'null' or rmRaw == false)
481
+ if allNull then
482
+ -- Legacy formula: 2^attempts minutes
483
+ local delayMs = math.pow(2, attempts) * 60000
484
+ nextAttemptAt = nowMs + delayMs
485
+ else
486
+ local retryDelaySec = 60
487
+ if rdRaw and rdRaw ~= 'null' then retryDelaySec = tonumber(rdRaw) end
488
+ local useBackoff = true
489
+ if rbRaw and rbRaw ~= 'null' then useBackoff = (rbRaw == 'true') end
490
+ local maxDelaySec = nil
491
+ if rmRaw and rmRaw ~= 'null' then maxDelaySec = tonumber(rmRaw) end
492
+
493
+ local delaySec
494
+ if useBackoff then
495
+ delaySec = retryDelaySec * math.pow(2, attempts)
496
+ if maxDelaySec then delaySec = math.min(delaySec, maxDelaySec) end
497
+ delaySec = delaySec * (0.5 + 0.5 * math.random())
498
+ else
499
+ delaySec = retryDelaySec
500
+ end
501
+ nextAttemptAt = nowMs + math.floor(delaySec * 1000)
502
+ end
338
503
  end
339
504
 
340
505
  -- Append to error_history