@convex-dev/workpool 0.2.14 → 0.2.15

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.
@@ -5,29 +5,45 @@ import {
5
5
  FunctionReference,
6
6
  FunctionType,
7
7
  FunctionVisibility,
8
+ GenericDataModel,
9
+ GenericMutationCtx,
8
10
  getFunctionName,
11
+ internalMutationGeneric,
12
+ RegisteredMutation,
9
13
  } from "convex/server";
10
- import { v, VString } from "convex/values";
14
+ import { Infer, v, Validator, VAny, VString } from "convex/values";
11
15
  import { Mounts } from "../component/_generated/api.js";
12
16
  import { DEFAULT_LOG_LEVEL, type LogLevel } from "../component/logging.js";
13
17
  import {
14
18
  Config,
15
19
  DEFAULT_MAX_PARALLELISM,
16
20
  OnComplete,
17
- runResult as resultValidator,
21
+ vResultValidator,
18
22
  type RetryBehavior,
19
23
  RunResult,
20
24
  OnCompleteArgs as SharedOnCompleteArgs,
21
25
  Status,
22
26
  } from "../component/shared.js";
23
27
  import { RunMutationCtx, RunQueryCtx, UseApi } from "./utils.js";
24
- export { resultValidator, type RunResult, type RetryBehavior, type OnComplete };
28
+ export {
29
+ vResultValidator,
30
+ type RunResult,
31
+ type RetryBehavior,
32
+ type OnComplete,
33
+ };
25
34
  export {
26
35
  retryBehavior as vRetryBehavior,
27
36
  onComplete as vOnComplete,
28
37
  } from "../component/shared.js";
29
38
  export { logLevel as vLogLevel, type LogLevel } from "../component/logging.js";
30
- export { resultValidator as vResultValidator };
39
+ export type WorkId = string & { __isWorkId: true };
40
+ export const vWorkIdValidator = v.string() as VString<WorkId>;
41
+ export {
42
+ /** @deprecated Use `vWorkIdValidator` instead. */
43
+ vWorkIdValidator as workIdValidator,
44
+ /** @deprecated Use `vResultValidator` instead. */
45
+ vResultValidator as resultValidator,
46
+ };
31
47
 
32
48
  // Attempts will run with delay [0, 250, 500, 1000, 2000] (ms)
