@convex-dev/workpool 0.2.0-alpha.0 → 0.2.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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/dist/commonjs/client/index.d.ts +3 -3
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +10 -5
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/complete.js +1 -1
  7. package/dist/commonjs/component/complete.js.map +1 -1
  8. package/dist/commonjs/component/kick.d.ts +0 -1
  9. package/dist/commonjs/component/kick.d.ts.map +1 -1
  10. package/dist/commonjs/component/kick.js +6 -4
  11. package/dist/commonjs/component/kick.js.map +1 -1
  12. package/dist/commonjs/component/lib.d.ts.map +1 -1
  13. package/dist/commonjs/component/lib.js +4 -3
  14. package/dist/commonjs/component/lib.js.map +1 -1
  15. package/dist/commonjs/component/logging.d.ts.map +1 -1
  16. package/dist/commonjs/component/logging.js +1 -2
  17. package/dist/commonjs/component/logging.js.map +1 -1
  18. package/dist/commonjs/component/loop.d.ts.map +1 -1
  19. package/dist/commonjs/component/loop.js +46 -38
  20. package/dist/commonjs/component/loop.js.map +1 -1
  21. package/dist/commonjs/component/shared.d.ts +1 -0
  22. package/dist/commonjs/component/shared.d.ts.map +1 -1
  23. package/dist/commonjs/component/shared.js +1 -0
  24. package/dist/commonjs/component/shared.js.map +1 -1
  25. package/dist/commonjs/component/stats.d.ts +19 -13
  26. package/dist/commonjs/component/stats.d.ts.map +1 -1
  27. package/dist/commonjs/component/stats.js +26 -21
  28. package/dist/commonjs/component/stats.js.map +1 -1
  29. package/dist/esm/client/index.d.ts +3 -3
  30. package/dist/esm/client/index.d.ts.map +1 -1
  31. package/dist/esm/client/index.js +10 -5
  32. package/dist/esm/client/index.js.map +1 -1
  33. package/dist/esm/component/complete.js +1 -1
  34. package/dist/esm/component/complete.js.map +1 -1
  35. package/dist/esm/component/kick.d.ts +0 -1
  36. package/dist/esm/component/kick.d.ts.map +1 -1
  37. package/dist/esm/component/kick.js +6 -4
  38. package/dist/esm/component/kick.js.map +1 -1
  39. package/dist/esm/component/lib.d.ts.map +1 -1
  40. package/dist/esm/component/lib.js +4 -3
  41. package/dist/esm/component/lib.js.map +1 -1
  42. package/dist/esm/component/logging.d.ts.map +1 -1
  43. package/dist/esm/component/logging.js +1 -2
  44. package/dist/esm/component/logging.js.map +1 -1
  45. package/dist/esm/component/loop.d.ts.map +1 -1
  46. package/dist/esm/component/loop.js +46 -38
  47. package/dist/esm/component/loop.js.map +1 -1
  48. package/dist/esm/component/shared.d.ts +1 -0
  49. package/dist/esm/component/shared.d.ts.map +1 -1
  50. package/dist/esm/component/shared.js +1 -0
  51. package/dist/esm/component/shared.js.map +1 -1
  52. package/dist/esm/component/stats.d.ts +19 -13
  53. package/dist/esm/component/stats.d.ts.map +1 -1
  54. package/dist/esm/component/stats.js +26 -21
  55. package/dist/esm/component/stats.js.map +1 -1
  56. package/package.json +7 -6
  57. package/src/client/index.ts +18 -8
  58. package/src/component/_generated/api.d.ts +3 -0
  59. package/src/component/complete.test.ts +10 -10
  60. package/src/component/complete.ts +1 -1
  61. package/src/component/kick.test.ts +10 -9
  62. package/src/component/kick.ts +11 -6
  63. package/src/component/lib.test.ts +20 -20
  64. package/src/component/lib.ts +4 -2
  65. package/src/component/logging.ts +1 -2
  66. package/src/component/loop.test.ts +101 -147
  67. package/src/component/loop.ts +49 -39
  68. package/src/component/recovery.test.ts +7 -7
  69. package/src/component/shared.ts +1 -0
  70. package/src/component/stats.ts +41 -22
