@convex-dev/workpool 0.3.1-alpha.1 → 0.3.1
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/README.md +9 -0
- package/dist/client/index.d.ts +9 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +22 -22
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +12 -6
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/dataModel.d.ts +1 -1
- package/dist/component/complete.d.ts.map +1 -1
- package/dist/component/complete.js +2 -2
- package/dist/component/complete.js.map +1 -1
- package/dist/component/config.d.ts +16 -0
- package/dist/component/config.d.ts.map +1 -0
- package/dist/component/config.js +63 -0
- package/dist/component/config.js.map +1 -0
- package/dist/component/kick.d.ts +1 -1
- package/dist/component/kick.d.ts.map +1 -1
- package/dist/component/kick.js +4 -29
- package/dist/component/kick.js.map +1 -1
- package/dist/component/lib.d.ts +6 -8
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +19 -29
- package/dist/component/lib.js.map +1 -1
- package/dist/component/loop.d.ts.map +1 -1
- package/dist/component/loop.js +4 -4
- package/dist/component/loop.js.map +1 -1
- package/dist/component/schema.d.ts +1 -3
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +4 -4
- package/dist/component/schema.js.map +1 -1
- package/dist/component/shared.d.ts +6 -9
- package/dist/component/shared.d.ts.map +1 -1
- package/dist/component/shared.js +3 -4
- package/dist/component/shared.js.map +1 -1
- package/package.json +22 -22
- package/src/client/index.ts +33 -25
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +18 -6
- package/src/component/_generated/dataModel.ts +1 -1
- package/src/component/complete.ts +2 -6
- package/src/component/config.test.ts +31 -0
- package/src/component/config.ts +72 -0
- package/src/component/kick.test.ts +2 -23
- package/src/component/kick.ts +4 -32
- package/src/component/lib.test.ts +3 -3
- package/src/component/lib.ts +21 -34
- package/src/component/loop.ts +3 -4
- package/src/component/recovery.test.ts +122 -122
- package/src/component/schema.ts +6 -6
- package/src/component/shared.ts +5 -7
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { convexTest } from "convex-test";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
DocumentByName,
|
|
4
|
+
GenericDatabaseReader,
|
|
5
|
+
GenericDataModel,
|
|
6
|
+
SystemDataModel,
|
|
7
|
+
SystemTableNames,
|
|
8
|
+
WithoutSystemFields,
|
|
9
|
+
} from "convex/server";
|
|
3
10
|
import {
|
|
4
11
|
afterEach,
|
|
5
12
|
assert,
|
|
@@ -14,8 +21,7 @@ import type { Doc, Id } from "./_generated/dataModel.js";
|
|
|
14
21
|
import type { MutationCtx } from "./_generated/server.js";
|
|
15
22
|
import { recoveryHandler } from "./recovery.js";
|
|
16
23
|
import schema from "./schema.js";
|
|
17
|
-
|
|
18
|
-
const modules = import.meta.glob("./**/*.ts");
|
|
24
|
+
import { modules } from "./setup.test.js";
|
|
19
25
|
|
|
20
26
|
describe("recovery", () => {
|
|
21
27
|
async function setupTest() {
|
|
@@ -196,13 +202,7 @@ describe("recovery", () => {
|
|
|
196
202
|
// Run recovery with mocked system.get
|
|
197
203
|
await t.run(async (ctx) => {
|
|
198
204
|
// Mock the system.get to return null for our scheduledId
|
|
199
|
-
|
|
200
|
-
ctx.db.system.get = async (id) => {
|
|
201
|
-
if (id === scheduledId) {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
return await originalGet(id);
|
|
205
|
-
};
|
|
205
|
+
ctx.db.system.get = patchedSystemGet(ctx.db, { [scheduledId]: null });
|
|
206
206
|
|
|
207
207
|
await recoveryHandler(ctx, {
|
|
208
208
|
jobs: [
|
|
@@ -244,31 +244,27 @@ describe("recovery", () => {
|
|
|
244
244
|
// Run recovery with mocked failed state
|
|
245
245
|
await t.run(async (ctx) => {
|
|
246
246
|
// Mock the system.get to return a failed state
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
logLevel: "WARN",
|
|
260
|
-
attempt: 0,
|
|
261
|
-
},
|
|
262
|
-
],
|
|
263
|
-
scheduledTime: Date.now(),
|
|
264
|
-
state: {
|
|
265
|
-
kind: "failed",
|
|
266
|
-
error: "Function execution failed",
|
|
247
|
+
ctx.db.system.get = patchedSystemGet(ctx.db, {
|
|
248
|
+
[scheduledId]: {
|
|
249
|
+
_id: scheduledId,
|
|
250
|
+
_creationTime: Date.now(),
|
|
251
|
+
name: "internal/worker.runActionWrapper",
|
|
252
|
+
args: [
|
|
253
|
+
{
|
|
254
|
+
workId,
|
|
255
|
+
fnHandle: "test_handle",
|
|
256
|
+
fnArgs: {},
|
|
257
|
+
logLevel: "WARN",
|
|
258
|
+
attempt: 0,
|
|
267
259
|
},
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
260
|
+
],
|
|
261
|
+
scheduledTime: Date.now(),
|
|
262
|
+
state: {
|
|
263
|
+
kind: "failed",
|
|
264
|
+
error: "Function execution failed",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
272
268
|
|
|
273
269
|
await recoveryHandler(ctx, {
|
|
274
270
|
jobs: [
|
|
@@ -310,30 +306,26 @@ describe("recovery", () => {
|
|
|
310
306
|
// Run recovery with mocked system.get
|
|
311
307
|
await t.run(async (ctx) => {
|
|
312
308
|
// Mock the system.get to return a canceled state
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
logLevel: "WARN",
|
|
326
|
-
attempt: 0,
|
|
327
|
-
},
|
|
328
|
-
],
|
|
329
|
-
scheduledTime: Date.now(),
|
|
330
|
-
state: {
|
|
331
|
-
kind: "canceled",
|
|
309
|
+
ctx.db.system.get = patchedSystemGet(ctx.db, {
|
|
310
|
+
[scheduledId]: {
|
|
311
|
+
_id: scheduledId,
|
|
312
|
+
_creationTime: Date.now(),
|
|
313
|
+
name: "internal/worker.runActionWrapper",
|
|
314
|
+
args: [
|
|
315
|
+
{
|
|
316
|
+
workId,
|
|
317
|
+
fnHandle: "test_handle",
|
|
318
|
+
fnArgs: {},
|
|
319
|
+
logLevel: "WARN",
|
|
320
|
+
attempt: 0,
|
|
332
321
|
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
322
|
+
],
|
|
323
|
+
scheduledTime: Date.now(),
|
|
324
|
+
state: {
|
|
325
|
+
kind: "canceled",
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
});
|
|
337
329
|
|
|
338
330
|
await recoveryHandler(ctx, {
|
|
339
331
|
jobs: [
|
|
@@ -379,50 +371,45 @@ describe("recovery", () => {
|
|
|
379
371
|
// Run recovery with mocked system.get
|
|
380
372
|
await t.run(async (ctx) => {
|
|
381
373
|
// Mock the system.get to return different states for each scheduled function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
logLevel: "WARN",
|
|
395
|
-
attempt: 0,
|
|
396
|
-
},
|
|
397
|
-
],
|
|
398
|
-
scheduledTime: Date.now(),
|
|
399
|
-
state: {
|
|
400
|
-
kind: "failed",
|
|
401
|
-
error: "Function 1 failed",
|
|
374
|
+
ctx.db.system.get = patchedSystemGet(ctx.db, {
|
|
375
|
+
[scheduledId1]: {
|
|
376
|
+
_id: scheduledId1,
|
|
377
|
+
_creationTime: Date.now(),
|
|
378
|
+
name: "internal/worker.runActionWrapper",
|
|
379
|
+
args: [
|
|
380
|
+
{
|
|
381
|
+
workId: workId1,
|
|
382
|
+
fnHandle: "test_handle",
|
|
383
|
+
fnArgs: { test: 1 },
|
|
384
|
+
logLevel: "WARN",
|
|
385
|
+
attempt: 0,
|
|
402
386
|
},
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
387
|
+
],
|
|
388
|
+
scheduledTime: Date.now(),
|
|
389
|
+
state: {
|
|
390
|
+
kind: "failed",
|
|
391
|
+
error: "Function 1 failed",
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
[scheduledId2]: {
|
|
395
|
+
_id: scheduledId2,
|
|
396
|
+
_creationTime: Date.now(),
|
|
397
|
+
name: "internal/worker.runActionWrapper",
|
|
398
|
+
args: [
|
|
399
|
+
{
|
|
400
|
+
workId: workId2,
|
|
401
|
+
fnHandle: "test_handle",
|
|
402
|
+
fnArgs: { test: 2 },
|
|
403
|
+
logLevel: "WARN",
|
|
404
|
+
attempt: 0,
|
|
421
405
|
},
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
406
|
+
],
|
|
407
|
+
scheduledTime: Date.now(),
|
|
408
|
+
state: {
|
|
409
|
+
kind: "canceled",
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
});
|
|
426
413
|
|
|
427
414
|
await recoveryHandler(ctx, {
|
|
428
415
|
jobs: [
|
|
@@ -486,30 +473,26 @@ describe("recovery", () => {
|
|
|
486
473
|
// Run recovery with mocked system.get
|
|
487
474
|
await t.run(async (ctx) => {
|
|
488
475
|
// Mock the system.get to return a pending state
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
logLevel: "WARN",
|
|
502
|
-
attempt: 0,
|
|
503
|
-
},
|
|
504
|
-
],
|
|
505
|
-
scheduledTime: Date.now(),
|
|
506
|
-
state: {
|
|
507
|
-
kind: "pending",
|
|
476
|
+
ctx.db.system.get = patchedSystemGet(ctx.db, {
|
|
477
|
+
[scheduledId]: {
|
|
478
|
+
_id: scheduledId,
|
|
479
|
+
_creationTime: Date.now(),
|
|
480
|
+
name: "internal/worker.runActionWrapper",
|
|
481
|
+
args: [
|
|
482
|
+
{
|
|
483
|
+
workId,
|
|
484
|
+
fnHandle: "test_handle",
|
|
485
|
+
fnArgs: {},
|
|
486
|
+
logLevel: "WARN",
|
|
487
|
+
attempt: 0,
|
|
508
488
|
},
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
489
|
+
],
|
|
490
|
+
scheduledTime: Date.now(),
|
|
491
|
+
state: {
|
|
492
|
+
kind: "pending",
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
});
|
|
513
496
|
|
|
514
497
|
await recoveryHandler(ctx, {
|
|
515
498
|
jobs: [
|
|
@@ -534,3 +517,20 @@ describe("recovery", () => {
|
|
|
534
517
|
});
|
|
535
518
|
});
|
|
536
519
|
});
|
|
520
|
+
|
|
521
|
+
function patchedSystemGet(
|
|
522
|
+
db: GenericDatabaseReader<GenericDataModel>,
|
|
523
|
+
overrides: Record<
|
|
524
|
+
string,
|
|
525
|
+
DocumentByName<SystemDataModel, "_scheduled_functions"> | null
|
|
526
|
+
>,
|
|
527
|
+
) {
|
|
528
|
+
const originalGet = db.system.get;
|
|
529
|
+
return async (
|
|
530
|
+
tableOrId: SystemTableNames | Id<SystemTableNames>,
|
|
531
|
+
maybeId?: Id<SystemTableNames>,
|
|
532
|
+
) => {
|
|
533
|
+
const id = (maybeId ?? tableOrId) as Id<"_scheduled_functions">;
|
|
534
|
+
return id in overrides ? overrides[id] : await originalGet(id);
|
|
535
|
+
};
|
|
536
|
+
}
|
package/src/component/schema.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { defineSchema, defineTable } from "convex/server";
|
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
import {
|
|
4
4
|
fnType,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
vConfig,
|
|
6
|
+
vOnCompleteFnContext,
|
|
7
7
|
retryBehavior,
|
|
8
|
-
|
|
8
|
+
vResult,
|
|
9
9
|
} from "./shared.js";
|
|
10
10
|
|
|
11
11
|
// Represents a slice of time to process work.
|
|
@@ -13,7 +13,7 @@ const segment = v.int64();
|
|
|
13
13
|
|
|
14
14
|
export default defineSchema({
|
|
15
15
|
// Written from kickLoop, read everywhere.
|
|
16
|
-
globals: defineTable(
|
|
16
|
+
globals: defineTable(vConfig),
|
|
17
17
|
// Singleton, only read & written by `main`.
|
|
18
18
|
internalState: defineTable({
|
|
19
19
|
// Ensure that only one main is running at a time.
|
|
@@ -64,7 +64,7 @@ export default defineSchema({
|
|
|
64
64
|
fnName: v.string(),
|
|
65
65
|
fnArgs: v.any(),
|
|
66
66
|
attempts: v.number(), // number of completed attempts
|
|
67
|
-
onComplete: v.optional(
|
|
67
|
+
onComplete: v.optional(vOnCompleteFnContext),
|
|
68
68
|
retryBehavior: v.optional(retryBehavior),
|
|
69
69
|
canceled: v.optional(v.boolean()),
|
|
70
70
|
}),
|
|
@@ -80,7 +80,7 @@ export default defineSchema({
|
|
|
80
80
|
// Written by complete, read & deleted by `main`.
|
|
81
81
|
pendingCompletion: defineTable({
|
|
82
82
|
segment,
|
|
83
|
-
runResult:
|
|
83
|
+
runResult: vResult,
|
|
84
84
|
workId: v.id("work"),
|
|
85
85
|
retry: v.boolean(),
|
|
86
86
|
})
|
package/src/component/shared.ts
CHANGED
|
@@ -33,12 +33,11 @@ export function fromSegment(segment: bigint): number {
|
|
|
33
33
|
return Number(segment) * SEGMENT_MS;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export const
|
|
36
|
+
export const vConfig = v.object({
|
|
37
37
|
maxParallelism: v.number(),
|
|
38
38
|
logLevel,
|
|
39
|
-
cancelationBatchSize: v.optional(v.number()),
|
|
40
39
|
});
|
|
41
|
-
export type Config = Infer<typeof
|
|
40
|
+
export type Config = Infer<typeof vConfig>;
|
|
42
41
|
|
|
43
42
|
export const retryBehavior = v.object({
|
|
44
43
|
maxAttempts: v.number(),
|
|
@@ -71,7 +70,7 @@ export const DEFAULT_RETRY_BEHAVIOR: RetryBehavior = {
|
|
|
71
70
|
// This ensures that the type satisfies the schema.
|
|
72
71
|
const _ = {} as RetryBehavior satisfies Infer<typeof retryBehavior>;
|
|
73
72
|
|
|
74
|
-
export const
|
|
73
|
+
export const vResult = v.union(
|
|
75
74
|
v.object({
|
|
76
75
|
kind: v.literal("success"),
|
|
77
76
|
returnValue: v.any(),
|
|
@@ -84,13 +83,12 @@ export const vResultValidator = v.union(
|
|
|
84
83
|
kind: v.literal("canceled"),
|
|
85
84
|
}),
|
|
86
85
|
);
|
|
87
|
-
export type RunResult = Infer<typeof
|
|
86
|
+
export type RunResult = Infer<typeof vResult>;
|
|
88
87
|
|
|
89
|
-
export const
|
|
88
|
+
export const vOnCompleteFnContext = v.object({
|
|
90
89
|
fnHandle: v.string(), // mutation
|
|
91
90
|
context: v.optional(v.any()),
|
|
92
91
|
});
|
|
93
|
-
export type OnComplete = Infer<typeof onComplete>;
|
|
94
92
|
|
|
95
93
|
export type OnCompleteArgs = {
|
|
96
94
|
/**
|