@convex-dev/workpool 0.2.0-beta.0 → 0.2.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 +87 -18
- package/dist/commonjs/client/index.d.ts +33 -8
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +37 -7
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/component/complete.d.ts +89 -0
- package/dist/commonjs/component/complete.d.ts.map +1 -0
- package/dist/commonjs/component/complete.js +82 -0
- package/dist/commonjs/component/complete.js.map +1 -0
- package/dist/commonjs/component/kick.d.ts +3 -3
- package/dist/commonjs/component/kick.d.ts.map +1 -1
- package/dist/commonjs/component/kick.js +17 -12
- package/dist/commonjs/component/kick.js.map +1 -1
- package/dist/commonjs/component/lib.d.ts +6 -6
- package/dist/commonjs/component/lib.d.ts.map +1 -1
- package/dist/commonjs/component/lib.js +53 -24
- package/dist/commonjs/component/lib.js.map +1 -1
- package/dist/commonjs/component/logging.d.ts +3 -2
- package/dist/commonjs/component/logging.d.ts.map +1 -1
- package/dist/commonjs/component/logging.js +34 -16
- package/dist/commonjs/component/logging.js.map +1 -1
- package/dist/commonjs/component/loop.d.ts +1 -14
- package/dist/commonjs/component/loop.d.ts.map +1 -1
- package/dist/commonjs/component/loop.js +216 -179
- package/dist/commonjs/component/loop.js.map +1 -1
- package/dist/commonjs/component/recovery.d.ts +45 -0
- package/dist/commonjs/component/recovery.d.ts.map +1 -1
- package/dist/commonjs/component/recovery.js +88 -65
- package/dist/commonjs/component/recovery.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +17 -13
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +5 -3
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/shared.d.ts +24 -15
- package/dist/commonjs/component/shared.d.ts.map +1 -1
- package/dist/commonjs/component/shared.js +20 -7
- package/dist/commonjs/component/shared.js.map +1 -1
- package/dist/commonjs/component/stats.d.ts +36 -29
- package/dist/commonjs/component/stats.d.ts.map +1 -1
- package/dist/commonjs/component/stats.js +110 -52
- package/dist/commonjs/component/stats.js.map +1 -1
- package/dist/commonjs/component/worker.d.ts +4 -14
- package/dist/commonjs/component/worker.d.ts.map +1 -1
- package/dist/commonjs/component/worker.js +23 -36
- package/dist/commonjs/component/worker.js.map +1 -1
- package/dist/esm/client/index.d.ts +33 -8
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +37 -7
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/component/complete.d.ts +89 -0
- package/dist/esm/component/complete.d.ts.map +1 -0
- package/dist/esm/component/complete.js +82 -0
- package/dist/esm/component/complete.js.map +1 -0
- package/dist/esm/component/kick.d.ts +3 -3
- package/dist/esm/component/kick.d.ts.map +1 -1
- package/dist/esm/component/kick.js +17 -12
- package/dist/esm/component/kick.js.map +1 -1
- package/dist/esm/component/lib.d.ts +6 -6
- package/dist/esm/component/lib.d.ts.map +1 -1
- package/dist/esm/component/lib.js +53 -24
- package/dist/esm/component/lib.js.map +1 -1
- package/dist/esm/component/logging.d.ts +3 -2
- package/dist/esm/component/logging.d.ts.map +1 -1
- package/dist/esm/component/logging.js +34 -16
- package/dist/esm/component/logging.js.map +1 -1
- package/dist/esm/component/loop.d.ts +1 -14
- package/dist/esm/component/loop.d.ts.map +1 -1
- package/dist/esm/component/loop.js +216 -179
- package/dist/esm/component/loop.js.map +1 -1
- package/dist/esm/component/recovery.d.ts +45 -0
- package/dist/esm/component/recovery.d.ts.map +1 -1
- package/dist/esm/component/recovery.js +88 -65
- package/dist/esm/component/recovery.js.map +1 -1
- package/dist/esm/component/schema.d.ts +17 -13
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +5 -3
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/shared.d.ts +24 -15
- package/dist/esm/component/shared.d.ts.map +1 -1
- package/dist/esm/component/shared.js +20 -7
- package/dist/esm/component/shared.js.map +1 -1
- package/dist/esm/component/stats.d.ts +36 -29
- package/dist/esm/component/stats.d.ts.map +1 -1
- package/dist/esm/component/stats.js +110 -52
- package/dist/esm/component/stats.js.map +1 -1
- package/dist/esm/component/worker.d.ts +4 -14
- package/dist/esm/component/worker.d.ts.map +1 -1
- package/dist/esm/component/worker.js +23 -36
- package/dist/esm/component/worker.js.map +1 -1
- package/package.json +12 -12
- package/src/client/index.ts +82 -43
- package/src/component/README.md +15 -15
- package/src/component/_generated/api.d.ts +10 -5
- package/src/component/complete.test.ts +508 -0
- package/src/component/complete.ts +109 -0
- package/src/component/kick.test.ts +29 -19
- package/src/component/kick.ts +25 -17
- package/src/component/lib.test.ts +262 -17
- package/src/component/lib.ts +68 -30
- package/src/component/logging.test.ts +16 -0
- package/src/component/logging.ts +45 -24
- package/src/component/loop.test.ts +1158 -0
- package/src/component/loop.ts +292 -224
- package/src/component/recovery.test.ts +536 -0
- package/src/component/recovery.ts +100 -75
- package/src/component/schema.ts +6 -4
- package/src/component/shared.ts +23 -8
- package/src/component/stats.test.ts +345 -0
- package/src/component/stats.ts +149 -56
- package/src/component/worker.ts +25 -38
package/src/component/schema.ts
CHANGED
|
@@ -51,18 +51,19 @@ export default defineSchema({
|
|
|
51
51
|
),
|
|
52
52
|
}),
|
|
53
53
|
|
|
54
|
-
// Written on enqueue.
|
|
54
|
+
// Written on enqueue. Deleted by `complete` for success, failure, canceled.
|
|
55
55
|
work: defineTable({
|
|
56
56
|
fnType: v.union(v.literal("action"), v.literal("mutation")),
|
|
57
57
|
fnHandle: v.string(),
|
|
58
58
|
fnName: v.string(),
|
|
59
59
|
fnArgs: v.any(),
|
|
60
|
-
attempts: v.number(),
|
|
60
|
+
attempts: v.number(), // number of completed attempts
|
|
61
61
|
onComplete: v.optional(onComplete),
|
|
62
62
|
retryBehavior: v.optional(retryBehavior),
|
|
63
|
+
canceled: v.optional(v.boolean()),
|
|
63
64
|
}),
|
|
64
65
|
|
|
65
|
-
// Written on enqueue, read & deleted by `main`.
|
|
66
|
+
// Written on enqueue & rescheduled for retry, read & deleted by `main`.
|
|
66
67
|
pendingStart: defineTable({
|
|
67
68
|
workId: v.id("work"),
|
|
68
69
|
segment,
|
|
@@ -70,11 +71,12 @@ export default defineSchema({
|
|
|
70
71
|
.index("workId", ["workId"])
|
|
71
72
|
.index("segment", ["segment"]),
|
|
72
73
|
|
|
73
|
-
// Written by
|
|
74
|
+
// Written by complete, read & deleted by `main`.
|
|
74
75
|
pendingCompletion: defineTable({
|
|
75
76
|
segment,
|
|
76
77
|
runResult,
|
|
77
78
|
workId: v.id("work"),
|
|
79
|
+
retry: v.boolean(),
|
|
78
80
|
})
|
|
79
81
|
.index("workId", ["workId"])
|
|
80
82
|
.index("segment", ["segment"]),
|
package/src/component/shared.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { Infer } from "convex/values";
|
|
|
3
3
|
import { v } from "convex/values";
|
|
4
4
|
import { Logger, logLevel } from "./logging.js";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
export const DEFAULT_MAX_PARALLELISM = 10;
|
|
7
|
+
const SEGMENT_MS = 100;
|
|
7
8
|
export const SECOND = 1000;
|
|
8
9
|
export const MINUTE = 60 * SECOND;
|
|
9
10
|
export const HOUR = 60 * MINUTE;
|
|
@@ -14,11 +15,11 @@ export function toSegment(ms: number): bigint {
|
|
|
14
15
|
return BigInt(Math.floor(ms / SEGMENT_MS));
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export function
|
|
18
|
+
export function getCurrentSegment(): bigint {
|
|
18
19
|
return toSegment(Date.now());
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function
|
|
22
|
+
export function getNextSegment(): bigint {
|
|
22
23
|
return toSegment(Date.now()) + 1n;
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -98,11 +99,11 @@ export const status = v.union(
|
|
|
98
99
|
v.union(
|
|
99
100
|
v.object({
|
|
100
101
|
state: v.literal("pending"),
|
|
101
|
-
|
|
102
|
+
previousAttempts: v.number(),
|
|
102
103
|
}),
|
|
103
104
|
v.object({
|
|
104
105
|
state: v.literal("running"),
|
|
105
|
-
|
|
106
|
+
previousAttempts: v.number(),
|
|
106
107
|
}),
|
|
107
108
|
v.object({
|
|
108
109
|
state: v.literal("finished"),
|
|
@@ -113,15 +114,29 @@ export type Status = Infer<typeof status>;
|
|
|
113
114
|
|
|
114
115
|
export function boundScheduledTime(ms: number, console: Logger): number {
|
|
115
116
|
if (ms < Date.now() - YEAR) {
|
|
116
|
-
console.
|
|
117
|
+
console.error("scheduled time is too old, defaulting to now", ms);
|
|
117
118
|
return Date.now();
|
|
118
119
|
}
|
|
119
120
|
if (ms > Date.now() + 4 * YEAR) {
|
|
120
|
-
console.
|
|
121
|
-
"
|
|
121
|
+
console.error(
|
|
122
|
+
"scheduled time is too far in the future, defaulting to 1 year from now",
|
|
122
123
|
ms
|
|
123
124
|
);
|
|
124
125
|
return Date.now() + YEAR;
|
|
125
126
|
}
|
|
126
127
|
return ms;
|
|
127
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the smaller of two bigint values.
|
|
132
|
+
*/
|
|
133
|
+
export function min<T extends bigint>(a: T, b: T): T {
|
|
134
|
+
return a > b ? b : a;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns the larger of two bigint values.
|
|
139
|
+
*/
|
|
140
|
+
export function max<T extends bigint>(a: T, b: T): T {
|
|
141
|
+
return a < b ? b : a;
|
|
142
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { convexTest } from "convex-test";
|
|
2
|
+
import {
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
it,
|
|
6
|
+
beforeEach,
|
|
7
|
+
afterEach,
|
|
8
|
+
vi,
|
|
9
|
+
assert,
|
|
10
|
+
} from "vitest";
|
|
11
|
+
import schema from "./schema";
|
|
12
|
+
import { internal } from "./_generated/api";
|
|
13
|
+
import { Logger } from "./logging";
|
|
14
|
+
import { getCurrentSegment } from "./shared";
|
|
15
|
+
import { paginator } from "convex-helpers/server/pagination";
|
|
16
|
+
|
|
17
|
+
const modules = import.meta.glob("./**/*.ts");
|
|
18
|
+
|
|
19
|
+
// Create a proper Logger mock
|
|
20
|
+
function createLoggerMock(): Logger {
|
|
21
|
+
return {
|
|
22
|
+
event: vi.fn(),
|
|
23
|
+
debug: vi.fn(),
|
|
24
|
+
info: vi.fn(),
|
|
25
|
+
warn: vi.fn(),
|
|
26
|
+
error: vi.fn(),
|
|
27
|
+
time: vi.fn(),
|
|
28
|
+
timeEnd: vi.fn(),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("stats", () => {
|
|
33
|
+
async function setupTest() {
|
|
34
|
+
const t = convexTest(schema, modules);
|
|
35
|
+
return t;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let t: Awaited<ReturnType<typeof setupTest>>;
|
|
39
|
+
|
|
40
|
+
beforeEach(async () => {
|
|
41
|
+
vi.useFakeTimers();
|
|
42
|
+
t = await setupTest();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("generateReport", () => {
|
|
50
|
+
it("should not generate a report when log level is above REPORT", async () => {
|
|
51
|
+
// Setup internal state
|
|
52
|
+
const stateId = await t.run(async (ctx) => {
|
|
53
|
+
return await ctx.db.insert("internalState", {
|
|
54
|
+
generation: 1n,
|
|
55
|
+
segmentCursors: {
|
|
56
|
+
incoming: 0n,
|
|
57
|
+
completion: 0n,
|
|
58
|
+
cancelation: 0n,
|
|
59
|
+
},
|
|
60
|
+
lastRecovery: 0n,
|
|
61
|
+
report: {
|
|
62
|
+
completed: 0,
|
|
63
|
+
succeeded: 0,
|
|
64
|
+
failed: 0,
|
|
65
|
+
retries: 0,
|
|
66
|
+
canceled: 0,
|
|
67
|
+
lastReportTs: 0,
|
|
68
|
+
},
|
|
69
|
+
running: [],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Mock the console.event function to track if it's called
|
|
74
|
+
const consoleMock = createLoggerMock();
|
|
75
|
+
|
|
76
|
+
// Get the state document
|
|
77
|
+
const state = await t.run(async (ctx) => {
|
|
78
|
+
return await ctx.db.get(stateId);
|
|
79
|
+
});
|
|
80
|
+
assert(state);
|
|
81
|
+
|
|
82
|
+
// Call generateReport with a log level that won't trigger reporting
|
|
83
|
+
await t.run(async (ctx) => {
|
|
84
|
+
const { generateReport } = await import("./stats");
|
|
85
|
+
await generateReport(ctx, consoleMock, state, {
|
|
86
|
+
maxParallelism: 10,
|
|
87
|
+
logLevel: "WARN", // Above REPORT level
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Verify that console.event was not called
|
|
92
|
+
expect(consoleMock.event).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should generate a report when backlog is small enough", async () => {
|
|
96
|
+
// Setup internal state
|
|
97
|
+
const stateId = await t.run(async (ctx) => {
|
|
98
|
+
return await ctx.db.insert("internalState", {
|
|
99
|
+
generation: 1n,
|
|
100
|
+
segmentCursors: {
|
|
101
|
+
incoming: 0n,
|
|
102
|
+
completion: 0n,
|
|
103
|
+
cancelation: 0n,
|
|
104
|
+
},
|
|
105
|
+
lastRecovery: 0n,
|
|
106
|
+
report: {
|
|
107
|
+
completed: 10,
|
|
108
|
+
succeeded: 6,
|
|
109
|
+
failed: 2,
|
|
110
|
+
retries: 2,
|
|
111
|
+
canceled: 0,
|
|
112
|
+
lastReportTs: 0,
|
|
113
|
+
},
|
|
114
|
+
running: [],
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Create a few pending start items
|
|
119
|
+
await t.run(async (ctx) => {
|
|
120
|
+
// Create a work item
|
|
121
|
+
const workId = await ctx.db.insert("work", {
|
|
122
|
+
fnType: "mutation",
|
|
123
|
+
fnHandle: "testHandle",
|
|
124
|
+
fnName: "testFunction",
|
|
125
|
+
fnArgs: { test: true },
|
|
126
|
+
attempts: 0,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Create a pendingStart for the work
|
|
130
|
+
await ctx.db.insert("pendingStart", {
|
|
131
|
+
workId,
|
|
132
|
+
segment: 5n, // Some segment between 0 and currentSegment
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Mock the console.event function to track if it's called
|
|
137
|
+
const consoleMock = createLoggerMock();
|
|
138
|
+
|
|
139
|
+
// Get the state document
|
|
140
|
+
const state = await t.run(async (ctx) => {
|
|
141
|
+
return await ctx.db.get(stateId);
|
|
142
|
+
});
|
|
143
|
+
assert(state);
|
|
144
|
+
|
|
145
|
+
// Call generateReport with REPORT log level
|
|
146
|
+
await t.run(async (ctx) => {
|
|
147
|
+
const { generateReport } = await import("./stats");
|
|
148
|
+
await generateReport(ctx, consoleMock, state, {
|
|
149
|
+
maxParallelism: 10,
|
|
150
|
+
logLevel: "REPORT", // This should trigger reporting
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Verify that console.event was called with the correct data
|
|
155
|
+
expect(consoleMock.event).toHaveBeenCalledWith("report", {
|
|
156
|
+
backlog: 1, // We created one pendingStart
|
|
157
|
+
running: 0,
|
|
158
|
+
completed: 10,
|
|
159
|
+
succeeded: 6,
|
|
160
|
+
failed: 2,
|
|
161
|
+
retries: 2,
|
|
162
|
+
canceled: 0,
|
|
163
|
+
failureRate: 0.4, // (failed + retries) / completed = (2 + 2) / 10 = 0.4
|
|
164
|
+
permanentFailureRate: 0.25, // failed / (completed - retries) = 2 / (10 - 2) = 2/8
|
|
165
|
+
lastReportTs: expect.any(Number),
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should schedule calculateBacklogAndReport when backlog is large", async () => {
|
|
170
|
+
// Setup internal state
|
|
171
|
+
const stateId = await t.run(async (ctx) => {
|
|
172
|
+
return await ctx.db.insert("internalState", {
|
|
173
|
+
generation: 1n,
|
|
174
|
+
segmentCursors: {
|
|
175
|
+
incoming: 0n,
|
|
176
|
+
completion: 0n,
|
|
177
|
+
cancelation: 0n,
|
|
178
|
+
},
|
|
179
|
+
lastRecovery: 0n,
|
|
180
|
+
report: {
|
|
181
|
+
completed: 10,
|
|
182
|
+
succeeded: 8,
|
|
183
|
+
failed: 1,
|
|
184
|
+
retries: 1,
|
|
185
|
+
canceled: 0,
|
|
186
|
+
lastReportTs: 0,
|
|
187
|
+
},
|
|
188
|
+
running: [],
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Create more pending start items than maxParallelism
|
|
193
|
+
const maxParallelism = 5;
|
|
194
|
+
|
|
195
|
+
// Create maxParallelism + 1 work items to trigger pagination
|
|
196
|
+
for (let i = 0; i < maxParallelism + 1; i++) {
|
|
197
|
+
await t.run(async (ctx) => {
|
|
198
|
+
// Create a work item
|
|
199
|
+
const workId = await ctx.db.insert("work", {
|
|
200
|
+
fnType: "mutation",
|
|
201
|
+
fnHandle: "testHandle",
|
|
202
|
+
fnName: `testFunction${i}`,
|
|
203
|
+
fnArgs: { test: i },
|
|
204
|
+
attempts: 0,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Create a pendingStart for the work
|
|
208
|
+
await ctx.db.insert("pendingStart", {
|
|
209
|
+
workId,
|
|
210
|
+
segment: 5n, // Some segment between 0 and currentSegment
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Mock the console.event function
|
|
216
|
+
const consoleMock = createLoggerMock();
|
|
217
|
+
|
|
218
|
+
// Get the state document
|
|
219
|
+
const state = await t.run(async (ctx) => {
|
|
220
|
+
return await ctx.db.get(stateId);
|
|
221
|
+
});
|
|
222
|
+
assert(state);
|
|
223
|
+
|
|
224
|
+
// Call generateReport with REPORT log level
|
|
225
|
+
await t.run(async (ctx) => {
|
|
226
|
+
const { generateReport } = await import("./stats");
|
|
227
|
+
await generateReport(ctx, consoleMock, state, {
|
|
228
|
+
maxParallelism,
|
|
229
|
+
logLevel: "REPORT", // This should trigger reporting
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Verify that calculateBacklogAndReport was scheduled
|
|
234
|
+
await t.run(async (ctx) => {
|
|
235
|
+
const scheduledFunctions = await ctx.db.system
|
|
236
|
+
.query("_scheduled_functions")
|
|
237
|
+
.collect();
|
|
238
|
+
|
|
239
|
+
expect(scheduledFunctions.length).toBeGreaterThan(0);
|
|
240
|
+
|
|
241
|
+
// Check that one of the scheduled functions is calculateBacklogAndReport
|
|
242
|
+
const calculateBacklogScheduled = scheduledFunctions.find(
|
|
243
|
+
(sf) => sf.name === "stats:calculateBacklogAndReport"
|
|
244
|
+
);
|
|
245
|
+
expect(calculateBacklogScheduled).toBeDefined();
|
|
246
|
+
assert(calculateBacklogScheduled);
|
|
247
|
+
|
|
248
|
+
// Verify console.event was not called yet (will be called by calculateBacklogAndReport)
|
|
249
|
+
expect(consoleMock.event).not.toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should calculate backlog and report correctly", async () => {
|
|
254
|
+
// Setup internal state
|
|
255
|
+
const stateId = await t.run(async (ctx) => {
|
|
256
|
+
return await ctx.db.insert("internalState", {
|
|
257
|
+
generation: 1n,
|
|
258
|
+
segmentCursors: {
|
|
259
|
+
incoming: 0n,
|
|
260
|
+
completion: 0n,
|
|
261
|
+
cancelation: 0n,
|
|
262
|
+
},
|
|
263
|
+
lastRecovery: 0n,
|
|
264
|
+
report: {
|
|
265
|
+
completed: 10,
|
|
266
|
+
succeeded: 8,
|
|
267
|
+
failed: 1,
|
|
268
|
+
retries: 1,
|
|
269
|
+
canceled: 0,
|
|
270
|
+
lastReportTs: 0,
|
|
271
|
+
},
|
|
272
|
+
running: [],
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Create some pending start items
|
|
277
|
+
const currentSegment = getCurrentSegment();
|
|
278
|
+
|
|
279
|
+
// Create 3 work items
|
|
280
|
+
for (let i = 0; i < 3; i++) {
|
|
281
|
+
await t.run(async (ctx) => {
|
|
282
|
+
// Create a work item
|
|
283
|
+
const workId = await ctx.db.insert("work", {
|
|
284
|
+
fnType: "mutation",
|
|
285
|
+
fnHandle: "testHandle",
|
|
286
|
+
fnName: `testFunction${i}`,
|
|
287
|
+
fnArgs: { test: i },
|
|
288
|
+
attempts: 0,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Create a pendingStart for the work
|
|
292
|
+
await ctx.db.insert("pendingStart", {
|
|
293
|
+
workId,
|
|
294
|
+
segment: 5n, // Some segment between 0 and currentSegment
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Get the state document
|
|
300
|
+
const state = await t.run(async (ctx) => {
|
|
301
|
+
return await ctx.db.get(stateId);
|
|
302
|
+
});
|
|
303
|
+
assert(state);
|
|
304
|
+
|
|
305
|
+
const cursor = await t.run(async (ctx) => {
|
|
306
|
+
return await paginator(ctx.db, schema)
|
|
307
|
+
.query("pendingStart")
|
|
308
|
+
.withIndex("segment", (q) =>
|
|
309
|
+
q.gte("segment", 0n).lt("segment", currentSegment)
|
|
310
|
+
)
|
|
311
|
+
.paginate({
|
|
312
|
+
numItems: 1,
|
|
313
|
+
cursor: null,
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Call calculateBacklogAndReport directly
|
|
318
|
+
await t.mutation(internal.stats.calculateBacklogAndReport, {
|
|
319
|
+
startSegment: 0n,
|
|
320
|
+
endSegment: currentSegment,
|
|
321
|
+
cursor: cursor.continueCursor,
|
|
322
|
+
report: state.report,
|
|
323
|
+
running: state.running.length,
|
|
324
|
+
logLevel: "REPORT",
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Verify that console.event was called with the correct data
|
|
328
|
+
// Note: We can't directly check the mock since it's created inside the mutation
|
|
329
|
+
// Instead, we can check if the function completed successfully
|
|
330
|
+
|
|
331
|
+
// We can verify the function was executed by checking if any scheduled functions were created
|
|
332
|
+
await t.run(async (ctx) => {
|
|
333
|
+
const scheduledFunctions = await ctx.db.system
|
|
334
|
+
.query("_scheduled_functions")
|
|
335
|
+
.collect();
|
|
336
|
+
|
|
337
|
+
// Since our backlog is small, no additional scheduled functions should be created
|
|
338
|
+
const calculateBacklogScheduled = scheduledFunctions.find(
|
|
339
|
+
(sf) => sf.name === "stats:calculateBacklogAndReport"
|
|
340
|
+
);
|
|
341
|
+
expect(calculateBacklogScheduled).toBeUndefined();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|