@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
package/src/queue.test.ts CHANGED
@@ -79,6 +79,47 @@ describe('queue integration', () => {
79
79
  expect(job?.status).toBe('completed');
80
80
  });
81
81
 
82
+ it('should store output when completing a job', async () => {
83
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
84
+ jobType: 'email',
85
+ payload: { to: 'output@example.com' },
86
+ });
87
+ await queue.getNextBatch(pool, 'worker-output', 1);
88
+ await queue.completeJob(pool, jobId, {
89
+ url: 'https://example.com/report.pdf',
90
+ });
91
+ const job = await queue.getJob(pool, jobId);
92
+ expect(job?.status).toBe('completed');
93
+ expect(job?.output).toEqual({ url: 'https://example.com/report.pdf' });
94
+ });
95
+
96
+ it('should have null output when completing without output', async () => {
97
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
98
+ jobType: 'email',
99
+ payload: { to: 'no-output@example.com' },
100
+ });
101
+ await queue.getNextBatch(pool, 'worker-no-output', 1);
102
+ await queue.completeJob(pool, jobId);
103
+ const job = await queue.getJob(pool, jobId);
104
+ expect(job?.status).toBe('completed');
105
+ expect(job?.output).toBeNull();
106
+ });
107
+
108
+ it('should preserve output set via updateOutput when completing without output arg', async () => {
109
+ const { PostgresBackend } = await import('./backends/postgres.js');
110
+ const backend = new PostgresBackend(pool);
111
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
112
+ jobType: 'email',
113
+ payload: { to: 'pre-output@example.com' },
114
+ });
115
+ await queue.getNextBatch(pool, 'worker-pre-output', 1);
116
+ await backend.updateOutput(jobId, { interim: true });
117
+ await queue.completeJob(pool, jobId);
118
+ const job = await queue.getJob(pool, jobId);
119
+ expect(job?.status).toBe('completed');
120
+ expect(job?.output).toEqual({ interim: true });
121
+ });
122
+
82
123
  it('should get the next batch of jobs to process', async () => {
83
124
  // Add jobs (do not set runAt, use DB default)
84
125
  const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
@@ -1958,4 +1999,440 @@ describe('getJobs', () => {
1958
1999
  const batch3 = await queue.getNextBatch(pool, 'worker-1', 1);
1959
2000
  expect(batch3.length).toBe(0);
1960
2001
  });
2002
+
2003
+ // ── Configurable retry strategy tests ────────────────────────────────
2004
+
2005
+ it('uses legacy backoff when no retry config is set', async () => {
2006
+ // Setup
2007
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2008
+ jobType: 'email',
2009
+ payload: { to: 'legacy@example.com' },
2010
+ maxAttempts: 3,
2011
+ });
2012
+
2013
+ // Act
2014
+ await queue.getNextBatch(pool, 'worker-1', 1);
2015
+ await queue.failJob(pool, jobId, new Error('fail'));
2016
+
2017
+ // Assert — legacy formula: 2^1 * 60s = 120s from now
2018
+ const job = await queue.getJob(pool, jobId);
2019
+ expect(job?.nextAttemptAt).not.toBeNull();
2020
+ const delaySec =
2021
+ (job!.nextAttemptAt!.getTime() - job!.lastFailedAt!.getTime()) / 1000;
2022
+ expect(delaySec).toBeGreaterThanOrEqual(115);
2023
+ expect(delaySec).toBeLessThanOrEqual(125);
2024
+ });
2025
+
2026
+ it('uses fixed delay when retryBackoff is false', async () => {
2027
+ // Setup
2028
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2029
+ jobType: 'email',
2030
+ payload: { to: 'fixed@example.com' },
2031
+ maxAttempts: 3,
2032
+ retryDelay: 10,
2033
+ retryBackoff: false,
2034
+ });
2035
+
2036
+ // Act
2037
+ await queue.getNextBatch(pool, 'worker-1', 1);
2038
+ await queue.failJob(pool, jobId, new Error('fail'));
2039
+
2040
+ // Assert — fixed 10s delay
2041
+ const job = await queue.getJob(pool, jobId);
2042
+ expect(job?.nextAttemptAt).not.toBeNull();
2043
+ expect(job?.retryDelay).toBe(10);
2044
+ expect(job?.retryBackoff).toBe(false);
2045
+ const delaySec =
2046
+ (job!.nextAttemptAt!.getTime() - job!.lastFailedAt!.getTime()) / 1000;
2047
+ expect(delaySec).toBeGreaterThanOrEqual(9);
2048
+ expect(delaySec).toBeLessThanOrEqual(11);
2049
+ });
2050
+
2051
+ it('uses exponential backoff with custom retryDelay', async () => {
2052
+ // Setup
2053
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2054
+ jobType: 'email',
2055
+ payload: { to: 'expo@example.com' },
2056
+ maxAttempts: 3,
2057
+ retryDelay: 5,
2058
+ retryBackoff: true,
2059
+ });
2060
+
2061
+ // Act — attempt 1
2062
+ await queue.getNextBatch(pool, 'worker-1', 1);
2063
+ await queue.failJob(pool, jobId, new Error('fail'));
2064
+
2065
+ // Assert — exponential: 5 * 2^1 = 10s, with jitter [5, 10]
2066
+ const job = await queue.getJob(pool, jobId);
2067
+ expect(job?.nextAttemptAt).not.toBeNull();
2068
+ const delaySec =
2069
+ (job!.nextAttemptAt!.getTime() - job!.lastFailedAt!.getTime()) / 1000;
2070
+ expect(delaySec).toBeGreaterThanOrEqual(4);
2071
+ expect(delaySec).toBeLessThanOrEqual(11);
2072
+ });
2073
+
2074
+ it('caps exponential backoff with retryDelayMax', async () => {
2075
+ // Setup
2076
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2077
+ jobType: 'email',
2078
+ payload: { to: 'capped@example.com' },
2079
+ maxAttempts: 5,
2080
+ retryDelay: 100,
2081
+ retryBackoff: true,
2082
+ retryDelayMax: 30,
2083
+ });
2084
+
2085
+ // Act — attempt 1
2086
+ await queue.getNextBatch(pool, 'worker-1', 1);
2087
+ await queue.failJob(pool, jobId, new Error('fail'));
2088
+
2089
+ // Assert — 100 * 2^1 = 200s but capped at 30s, with jitter [15, 30]
2090
+ const job = await queue.getJob(pool, jobId);
2091
+ expect(job?.nextAttemptAt).not.toBeNull();
2092
+ expect(job?.retryDelayMax).toBe(30);
2093
+ const delaySec =
2094
+ (job!.nextAttemptAt!.getTime() - job!.lastFailedAt!.getTime()) / 1000;
2095
+ expect(delaySec).toBeGreaterThanOrEqual(14);
2096
+ expect(delaySec).toBeLessThanOrEqual(31);
2097
+ });
2098
+
2099
+ it('stores retry config on job record', async () => {
2100
+ // Setup
2101
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2102
+ jobType: 'email',
2103
+ payload: { to: 'config@example.com' },
2104
+ retryDelay: 30,
2105
+ retryBackoff: false,
2106
+ retryDelayMax: 120,
2107
+ });
2108
+
2109
+ // Act
2110
+ const job = await queue.getJob(pool, jobId);
2111
+
2112
+ // Assert
2113
+ expect(job?.retryDelay).toBe(30);
2114
+ expect(job?.retryBackoff).toBe(false);
2115
+ expect(job?.retryDelayMax).toBe(120);
2116
+ });
2117
+
2118
+ it('returns null retry config for jobs without it', async () => {
2119
+ // Setup
2120
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2121
+ jobType: 'email',
2122
+ payload: { to: 'noconfig@example.com' },
2123
+ });
2124
+
2125
+ // Act
2126
+ const job = await queue.getJob(pool, jobId);
2127
+
2128
+ // Assert
2129
+ expect(job?.retryDelay).toBeNull();
2130
+ expect(job?.retryBackoff).toBeNull();
2131
+ expect(job?.retryDelayMax).toBeNull();
2132
+ });
2133
+
2134
+ it('allows editing retry config via editJob', async () => {
2135
+ // Setup
2136
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
2137
+ jobType: 'email',
2138
+ payload: { to: 'edit@example.com' },
2139
+ });
2140
+
2141
+ // Act
2142
+ await queue.editJob(pool, jobId, {
2143
+ retryDelay: 15,
2144
+ retryBackoff: false,
2145
+ retryDelayMax: 60,
2146
+ });
2147
+
2148
+ // Assert
2149
+ const job = await queue.getJob(pool, jobId);
2150
+ expect(job?.retryDelay).toBe(15);
2151
+ expect(job?.retryBackoff).toBe(false);
2152
+ expect(job?.retryDelayMax).toBe(60);
2153
+ });
2154
+ });
2155
+
2156
+ describe('queue.addJob with db option (BYOC)', () => {
2157
+ let pool: Pool;
2158
+ let dbName: string;
2159
+
2160
+ beforeEach(async () => {
2161
+ const setup = await createTestDbAndPool();
2162
+ pool = setup.pool;
2163
+ dbName = setup.dbName;
2164
+ });
2165
+
2166
+ afterEach(async () => {
2167
+ await pool.end();
2168
+ await destroyTestDb(dbName);
2169
+ });
2170
+
2171
+ it('rolls back the job when the transaction is rolled back', async () => {
2172
+ // Setup
2173
+ const client = await pool.connect();
2174
+ await client.query('BEGIN');
2175
+
2176
+ // Act
2177
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(
2178
+ pool,
2179
+ { jobType: 'email', payload: { to: 'rollback@example.com' } },
2180
+ { db: client },
2181
+ );
2182
+ await client.query('ROLLBACK');
2183
+ client.release();
2184
+
2185
+ // Assert
2186
+ const job = await queue.getJob(pool, jobId);
2187
+ expect(job).toBeNull();
2188
+ });
2189
+
2190
+ it('persists the job when the transaction is committed', async () => {
2191
+ // Setup
2192
+ const client = await pool.connect();
2193
+ await client.query('BEGIN');
2194
+
2195
+ // Act
2196
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(
2197
+ pool,
2198
+ { jobType: 'email', payload: { to: 'commit@example.com' } },
2199
+ { db: client },
2200
+ );
2201
+ await client.query('COMMIT');
2202
+ client.release();
2203
+
2204
+ // Assert
2205
+ const job = await queue.getJob(pool, jobId);
2206
+ expect(job).not.toBeNull();
2207
+ expect(job?.payload).toEqual({ to: 'commit@example.com' });
2208
+ });
2209
+ });
2210
+
2211
+ describe('addJobs batch insert', () => {
2212
+ let pool: Pool;
2213
+ let dbName: string;
2214
+
2215
+ beforeEach(async () => {
2216
+ const setup = await createTestDbAndPool();
2217
+ pool = setup.pool;
2218
+ dbName = setup.dbName;
2219
+ });
2220
+
2221
+ afterEach(async () => {
2222
+ await pool.end();
2223
+ await destroyTestDb(dbName);
2224
+ });
2225
+
2226
+ it('inserts multiple jobs and returns IDs in order', async () => {
2227
+ // Act
2228
+ const ids = await queue.addJobs<
2229
+ { email: { to: string }; report: { id: string } },
2230
+ 'email' | 'report'
2231
+ >(pool, [
2232
+ { jobType: 'email', payload: { to: 'a@test.com' } },
2233
+ { jobType: 'report', payload: { id: 'r1' } },
2234
+ { jobType: 'email', payload: { to: 'b@test.com' } },
2235
+ ]);
2236
+
2237
+ // Assert
2238
+ expect(ids).toHaveLength(3);
2239
+ expect(ids[0]).toBeLessThan(ids[1]);
2240
+ expect(ids[1]).toBeLessThan(ids[2]);
2241
+
2242
+ const job0 = await queue.getJob(pool, ids[0]);
2243
+ expect(job0?.jobType).toBe('email');
2244
+ expect(job0?.payload).toEqual({ to: 'a@test.com' });
2245
+
2246
+ const job1 = await queue.getJob(pool, ids[1]);
2247
+ expect(job1?.jobType).toBe('report');
2248
+ expect(job1?.payload).toEqual({ id: 'r1' });
2249
+
2250
+ const job2 = await queue.getJob(pool, ids[2]);
2251
+ expect(job2?.jobType).toBe('email');
2252
+ expect(job2?.payload).toEqual({ to: 'b@test.com' });
2253
+ });
2254
+
2255
+ it('returns empty array for empty input', async () => {
2256
+ // Act
2257
+ const ids = await queue.addJobs(pool, []);
2258
+
2259
+ // Assert
2260
+ expect(ids).toEqual([]);
2261
+ });
2262
+
2263
+ it('respects priority and runAt per job', async () => {
2264
+ // Setup
2265
+ const futureDate = new Date(Date.now() + 60_000);
2266
+
2267
+ // Act
2268
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(pool, [
2269
+ { jobType: 'task', payload: { n: 1 }, priority: 5 },
2270
+ { jobType: 'task', payload: { n: 2 }, priority: 10, runAt: futureDate },
2271
+ ]);
2272
+
2273
+ // Assert
2274
+ const job0 = await queue.getJob(pool, ids[0]);
2275
+ expect(job0?.priority).toBe(5);
2276
+
2277
+ const job1 = await queue.getJob(pool, ids[1]);
2278
+ expect(job1?.priority).toBe(10);
2279
+ expect(job1?.runAt.getTime()).toBeCloseTo(futureDate.getTime(), -3);
2280
+ });
2281
+
2282
+ it('handles idempotency keys for new jobs', async () => {
2283
+ // Act
2284
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(pool, [
2285
+ { jobType: 'task', payload: { n: 1 }, idempotencyKey: 'key-a' },
2286
+ { jobType: 'task', payload: { n: 2 }, idempotencyKey: 'key-b' },
2287
+ ]);
2288
+
2289
+ // Assert
2290
+ expect(ids).toHaveLength(2);
2291
+ expect(ids[0]).not.toBe(ids[1]);
2292
+
2293
+ const job0 = await queue.getJob(pool, ids[0]);
2294
+ expect(job0?.idempotencyKey).toBe('key-a');
2295
+
2296
+ const job1 = await queue.getJob(pool, ids[1]);
2297
+ expect(job1?.idempotencyKey).toBe('key-b');
2298
+ });
2299
+
2300
+ it('returns existing IDs for conflicting idempotency keys', async () => {
2301
+ // Setup — insert a job first
2302
+ const existingId = await queue.addJob<{ task: { n: number } }, 'task'>(
2303
+ pool,
2304
+ { jobType: 'task', payload: { n: 0 }, idempotencyKey: 'dup-key' },
2305
+ );
2306
+
2307
+ // Act — batch includes a duplicate key
2308
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(pool, [
2309
+ { jobType: 'task', payload: { n: 1 } },
2310
+ { jobType: 'task', payload: { n: 2 }, idempotencyKey: 'dup-key' },
2311
+ { jobType: 'task', payload: { n: 3 } },
2312
+ ]);
2313
+
2314
+ // Assert
2315
+ expect(ids).toHaveLength(3);
2316
+ expect(ids[1]).toBe(existingId);
2317
+ expect(ids[0]).not.toBe(existingId);
2318
+ expect(ids[2]).not.toBe(existingId);
2319
+ });
2320
+
2321
+ it('handles mix of keyed and non-keyed jobs', async () => {
2322
+ // Act
2323
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(pool, [
2324
+ { jobType: 'task', payload: { n: 1 } },
2325
+ { jobType: 'task', payload: { n: 2 }, idempotencyKey: 'mix-1' },
2326
+ { jobType: 'task', payload: { n: 3 } },
2327
+ { jobType: 'task', payload: { n: 4 }, idempotencyKey: 'mix-2' },
2328
+ { jobType: 'task', payload: { n: 5 } },
2329
+ ]);
2330
+
2331
+ // Assert
2332
+ expect(ids).toHaveLength(5);
2333
+ const uniqueIds = new Set(ids);
2334
+ expect(uniqueIds.size).toBe(5);
2335
+
2336
+ const job1 = await queue.getJob(pool, ids[1]);
2337
+ expect(job1?.idempotencyKey).toBe('mix-1');
2338
+
2339
+ const job3 = await queue.getJob(pool, ids[3]);
2340
+ expect(job3?.idempotencyKey).toBe('mix-2');
2341
+ });
2342
+
2343
+ it('records added events only for newly inserted jobs', async () => {
2344
+ // Setup — pre-insert a job with a known key
2345
+ const existingId = await queue.addJob<{ task: { n: number } }, 'task'>(
2346
+ pool,
2347
+ { jobType: 'task', payload: { n: 0 }, idempotencyKey: 'evt-key' },
2348
+ );
2349
+
2350
+ // Act
2351
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(pool, [
2352
+ { jobType: 'task', payload: { n: 1 } },
2353
+ { jobType: 'task', payload: { n: 2 }, idempotencyKey: 'evt-key' },
2354
+ ]);
2355
+
2356
+ // Assert — the new job should have an event from addJobs
2357
+ const events0 = await queue.getJobEvents(pool, ids[0]);
2358
+ const addedEvents0 = events0.filter(
2359
+ (e: JobEvent) => e.eventType === JobEventType.Added,
2360
+ );
2361
+ expect(addedEvents0).toHaveLength(1);
2362
+
2363
+ // The duplicate should only have the original event from addJob, not a second from addJobs
2364
+ const eventsExisting = await queue.getJobEvents(pool, existingId);
2365
+ const addedEventsExisting = eventsExisting.filter(
2366
+ (e: JobEvent) => e.eventType === JobEventType.Added,
2367
+ );
2368
+ expect(addedEventsExisting).toHaveLength(1);
2369
+ });
2370
+
2371
+ it('stores tags correctly per job', async () => {
2372
+ // Act
2373
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(pool, [
2374
+ { jobType: 'task', payload: { n: 1 }, tags: ['urgent', 'billing'] },
2375
+ { jobType: 'task', payload: { n: 2 }, tags: ['low-priority'] },
2376
+ { jobType: 'task', payload: { n: 3 } },
2377
+ ]);
2378
+
2379
+ // Assert
2380
+ const job0 = await queue.getJob(pool, ids[0]);
2381
+ expect(job0?.tags).toEqual(['urgent', 'billing']);
2382
+
2383
+ const job1 = await queue.getJob(pool, ids[1]);
2384
+ expect(job1?.tags).toEqual(['low-priority']);
2385
+
2386
+ const job2 = await queue.getJob(pool, ids[2]);
2387
+ expect(job2?.tags).toBeNull();
2388
+ });
2389
+
2390
+ it('works with transactional db option — commit', async () => {
2391
+ // Setup
2392
+ const client = await pool.connect();
2393
+ await client.query('BEGIN');
2394
+
2395
+ // Act
2396
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(
2397
+ pool,
2398
+ [
2399
+ { jobType: 'task', payload: { n: 1 } },
2400
+ { jobType: 'task', payload: { n: 2 } },
2401
+ ],
2402
+ { db: client },
2403
+ );
2404
+ await client.query('COMMIT');
2405
+ client.release();
2406
+
2407
+ // Assert
2408
+ expect(ids).toHaveLength(2);
2409
+ const job0 = await queue.getJob(pool, ids[0]);
2410
+ expect(job0).not.toBeNull();
2411
+ const job1 = await queue.getJob(pool, ids[1]);
2412
+ expect(job1).not.toBeNull();
2413
+ });
2414
+
2415
+ it('works with transactional db option — rollback', async () => {
2416
+ // Setup
2417
+ const client = await pool.connect();
2418
+ await client.query('BEGIN');
2419
+
2420
+ // Act
2421
+ const ids = await queue.addJobs<{ task: { n: number } }, 'task'>(
2422
+ pool,
2423
+ [
2424
+ { jobType: 'task', payload: { n: 1 } },
2425
+ { jobType: 'task', payload: { n: 2 } },
2426
+ ],
2427
+ { db: client },
2428
+ );
2429
+ await client.query('ROLLBACK');
2430
+ client.release();
2431
+
2432
+ // Assert — jobs should not exist after rollback
2433
+ const job0 = await queue.getJob(pool, ids[0]);
2434
+ expect(job0).toBeNull();
2435
+ const job1 = await queue.getJob(pool, ids[1]);
2436
+ expect(job1).toBeNull();
2437
+ });
1961
2438
  });