33
49
  export const DEFAULT_RETRY_BEHAVIOR: RetryBehavior = {
@@ -35,56 +51,6 @@ export const DEFAULT_RETRY_BEHAVIOR: RetryBehavior = {
35
51
  initialBackoffMs: 250,
36
52
  base: 2,
37
53
  };
38
- export type WorkId = string & { __isWorkId: true };
39
- export const workIdValidator = v.string() as VString<WorkId>;
40
- export { workIdValidator as vWorkIdValidator };
41
-
42
- export type NameOption = {
43
- /**
44
- * The name of the function. By default, if you pass in api.foo.bar.baz,
45
- * it will use "foo/bar:baz" as the name. If you pass in a function handle,
46
- * it will use the function handle directly.
47
- */
48
- name?: string;
49
- };
50
-
51
- export type RetryOption = {
52
- /** Whether to retry the action if it fails.
53
- * If true, it will use the default retry behavior.
54
- * If custom behavior is provided, it will retry using that behavior.
55
- * If unset, it will use the Workpool's configured default.
56
- */
57
- retry?: boolean | RetryBehavior;
58
- };
59
-
60
- export type WorkpoolOptions = {
61
- /** How many actions/mutations can be running at once within this pool.
62
- * Min 1, Max 300.
63
- */
64
- maxParallelism?: number;
65
- /** How much to log. This is updated on each call to `enqueue*`,
66
- * `status`, or `cancel*`.
67
- * Default is REPORT, which logs warnings, errors, and a periodic report.
68
- * With INFO, you can also see events for started and completed work.
69
- * Stats generated can be parsed by tools like
70
- * [Axiom](https://axiom.co) for monitoring.
71
- * With DEBUG, you can see timers and internal events for work being
72
- * scheduled.
73
- */
74
- logLevel?: LogLevel;
75
- } & WorkpoolRetryOptions;
76
-
77
- export type WorkpoolRetryOptions = {
78
- /** Default retry behavior for enqueued actions.
79
- * See {@link RetryBehavior}.
80
- */
81
- defaultRetryBehavior?: RetryBehavior;
82
- /** Whether to retry actions that fail by default. Default: false.
83
- * NOTE: Only enable this if your actions are idempotent.
84
- * See the docs (README.md) for more details.
85
- */
86
- retryActionsByDefault?: boolean;
87
- };
88
54
 
89
55
  export class Workpool {
90
56
  /**
@@ -233,8 +199,122 @@ export class Workpool {
233
199
  async status(ctx: RunQueryCtx, id: WorkId): Promise<Status> {
234
200
  return ctx.runQuery(this.component.lib.status, { id });
235
201
  }
202
+
203
+ /**
204
+ * Defines a mutation that will be run after a work item completes.
205
+ * You can pass this to a call to enqueue* like so:
206
+ * ```ts
207
+ * export const myOnComplete = workpool.defineOnComplete({
208
+ * context: v.literal("myContextValue"), // optional
209
+ * handler: async (ctx, {workId, context, result}) => {
210
+ * // ... do something with the result
211
+ * },
212
+ * });
213
+ *
214
+ * // in some other function:
215
+ * const workId = await workpool.enqueueAction(ctx, internal.foo.bar, {
216
+ * // ... args to action
217
+ * }, {
218
+ * onComplete: internal.foo.myOnComplete,
219
+ * });
220
+ * ```
221
+ */
222
+ defineOnComplete<
223
+ DataModel extends GenericDataModel,
224
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
+ V extends Validator<any, "required", any> = VAny,
226
+ >({
227
+ context,
228
+ handler,
229
+ }: {
230
+ context?: V;
231
+ handler: (
232
+ ctx: GenericMutationCtx<DataModel>,
233
+ args: {
234
+ workId: WorkId;
235
+ context: Infer<V>;
236
+ result: RunResult;
237
+ }
238
+ ) => Promise<void>;
239
+ }): RegisteredMutation<"internal", OnCompleteArgs, null> {
240
+ return internalMutationGeneric({
241
+ args: vOnCompleteValidator(context),
242
+ handler,
243
+ });
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Returns a validator to use for the onComplete mutation.
249
+ * To be used like:
250
+ * ```ts
251
+ * export const myOnComplete = internalMutation({
252
+ * args: vOnCompleteValidator(v.string()),
253
+ * handler: async (ctx, {workId, context, result}) => {
254
+ * // context has been validated as a string
255
+ * // ... do something with the result
256
+ * },
257
+ * });
258
+ * @param context - The context validator. If not provided, it will be `v.any()`.
259
+ * @returns The validator for the onComplete mutation.
260
+ */
261
+ export function vOnCompleteValidator<
262
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
263
+ V extends Validator<any, "required", any> = VAny,
264
+ >(context?: V) {
265
+ return v.object({
266
+ workId: vWorkIdValidator,
267
+ context: context ?? v.any(),
268
+ result: vResultValidator,
269
+ });
236
270
  }
237
271
 
272
+ export type NameOption = {
273
+ /**
274
+ * The name of the function. By default, if you pass in api.foo.bar.baz,
275
+ * it will use "foo/bar:baz" as the name. If you pass in a function handle,
276
+ * it will use the function handle directly.
277
+ */
278
+ name?: string;
279
+ };
280
+
281
+ export type RetryOption = {
282
+ /** Whether to retry the action if it fails.
283
+ * If true, it will use the default retry behavior.
284
+ * If custom behavior is provided, it will retry using that behavior.
285
+ * If unset, it will use the Workpool's configured default.
286
+ */
287
+ retry?: boolean | RetryBehavior;
288
+ };
289
+
290
+ export type WorkpoolOptions = {
291
+ /** How many actions/mutations can be running at once within this pool.
292
+ * Min 1, Max 300.
293
+ */
294
+ maxParallelism?: number;
295
+ /** How much to log. This is updated on each call to `enqueue*`,
296
+ * `status`, or `cancel*`.
297
+ * Default is REPORT, which logs warnings, errors, and a periodic report.
298
+ * With INFO, you can also see events for started and completed work.
299
+ * Stats generated can be parsed by tools like
300
+ * [Axiom](https://axiom.co) for monitoring.
301
+ * With DEBUG, you can see timers and internal events for work being
302
+ * scheduled.
303
+ */
304
+ logLevel?: LogLevel;
305
+ } & WorkpoolRetryOptions;
306
+
307
+ export type WorkpoolRetryOptions = {
308
+ /** Default retry behavior for enqueued actions.
309
+ * See {@link RetryBehavior}.
310
+ */
311
+ defaultRetryBehavior?: RetryBehavior;
312
+ /** Whether to retry actions that fail by default. Default: false.
313
+ * NOTE: Only enable this if your actions are idempotent.
314
+ * See the docs (README.md) for more details.
315
+ */
316
+ retryActionsByDefault?: boolean;
317
+ };
238
318
  export type SchedulerOptions =
239
319
  | {
240
320
  /**
@@ -259,12 +339,18 @@ export type CallbackOptions = {
259
339
  * The context type is for your use, feel free to provide a validator for it.
260
340
  * e.g.
261
341
  * ```ts
342
+ * export const completion = workpool.defineOnComplete({
343
+ * context: v.string(),
344
+ * handler: async (ctx, {workId, context, result}) => {
345
+ * // context has been validated as a string
346
+ * // ... do something with the result
347
+ * },
348
+ * });
349
+ * ```
350
+ * or more manually:
351
+ * ```ts
262
352
  * export const completion = internalMutation({
263
- * args: {
264
- * workId: workIdValidator,
265
- * context: v.any(),
266
- * result: resultValidator,
267
- * },
353
+ * args: vOnCompleteValidator(v.string()),
268
354
  * handler: async (ctx, args) => {
269
355
  * console.log(args.result, "Got Context back -> ", args.context, Date.now() - args.context);
270
356
  * },
@@ -4,7 +4,7 @@ import { Id } from "./_generated/dataModel.js";
4
4
  import { internalMutation, MutationCtx } from "./_generated/server.js";
5
5
  import { kickMainLoop } from "./kick.js";
6
6
  import { createLogger } from "./logging.js";
7
- import { OnCompleteArgs, RunResult, runResult } from "./shared.js";
7
+ import { OnCompleteArgs, RunResult, vResultValidator } from "./shared.js";
8
8
  import { recordCompleted } from "./stats.js";
9
9
 
10
10
  export type CompleteJob = Infer<typeof completeArgs.fields.jobs.element>;
@@ -12,7 +12,7 @@ export type CompleteJob = Infer<typeof completeArgs.fields.jobs.element>;
12
12
  export const completeArgs = v.object({
13
13
  jobs: v.array(
14
14
  v.object({
15
- runResult: runResult,
15
+ runResult: vResultValidator,
16
16
  workId: v.id("work"),
17
17
  attempt: v.number(),
18
18
  })
@@ -5,7 +5,7 @@ import {
5
5
  config,
6
6
  onComplete,
7
7
  retryBehavior,
8
- runResult,
8
+ vResultValidator,
9
9
  } from "./shared.js";
10
10
 
11
11
  // Represents a slice of time to process work.
@@ -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: vResultValidator,
84
84
  workId: v.id("work"),
85
85
  retry: v.boolean(),
86
86
  })
@@ -64,7 +64,7 @@ export type RetryBehavior = {
64
64
  // This ensures that the type satisfies the schema.
65
65
  const _ = {} as RetryBehavior satisfies Infer<typeof retryBehavior>;
66
66
 
67
- export const runResult = v.union(
67
+ export const vResultValidator = v.union(
68
68
  v.object({
69
69
  kind: v.literal("success"),
70
70
  returnValue: v.any(),
@@ -77,7 +77,7 @@ export const runResult = v.union(
77
77
  kind: v.literal("canceled"),
78
78
  })
79
79
  );
80
- export type RunResult = Infer<typeof runResult>;
80
+ export type RunResult = Infer<typeof vResultValidator>;
81
81
 
82
82
  export const onComplete = v.object({
83
83
  fnHandle: v.string(), // mutation