@@ -30,7 +30,7 @@ describe("complete", () => {
30
30
  await t.run(async (ctx) => {
31
31
  await ctx.db.insert("globals", {
32
32
  maxParallelism: 10,
33
- logLevel: "INFO",
33
+ logLevel: "WARN",
34
34
  });
35
35
  });
36
36
  });
@@ -50,7 +50,7 @@ describe("complete", () => {
50
50
  runAt: Date.now(),
51
51
  config: {
52
52
  maxParallelism: 10,
53
- logLevel: "INFO",
53
+ logLevel: "WARN",
54
54
  },
55
55
  });
56
56
 
@@ -95,7 +95,7 @@ describe("complete", () => {
95
95
  runAt: Date.now(),
96
96
  config: {
97
97
  maxParallelism: 10,
98
- logLevel: "INFO",
98
+ logLevel: "WARN",
99
99
  },
100
100
  retryBehavior: {
101
101
  maxAttempts: 3,
@@ -146,7 +146,7 @@ describe("complete", () => {
146
146
  runAt: Date.now(),
147
147
  config: {
148
148
  maxParallelism: 10,
149
- logLevel: "INFO",
149
+ logLevel: "WARN",
150
150
  },
151
151
  retryBehavior: {
152
152
  maxAttempts: 2, // Only 1 retry allowed
@@ -204,7 +204,7 @@ describe("complete", () => {
204
204
  runAt: Date.now(),
205
205
  config: {
206
206
  maxParallelism: 10,
207
- logLevel: "INFO",
207
+ logLevel: "WARN",
208
208
  },
209
209
  });
210
210
 
@@ -250,7 +250,7 @@ describe("complete", () => {
250
250
  runAt: Date.now(),
251
251
  config: {
252
252
  maxParallelism: 10,
253
- logLevel: "INFO",
253
+ logLevel: "WARN",
254
254
  },
255
255
  onComplete: {
256
256
  fnHandle: "testOnComplete",
@@ -298,7 +298,7 @@ describe("complete", () => {
298
298
  runAt: Date.now(),
299
299
  config: {
300
300
  maxParallelism: 10,
301
- logLevel: "INFO",
301
+ logLevel: "WARN",
302
302
  },
303
303
  });
304
304
 
@@ -310,7 +310,7 @@ describe("complete", () => {
310
310
  runAt: Date.now(),
311
311
  config: {
312
312
  maxParallelism: 10,
313
- logLevel: "INFO",
313
+ logLevel: "WARN",
314
314
  },
315
315
  retryBehavior: {
316
316
  maxAttempts: 3,
@@ -366,7 +366,7 @@ describe("complete", () => {
366
366
  runAt: Date.now(),
367
367
  config: {
368
368
  maxParallelism: 10,
369
- logLevel: "INFO",
369
+ logLevel: "WARN",
370
370
  },
371
371
  });
372
372
 
@@ -418,7 +418,7 @@ describe("complete", () => {
418
418
  runAt: Date.now(),
419
419
  config: {
420
420
  maxParallelism: 10,
421
- logLevel: "INFO",
421
+ logLevel: "WARN",
422
422
  },
423
423
  retryBehavior: {
424
424
  maxAttempts: 3,
@@ -72,7 +72,7 @@ export async function completeHandler(
72
72
  // TODO: store failures in a table for later debugging
73
73
  }
74
74
  }
75
- console.info(recordCompleted(work, job.runResult.kind));
75
+ recordCompleted(console, work, job.runResult.kind);
76
76
  // This is the terminating state for work.
77
77
  await ctx.db.delete(job.workId);
78
78
  }
@@ -8,13 +8,14 @@ import {
8
8
  test,
9
9
  vi,
10
10
  } from "vitest";
11
- import schema from "./schema.js";
12
- import { modules } from "./setup.test.js";
13
- import { DEFAULT_MAX_PARALLELISM, kickMainLoop } from "./kick.js";
14
- import { DEFAULT_LOG_LEVEL } from "./logging.js";
15
11
  import { internal } from "./_generated/api";
16
- import { toSegment, fromSegment, nextSegment } from "./shared";
17
12
  import { Id } from "./_generated/dataModel.js";
13
+ import { kickMainLoop } from "./kick.js";
14
+ import { DEFAULT_LOG_LEVEL } from "./logging.js";
15
+ import schema from "./schema.js";
16
+ import { modules } from "./setup.test.js";
17
+ import { fromSegment, nextSegment, toSegment } from "./shared";
18
+ import { DEFAULT_MAX_PARALLELISM } from "./shared.js";
18
19
 
19
20
  describe("kickMainLoop", () => {
20
21
  beforeEach(() => {
@@ -53,13 +54,13 @@ describe("kickMainLoop", () => {
53
54
  expect(globals.logLevel).toBe(DEFAULT_LOG_LEVEL);
54
55
  await kickMainLoop(ctx, "enqueue", {
55
56
  maxParallelism: DEFAULT_MAX_PARALLELISM + 1,
56
- logLevel: "DEBUG",
57
+ logLevel: "ERROR",
57
58
  });
58
59
  const after = await ctx.db.query("globals").unique();
59
60
  expect(after).not.toBeNull();
60
61
  assert(after);
61
62
  expect(after.maxParallelism).toBe(DEFAULT_MAX_PARALLELISM + 1);
62
- expect(after.logLevel).toBe("DEBUG");
63
+ expect(after.logLevel).toBe("ERROR");
63
64
  });
64
65
  });
65
66
 
@@ -242,7 +243,7 @@ describe("kickMainLoop", () => {
242
243
  // Initial kick with custom config
243
244
  await kickMainLoop(ctx, "enqueue", {
244
245
  maxParallelism: 5,
245
- logLevel: "DEBUG",
246
+ logLevel: "ERROR",
246
247
  });
247
248
 
248
249
  // Kick from different sources
@@ -254,7 +255,7 @@ describe("kickMainLoop", () => {
254
255
  expect(globals).not.toBeNull();
255
256
  assert(globals);
256
257
  expect(globals.maxParallelism).toBe(5);
257
- expect(globals.logLevel).toBe("DEBUG");
258
+ expect(globals.logLevel).toBe("ERROR");
258
259
  });
259
260
  });
260
261
 
@@ -2,9 +2,14 @@ import { internal } from "./_generated/api.js";
2
2
  import { internalMutation, MutationCtx } from "./_generated/server.js";
3
3
  import { createLogger, DEFAULT_LOG_LEVEL } from "./logging.js";
4
4
  import { INITIAL_STATE } from "./loop.js";
5
- import { Config, nextSegment } from "./shared.js";
5
+ import {
6
+ boundScheduledTime,
7
+ Config,
8
+ DEFAULT_MAX_PARALLELISM,
9
+ fromSegment,
10
+ nextSegment,
11
+ } from "./shared.js";
6
12
 
7
- export const DEFAULT_MAX_PARALLELISM = 10;
8
13
  /**
9
14
  * Called from outside the loop:
10
15
  */
@@ -51,12 +56,12 @@ export async function kickMainLoop(
51
56
  `[${source}] main is marked as scheduled, but it's status is ${scheduled?.state.kind}`
52
57
  );
53
58
  }
59
+ } else if (runStatus.state.kind === "idle") {
60
+ console.debug(`[${source}] main was idle, so run it now`);
54
61
  }
55
- console.debug(
56
- `[${source}] main was scheduled later, so reschedule it to run now`
57
- );
58
62
  await ctx.db.patch(runStatus._id, { state: { kind: "running" } });
59
- await ctx.scheduler.runAfter(0, internal.loop.main, {
63
+ const scheduledTime = boundScheduledTime(fromSegment(segment), console);
64
+ await ctx.scheduler.runAt(scheduledTime, internal.loop.main, {
60
65
  generation: runStatus.state.generation,
61
66
  segment,
62
67
  });
@@ -44,7 +44,7 @@ describe("lib", () => {
44
44
  runAt: Date.now(),
45
45
  config: {
46
46
  maxParallelism: 10,
47
- logLevel: "INFO",
47
+ logLevel: "WARN",
48
48
  },
49
49
  });
50
50
 
@@ -63,7 +63,7 @@ describe("lib", () => {
63
63
  runAt: Date.now(),
64
64
  config: {
65
65
  maxParallelism: 101, // More than MAX_POSSIBLE_PARALLELISM
66
- logLevel: "INFO",
66
+ logLevel: "WARN",
67
67
  },
68
68
  })
69
69
  ).rejects.toThrow("maxParallelism must be <= 100");
@@ -79,7 +79,7 @@ describe("lib", () => {
79
79
  runAt: Date.now(),
80
80
  config: {
81
81
  maxParallelism: 0, // Less than minimum
82
- logLevel: "INFO",
82
+ logLevel: "WARN",
83
83
  },
84
84
  })
85
85
  ).rejects.toThrow("maxParallelism must be >= 1");
@@ -96,13 +96,13 @@ describe("lib", () => {
96
96
  runAt: Date.now(),
97
97
  config: {
98
98
  maxParallelism: 10,
99
- logLevel: "INFO",
99
+ logLevel: "WARN",
100
100
  },
101
101
  });
102
102
 
103
103
  await t.mutation(api.lib.cancel, {
104
104
  id,
105
- logLevel: "INFO",
105
+ logLevel: "WARN",
106
106
  });
107
107
 
108
108
  // Verify a pending cancelation was created
@@ -124,20 +124,20 @@ describe("lib", () => {
124
124
  runAt: Date.now(),
125
125
  config: {
126
126
  maxParallelism: 10,
127
- logLevel: "INFO",
127
+ logLevel: "WARN",
128
128
  },
129
129
  });
130
130
 
131
131
  // Cancel the first time
132
132
  await t.mutation(api.lib.cancel, {
133
133
  id,
134
- logLevel: "INFO",
134
+ logLevel: "WARN",
135
135
  });
136
136
 
137
137
  // Cancel the second time
138
138
  await t.mutation(api.lib.cancel, {
139
139
  id,
140
- logLevel: "INFO",
140
+ logLevel: "WARN",
141
141
  });
142
142
 
143
143
  // Verify only one pending cancelation was created
@@ -159,7 +159,7 @@ describe("lib", () => {
159
159
  runAt: Date.now(),
160
160
  config: {
161
161
  maxParallelism: 10,
162
- logLevel: "INFO",
162
+ logLevel: "WARN",
163
163
  },
164
164
  });
165
165
 
@@ -171,7 +171,7 @@ describe("lib", () => {
171
171
  // Try to cancel the deleted work
172
172
  await t.mutation(api.lib.cancel, {
173
173
  id,
174
- logLevel: "INFO",
174
+ logLevel: "WARN",
175
175
  });
176
176
 
177
177
  // Verify no pending cancelation was created
@@ -196,14 +196,14 @@ describe("lib", () => {
196
196
  runAt: Date.now() + 5 * 60 * 1000,
197
197
  config: {
198
198
  maxParallelism: 10,
199
- logLevel: "INFO",
199
+ logLevel: "WARN",
200
200
  },
201
201
  });
202
202
  ids.push(id);
203
203
  }
204
204
 
205
205
  await t.mutation(api.lib.cancelAll, {
206
- logLevel: "INFO",
206
+ logLevel: "WARN",
207
207
  before: Date.now() + 1000,
208
208
  });
209
209
 
@@ -231,13 +231,13 @@ describe("lib", () => {
231
231
  runAt: Date.now(),
232
232
  config: {
233
233
  maxParallelism: 10,
234
- logLevel: "INFO",
234
+ logLevel: "WARN",
235
235
  },
236
236
  });
237
237
  }
238
238
 
239
239
  await t.mutation(api.lib.cancelAll, {
240
- logLevel: "INFO",
240
+ logLevel: "WARN",
241
241
  before: Date.now() + 1000,
242
242
  });
243
243
 
@@ -277,7 +277,7 @@ describe("lib", () => {
277
277
  runAt: Date.now(),
278
278
  config: {
279
279
  maxParallelism: 10,
280
- logLevel: "INFO",
280
+ logLevel: "WARN",
281
281
  },
282
282
  });
283
283
  await t.run(async (ctx) => {
@@ -297,7 +297,7 @@ describe("lib", () => {
297
297
  runAt: Date.now(),
298
298
  config: {
299
299
  maxParallelism: 10,
300
- logLevel: "INFO",
300
+ logLevel: "WARN",
301
301
  },
302
302
  });
303
303
 
@@ -323,7 +323,7 @@ describe("lib", () => {
323
323
  runAt: Date.now(),
324
324
  config: {
325
325
  maxParallelism: 10,
326
- logLevel: "INFO",
326
+ logLevel: "WARN",
327
327
  },
328
328
  });
329
329
 
@@ -353,7 +353,7 @@ describe("lib", () => {
353
353
  },
354
354
  config: {
355
355
  maxParallelism: 10,
356
- logLevel: "INFO",
356
+ logLevel: "WARN",
357
357
  },
358
358
  });
359
359
 
@@ -386,7 +386,7 @@ describe("lib", () => {
386
386
  runAt: Date.now(),
387
387
  config: {
388
388
  maxParallelism: 10,
389
- logLevel: "INFO",
389
+ logLevel: "WARN",
390
390
  },
391
391
  });
392
392
 
@@ -419,7 +419,7 @@ describe("lib", () => {
419
419
  runAt: Date.now(),
420
420
  config: {
421
421
  maxParallelism: 10,
422
- logLevel: "INFO",
422
+ logLevel: "WARN",
423
423
  },
424
424
  });
425
425
 
@@ -8,12 +8,14 @@ import {
8
8
  status as statusValidator,
9
9
  toSegment,
10
10
  boundScheduledTime,
11
+ max,
11
12
  } from "./shared.js";
12
13
  import { LogLevel, logLevel } from "./logging.js";
13
14
  import { kickMainLoop } from "./kick.js";
14
15
  import { api } from "./_generated/api.js";
15
16
  import { createLogger } from "./logging.js";
16
17
  import { Id } from "./_generated/dataModel.js";
18
+ import { recordEnqueued } from "./stats.js";
17
19
 
18
20
  const MAX_POSSIBLE_PARALLELISM = 100;
19
21
 
@@ -45,10 +47,10 @@ export const enqueue = mutation({
45
47
  });
46
48
  await ctx.db.insert("pendingStart", {
47
49
  workId,
48
- segment: toSegment(runAt),
50
+ segment: max(toSegment(runAt), nextSegment()),
49
51
  });
50
52
  await kickMainLoop(ctx, "enqueue", config);
51
- // TODO: stats event
53
+ recordEnqueued(console, { workId, fnName: workArgs.fnName, runAt });
52
54
  return workId;
53
55
  },
54
56
  });
@@ -53,9 +53,8 @@ export function createLogger(level?: LogLevel): Logger {
53
53
  event: (event: string, payload: Record<string, unknown>) => {
54
54
  if (levelIndex <= 1) {
55
55
  const fullPayload = {
56
- system: "idempotent-workpool-component",
57
56
  event,
58
- payload,
57
+ ...payload,
59
58
  };
60
59
  console.info(JSON.stringify(fullPayload));
61
60
  }