package/src/queue.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  JobEventType,
17
17
  TagQueryMode,
18
18
  WaitpointRecord,
19
+ AddJobOptions,
19
20
  } from './types.js';
20
21
  import { PostgresBackend } from './backends/postgres.js';
21
22
 
@@ -34,7 +35,14 @@ export const recordJobEvent = async (
34
35
  export const addJob = async <PayloadMap, T extends keyof PayloadMap & string>(
35
36
  pool: Pool,
36
37
  job: JobOptions<PayloadMap, T>,
37
- ): Promise<number> => new PostgresBackend(pool).addJob(job);
38
+ options?: AddJobOptions,
39
+ ): Promise<number> => new PostgresBackend(pool).addJob(job, options);
40
+
41
+ export const addJobs = async <PayloadMap, T extends keyof PayloadMap & string>(
42
+ pool: Pool,
43
+ jobs: JobOptions<PayloadMap, T>[],
44
+ options?: AddJobOptions,
45
+ ): Promise<number[]> => new PostgresBackend(pool).addJobs(jobs, options);
38
46
 
39
47
  export const getJob = async <PayloadMap, T extends keyof PayloadMap & string>(
40
48
  pool: Pool,
@@ -72,8 +80,11 @@ export const getNextBatch = async <
72
80
  jobType,
73
81
  );
