@convex-dev/workpool 0.2.18-alpha.3 → 0.2.19-alpha.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.
@@ -1,10 +1,10 @@
1
- import { v } from "convex/values";
1
+ import { Infer, ObjectType, v } from "convex/values";
2
2
  import { api } from "./_generated/api.js";
3
3
  import { fnType } from "./shared.js";
4
4
  import { Id } from "./_generated/dataModel.js";
5
- import { mutation, MutationCtx, query } from "./_generated/server.js";
5
+ import { mutation, MutationCtx, query, QueryCtx } from "./_generated/server.js";
6
6
  import { kickMainLoop } from "./kick.js";
7
- import { createLogger, LogLevel, logLevel } from "./logging.js";
7
+ import { createLogger, Logger, LogLevel, logLevel } from "./logging.js";
8
8
  import {
9
9
  boundScheduledTime,
10
10
  config,
@@ -20,44 +20,75 @@ import { recordEnqueued } from "./stats.js";
20
20
  const MAX_POSSIBLE_PARALLELISM = 100;
21
21
  const MAX_PARALLELISM_SOFT_LIMIT = 50;
22
22
 
23
+ const itemArgs = {
24
+ fnHandle: v.string(),
25
+ fnName: v.string(),
26
+ fnArgs: v.any(),
27
+ fnType,
28
+ runAt: v.number(),
29
+ // TODO: annotation?
30
+ onComplete: v.optional(onComplete),
31
+ retryBehavior: v.optional(retryBehavior),
32
+ };
33
+ const enqueueArgs = {
34
+ ...itemArgs,
35
+ config,
36
+ };
23
37
  export const enqueue = mutation({
38
+ args: enqueueArgs,
39
+ returns: v.id("work"),
40
+ handler: async (ctx, { config, ...itemArgs }) => {
41
+ validateConfig(config);
42
+ const console = createLogger(config.logLevel);
43
+ const kickSegment = await kickMainLoop(ctx, "enqueue", config);
44
+ return await enqueueHandler(ctx, console, kickSegment, itemArgs);
45
+ },
46
+ });
47
+ async function enqueueHandler(
48
+ ctx: MutationCtx,
49
+ console: Logger,
50
+ kickSegment: bigint,
51
+ { runAt, ...workArgs }: ObjectType<typeof itemArgs>
52
+ ) {
53
+ runAt = boundScheduledTime(runAt, console);
54
+ const workId = await ctx.db.insert("work", {
55
+ ...workArgs,
56
+ attempts: 0,
57
+ });
58
+ await ctx.db.insert("pendingStart", {
59
+ workId,
60
+ segment: max(toSegment(runAt), kickSegment),
61
+ });
62
+ recordEnqueued(console, { workId, fnName: workArgs.fnName, runAt });
63
+ return workId;
64
+ }
65
+
66
+ type Config = Infer<typeof config>;
67
+ function validateConfig(config: Config) {
68
+ if (config.maxParallelism > MAX_POSSIBLE_PARALLELISM) {
69
+ throw new Error(`maxParallelism must be <= ${MAX_PARALLELISM_SOFT_LIMIT}`);
70
+ } else if (config.maxParallelism > MAX_PARALLELISM_SOFT_LIMIT) {
71
+ createLogger(config.logLevel).warn(
72
+ `maxParallelism should be <= ${MAX_PARALLELISM_SOFT_LIMIT}, but is set to ${config.maxParallelism}. This will be an error in a future version.`
73
+ );
74
+ } else if (config.maxParallelism < 1) {
75
+ throw new Error("maxParallelism must be >= 1");
76
+ }
77
+ }
78
+
79
+ export const enqueueBatch = mutation({
24
80
  args: {
25
- fnHandle: v.string(),
26
- fnName: v.string(),
27
- fnArgs: v.any(),
28
- fnType,
29
- runAt: v.number(),
30
- // TODO: annotation?
31
- onComplete: v.optional(onComplete),
32
- retryBehavior: v.optional(retryBehavior),
81
+ items: v.array(v.object(itemArgs)),
33
82
  config,
34
83
  },
35
- returns: v.id("work"),
36
- handler: async (ctx, { config, runAt, ...workArgs }) => {
84
+ returns: v.array(v.id("work")),
85
+ handler: async (ctx, { config, items }) => {
86
+ validateConfig(config);
37
87
  const console = createLogger(config.logLevel);
38
- if (config.maxParallelism > MAX_POSSIBLE_PARALLELISM) {
39
- throw new Error(
40
- `maxParallelism must be <= ${MAX_PARALLELISM_SOFT_LIMIT}`
41
- );
42
- } else if (config.maxParallelism > MAX_PARALLELISM_SOFT_LIMIT) {
43
- console.warn(
44
- `maxParallelism should be <= ${MAX_PARALLELISM_SOFT_LIMIT}, but is set to ${config.maxParallelism}. This will be an error in a future version.`
45
- );
46
- } else if (config.maxParallelism < 1) {
47
- throw new Error("maxParallelism must be >= 1");
48
- }
49
- runAt = boundScheduledTime(runAt, console);
50
- const workId = await ctx.db.insert("work", {
51
- ...workArgs,
52
- attempts: 0,
53
- });
54
- const limit = await kickMainLoop(ctx, "enqueue", config);
55
- await ctx.db.insert("pendingStart", {
56
- workId,
57
- segment: max(toSegment(runAt), limit),
58
- });
59
- recordEnqueued(console, { workId, fnName: workArgs.fnName, runAt });
60
- return workId;
88
+ const kickSegment = await kickMainLoop(ctx, "enqueue", config);
89
+ return Promise.all(
90
+ items.map((item) => enqueueHandler(ctx, console, kickSegment, item))
91
+ );
61
92
  },
62
93
  });
63
94
 
@@ -80,14 +111,19 @@ export const cancel = mutation({
80
111
 
81
112
  const PAGE_SIZE = 64;
82
113
  export const cancelAll = mutation({
83
- args: { logLevel, before: v.optional(v.number()) },
84
- handler: async (ctx, { logLevel, before }) => {
114
+ args: {
115
+ logLevel,
116
+ before: v.optional(v.number()),
117
+ limit: v.optional(v.number()),
118
+ },
119
+ handler: async (ctx, { logLevel, before, limit }) => {
85
120
  const beforeTime = before ?? Date.now();
121
+ const pageSize = limit ?? PAGE_SIZE;
86
122
  const pageOfWork = await ctx.db
87
123
  .query("work")
88
124
  .withIndex("by_creation_time", (q) => q.lte("_creationTime", beforeTime))
89
125
  .order("desc")
90
- .take(PAGE_SIZE);
126
+ .take(pageSize);
91
127
  const shouldCancel = await Promise.all(
92
128
  pageOfWork.map(async ({ _id }) =>
93
129
  shouldCancelWorkItem(ctx, _id, logLevel)
@@ -107,7 +143,7 @@ export const cancelAll = mutation({
107
143
  }
108
144
  })
109
145
  );
110
- if (pageOfWork.length === PAGE_SIZE) {
146
+ if (pageOfWork.length === pageSize) {
111
147
  await ctx.scheduler.runAfter(0, api.lib.cancelAll, {
112
148
  logLevel,
113
149
  before: pageOfWork[pageOfWork.length - 1]._creationTime,
@@ -119,27 +155,38 @@ export const cancelAll = mutation({
119
155
  export const status = query({
120
156
  args: { id: v.id("work") },
121
157
  returns: statusValidator,
122
- handler: async (ctx, { id }) => {
123
- const work = await ctx.db.get(id);
124
- if (!work) {
125
- return { state: "finished" } as const;
126
- }
127
- const pendingStart = await ctx.db
128
- .query("pendingStart")
129
- .withIndex("workId", (q) => q.eq("workId", id))
130
- .unique();
131
- if (pendingStart) {
132
- return { state: "pending", previousAttempts: work.attempts } as const;
133
- }
134
- const pendingCompletion = await ctx.db
135
- .query("pendingCompletion")
136
- .withIndex("workId", (q) => q.eq("workId", id))
137
- .unique();
138
- if (pendingCompletion?.retry) {
139
- return { state: "pending", previousAttempts: work.attempts } as const;
140
- }
141
- // Assume it's in progress. It could be pending cancelation
142
- return { state: "running", previousAttempts: work.attempts } as const;
158
+ handler: statusHandler,
159
+ });
160
+ async function statusHandler(ctx: QueryCtx, { id }: { id: Id<"work"> }) {
161
+ const work = await ctx.db.get(id);
162
+ if (!work) {
163
+ return { state: "finished" } as const;
164
+ }
165
+ const pendingStart = await ctx.db
166
+ .query("pendingStart")
167
+ .withIndex("workId", (q) => q.eq("workId", id))
168
+ .unique();
169
+ if (pendingStart) {
170
+ return { state: "pending", previousAttempts: work.attempts } as const;
171
+ }
172
+ const pendingCompletion = await ctx.db
173
+ .query("pendingCompletion")
174
+ .withIndex("workId", (q) => q.eq("workId", id))
175
+ .unique();
176
+ if (pendingCompletion?.retry) {
177
+ return { state: "pending", previousAttempts: work.attempts } as const;
178
+ }
179
+ // Assume it's in progress. It could be pending cancelation
180
+ return { state: "running", previousAttempts: work.attempts } as const;
181
+ }
182
+
183
+ export const statusBatch = query({
184
+ args: { ids: v.array(v.id("work")) },
185
+ returns: v.array(statusValidator),
186
+ handler: async (ctx, { ids }) => {
187
+ return await Promise.all(
188
+ ids.map(async (id) => await statusHandler(ctx, { id }))
189
+ );
143
190
  },
144
191
  });
145
192