74
82
 
75
- export const completeJob = async (pool: Pool, jobId: number): Promise<void> =>
76
- new PostgresBackend(pool).completeJob(jobId);
83
+ export const completeJob = async (
84
+ pool: Pool,
85
+ jobId: number,
86
+ output?: unknown,
87
+ ): Promise<void> => new PostgresBackend(pool).completeJob(jobId, output);
77
88
 
78
89
  export const prolongJob = async (pool: Pool, jobId: number): Promise<void> =>
79
90
  new PostgresBackend(pool).prolongJob(jobId);
@@ -109,6 +120,9 @@ export const editJob = async <PayloadMap, T extends keyof PayloadMap & string>(
109
120
  runAt?: Date | null;
110
121
  timeoutMs?: number | null;
111
122
  tags?: string[] | null;
123
+ retryDelay?: number | null;
124
+ retryBackoff?: boolean | null;
125
+ retryDelayMax?: number | null;
112
126
  },
113
127
  ): Promise<void> => new PostgresBackend(pool).editJob(jobId, updates);
114
128
 
@@ -134,6 +148,9 @@ export const editAllPendingJobs = async <
134
148
  runAt?: Date | null;
135
149
  timeoutMs?: number;
136
150
  tags?: string[];
151
+ retryDelay?: number | null;
152
+ retryBackoff?: boolean | null;
153
+ retryDelayMax?: number | null;
137
154
  },
138
155
  ): Promise<number> =>
139
156
  new PostgresBackend(pool).editAllPendingJobs(filters, updates);