@convex-dev/workpool 0.2.18-alpha.2 → 0.2.18
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 +74 -71
- package/dist/commonjs/client/index.d.ts +102 -38
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +124 -50
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/component/kick.d.ts.map +1 -1
- package/dist/commonjs/component/kick.js +2 -2
- package/dist/commonjs/component/kick.js.map +1 -1
- package/dist/commonjs/component/lib.d.ts +38 -1
- package/dist/commonjs/component/lib.d.ts.map +1 -1
- package/dist/commonjs/component/lib.js +83 -52
- package/dist/commonjs/component/lib.js.map +1 -1
- package/dist/esm/client/index.d.ts +102 -38
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +124 -50
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/component/kick.d.ts.map +1 -1
- package/dist/esm/component/kick.js +2 -2
- package/dist/esm/component/kick.js.map +1 -1
- package/dist/esm/component/lib.d.ts +38 -1
- package/dist/esm/component/lib.d.ts.map +1 -1
- package/dist/esm/component/lib.js +83 -52
- package/dist/esm/component/lib.js.map +1 -1
- package/package.json +13 -8
- package/src/client/index.ts +221 -83
- package/src/component/_generated/api.d.ts +34 -0
- package/src/component/kick.ts +3 -1
- package/src/component/lib.ts +99 -57
package/src/client/index.ts
CHANGED
|
@@ -40,7 +40,7 @@ export {
|
|
|
40
40
|
/** @deprecated Use `vResultValidator` instead. */
|
|
41
41
|
vResultValidator as resultValidator,
|
|
42
42
|
};
|
|
43
|
-
/**
|
|
43
|
+
/** Equivalent to `vOnCompleteArgs(<your-context-validator>)`. */
|
|
44
44
|
export const vOnComplete = vOnCompleteArgs(v.any());
|
|
45
45
|
/** @deprecated Use `vOnCompleteArgs()` instead. */
|
|
46
46
|
export const vOnCompleteValidator = vOnCompleteArgs;
|
|
@@ -52,6 +52,9 @@ export const DEFAULT_RETRY_BEHAVIOR: RetryBehavior = {
|
|
|
52
52
|
base: 2,
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
// UseApi<api> for jump to definition
|
|
56
|
+
export type WorkpoolComponent = UseApi<Mounts>;
|
|
57
|
+
|
|
55
58
|
export class Workpool {
|
|
56
59
|
/**
|
|
57
60
|
* Initializes a Workpool.
|
|
@@ -65,9 +68,10 @@ export class Workpool {
|
|
|
65
68
|
* @param options - The {@link WorkpoolOptions} for the Workpool.
|
|
66
69
|
*/
|
|
67
70
|
constructor(
|
|
68
|
-
|
|
71
|
+
public component: WorkpoolComponent,
|
|
69
72
|
public options: WorkpoolOptions
|
|
70
73
|
) {}
|
|
74
|
+
|
|
71
75
|
/**
|
|
72
76
|
* Enqueues an action to be run.
|
|
73
77
|
*
|
|
@@ -82,28 +86,48 @@ export class Workpool {
|
|
|
82
86
|
ctx: RunMutationCtx,
|
|
83
87
|
fn: FunctionReference<"action", FunctionVisibility, Args, ReturnType>,
|
|
84
88
|
fnArgs: Args,
|
|
85
|
-
options?: RetryOption &
|
|
89
|
+
options?: RetryOption & EnqueueOptions
|
|
86
90
|
): Promise<WorkId> {
|
|
87
91
|
const retryBehavior = getRetryBehavior(
|
|
88
92
|
this.options.defaultRetryBehavior,
|
|
89
93
|
this.options.retryActionsByDefault,
|
|
90
94
|
options?.retry
|
|
91
95
|
);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
return enqueue(this.component, ctx, "action", fn, fnArgs, {
|
|
97
|
+
retryBehavior,
|
|
98
|
+
...this.options,
|
|
99
|
+
...options,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Enqueues a batch of actions to be run.
|
|
105
|
+
* Each action will be run independently, and the onComplete handler will
|
|
106
|
+
* be called for each action.
|
|
107
|
+
*
|
|
108
|
+
* @param ctx - The mutation or action ctx that can call ctx.runMutation.
|
|
109
|
+
* @param fn - The action to run, like `internal.example.myAction`.
|
|
110
|
+
* @param argsArray - The arguments to pass to the action.
|
|
111
|
+
* @param options - The options for the actions to specify retry behavior,
|
|
112
|
+
* onComplete handling, and scheduling via `runAt` or `runAfter`.
|
|
113
|
+
* @returns The IDs of the work that was enqueued.
|
|
114
|
+
*/
|
|
115
|
+
async enqueueActionBatch<Args extends DefaultFunctionArgs, ReturnType>(
|
|
116
|
+
ctx: RunMutationCtx,
|
|
117
|
+
fn: FunctionReference<"action", FunctionVisibility, Args, ReturnType>,
|
|
118
|
+
argsArray: Array<Args>,
|
|
119
|
+
options?: RetryOption & EnqueueOptions
|
|
120
|
+
): Promise<WorkId[]> {
|
|
121
|
+
const retryBehavior = getRetryBehavior(
|
|
122
|
+
this.options.defaultRetryBehavior,
|
|
123
|
+
this.options.retryActionsByDefault,
|
|
124
|
+
options?.retry
|
|
125
|
+
);
|
|
126
|
+
return enqueueBatch(this.component, ctx, "action", fn, argsArray, {
|
|
104
127
|
retryBehavior,
|
|
128
|
+
...this.options,
|
|
129
|
+
...options,
|
|
105
130
|
});
|
|
106
|
-
return id as WorkId;
|
|
107
131
|
}
|
|
108
132
|
|
|
109
133
|
/**
|
|
@@ -123,44 +147,81 @@ export class Workpool {
|
|
|
123
147
|
ctx: RunMutationCtx,
|
|
124
148
|
fn: FunctionReference<"mutation", FunctionVisibility, Args, ReturnType>,
|
|
125
149
|
fnArgs: Args,
|
|
126
|
-
options?:
|
|
150
|
+
options?: EnqueueOptions
|
|
127
151
|
): Promise<WorkId> {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
return enqueue(this.component, ctx, "mutation", fn, fnArgs, {
|
|
153
|
+
...this.options,
|
|
154
|
+
...options,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Enqueues a batch of mutations to be run.
|
|
159
|
+
* Each mutation will be run independently, and the onComplete handler will
|
|
160
|
+
* be called for each mutation.
|
|
161
|
+
*
|
|
162
|
+
* @param ctx - The mutation or action context that can call ctx.runMutation.
|
|
163
|
+
* @param fn - The mutation to run, like `internal.example.myMutation`.
|
|
164
|
+
* @param argsArray - The arguments to pass to the mutations.
|
|
165
|
+
* @param options - The options for the mutations to specify onComplete handling
|
|
166
|
+
* and scheduling via `runAt` or `runAfter`.
|
|
167
|
+
*/
|
|
168
|
+
async enqueueMutationBatch<Args extends DefaultFunctionArgs, ReturnType>(
|
|
169
|
+
ctx: RunMutationCtx,
|
|
170
|
+
fn: FunctionReference<"mutation", FunctionVisibility, Args, ReturnType>,
|
|
171
|
+
argsArray: Array<Args>,
|
|
172
|
+
options?: EnqueueOptions
|
|
173
|
+
): Promise<WorkId[]> {
|
|
174
|
+
return enqueueBatch(this.component, ctx, "mutation", fn, argsArray, {
|
|
175
|
+
...this.options,
|
|
176
|
+
...options,
|
|
140
177
|
});
|
|
141
|
-
return id as WorkId;
|
|
142
178
|
}
|
|
143
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Enqueues a query to be run.
|
|
182
|
+
* Usually not what you want, but it can be useful during workflows.
|
|
183
|
+
* The query is run in a mutation and the result is returned to the caller,
|
|
184
|
+
* so it can conflict if other mutations are writing the value.
|
|
185
|
+
*
|
|
186
|
+
* @param ctx - The mutation or action context that can call ctx.runMutation.
|
|
187
|
+
* @param fn - The query to run, like `internal.example.myQuery`.
|
|
188
|
+
* @param fnArgs - The arguments to pass to the query.
|
|
189
|
+
* @param options - The options for the query to specify onComplete handling
|
|
190
|
+
* and scheduling via `runAt` or `runAfter`.
|
|
191
|
+
*/
|
|
144
192
|
async enqueueQuery<Args extends DefaultFunctionArgs, ReturnType>(
|
|
145
193
|
ctx: RunMutationCtx,
|
|
146
194
|
fn: FunctionReference<"query", FunctionVisibility, Args, ReturnType>,
|
|
147
195
|
fnArgs: Args,
|
|
148
|
-
options?:
|
|
196
|
+
options?: EnqueueOptions
|
|
149
197
|
): Promise<WorkId> {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
return enqueue(this.component, ctx, "query", fn, fnArgs, {
|
|
199
|
+
...this.options,
|
|
200
|
+
...options,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Enqueues a batch of queries to be run.
|
|
206
|
+
* Each query will be run independently, and the onComplete handler will
|
|
207
|
+
* be called for each query.
|
|
208
|
+
*
|
|
209
|
+
* @param ctx - The mutation or action context that can call ctx.runMutation.
|
|
210
|
+
* @param fn - The query to run, like `internal.example.myQuery`.
|
|
211
|
+
* @param argsArray - The arguments to pass to the queries.
|
|
212
|
+
* @param options - The options for the queries to specify onComplete handling
|
|
213
|
+
* and scheduling via `runAt` or `runAfter`.
|
|
214
|
+
*/
|
|
215
|
+
async enqueueQueryBatch<Args extends DefaultFunctionArgs, ReturnType>(
|
|
216
|
+
ctx: RunMutationCtx,
|
|
217
|
+
fn: FunctionReference<"query", FunctionVisibility, Args, ReturnType>,
|
|
218
|
+
argsArray: Array<Args>,
|
|
219
|
+
options?: EnqueueOptions
|
|
220
|
+
): Promise<WorkId[]> {
|
|
221
|
+
return enqueueBatch(this.component, ctx, "query", fn, argsArray, {
|
|
222
|
+
...this.options,
|
|
223
|
+
...options,
|
|
162
224
|
});
|
|
163
|
-
return id as WorkId;
|
|
164
225
|
}
|
|
165
226
|
|
|
166
227
|
/**
|
|
@@ -200,6 +261,17 @@ export class Workpool {
|
|
|
200
261
|
return ctx.runQuery(this.component.lib.status, { id });
|
|
201
262
|
}
|
|
202
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Gets the status of a batch of work items.
|
|
266
|
+
*
|
|
267
|
+
* @param ctx - The query context that can call ctx.runQuery.
|
|
268
|
+
* @param ids - The IDs of the work to get the status of.
|
|
269
|
+
* @returns The status of the work items.
|
|
270
|
+
*/
|
|
271
|
+
async statusBatch(ctx: RunQueryCtx, ids: WorkId[]): Promise<Status[]> {
|
|
272
|
+
return ctx.runQuery(this.component.lib.statusBatch, { ids });
|
|
273
|
+
}
|
|
274
|
+
|
|
203
275
|
/**
|
|
204
276
|
* Defines a mutation that will be run after a work item completes.
|
|
205
277
|
* You can pass this to a call to enqueue* like so:
|
|
@@ -264,20 +336,11 @@ export function vOnCompleteArgs<
|
|
|
264
336
|
>(context?: V) {
|
|
265
337
|
return v.object({
|
|
266
338
|
workId: vWorkIdValidator,
|
|
267
|
-
context: context ?? v.optional(v.any()),
|
|
339
|
+
context: (context ?? v.optional(v.any())) as V,
|
|
268
340
|
result: vResultValidator,
|
|
269
341
|
});
|
|
270
342
|
}
|
|
271
343
|
|
|
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
344
|
export type RetryOption = {
|
|
282
345
|
/** Whether to retry the action if it fails.
|
|
283
346
|
* If true, it will use the default retry behavior.
|
|
@@ -315,25 +378,14 @@ export type WorkpoolRetryOptions = {
|
|
|
315
378
|
*/
|
|
316
379
|
retryActionsByDefault?: boolean;
|
|
317
380
|
};
|
|
318
|
-
export type SchedulerOptions =
|
|
319
|
-
| {
|
|
320
|
-
/**
|
|
321
|
-
* The time (ms since epoch) to run the action at.
|
|
322
|
-
* If not provided, the action will be run as soon as possible.
|
|
323
|
-
* Note: this is advisory only. It may run later.
|
|
324
|
-
*/
|
|
325
|
-
runAt?: number;
|
|
326
|
-
}
|
|
327
|
-
| {
|
|
328
|
-
/**
|
|
329
|
-
* The number of milliseconds to run the action after.
|
|
330
|
-
* If not provided, the action will be run as soon as possible.
|
|
331
|
-
* Note: this is advisory only. It may run later.
|
|
332
|
-
*/
|
|
333
|
-
runAfter?: number;
|
|
334
|
-
};
|
|
335
381
|
|
|
336
|
-
export type
|
|
382
|
+
export type EnqueueOptions = {
|
|
383
|
+
/**
|
|
384
|
+
* The name of the function. By default, if you pass in api.foo.bar.baz,
|
|
385
|
+
* it will use "foo/bar:baz" as the name. If you pass in a function handle,
|
|
386
|
+
* it will use the function handle directly.
|
|
387
|
+
*/
|
|
388
|
+
name?: string;
|
|
337
389
|
/**
|
|
338
390
|
* A mutation to run after the function succeeds, fails, or is canceled.
|
|
339
391
|
* The context type is for your use, feel free to provide a validator for it.
|
|
@@ -368,7 +420,24 @@ export type CallbackOptions = {
|
|
|
368
420
|
* Useful for passing data from the enqueue site to the onComplete site.
|
|
369
421
|
*/
|
|
370
422
|
context?: unknown;
|
|
371
|
-
}
|
|
423
|
+
} & (
|
|
424
|
+
| {
|
|
425
|
+
/**
|
|
426
|
+
* The time (ms since epoch) to run the action at.
|
|
427
|
+
* If not provided, the action will be run as soon as possible.
|
|
428
|
+
* Note: this is advisory only. It may run later.
|
|
429
|
+
*/
|
|
430
|
+
runAt?: number;
|
|
431
|
+
}
|
|
432
|
+
| {
|
|
433
|
+
/**
|
|
434
|
+
* The number of milliseconds to run the action after.
|
|
435
|
+
* If not provided, the action will be run as soon as possible.
|
|
436
|
+
* Note: this is advisory only. It may run later.
|
|
437
|
+
*/
|
|
438
|
+
runAfter?: number;
|
|
439
|
+
}
|
|
440
|
+
);
|
|
372
441
|
|
|
373
442
|
export type OnCompleteArgs = {
|
|
374
443
|
/**
|
|
@@ -412,36 +481,105 @@ function getRetryBehavior(
|
|
|
412
481
|
return retryOverride ?? (retryByDefault ? defaultRetry : undefined);
|
|
413
482
|
}
|
|
414
483
|
|
|
415
|
-
async function
|
|
484
|
+
async function enqueueArgs(
|
|
416
485
|
fn:
|
|
417
486
|
| FunctionReference<FunctionType, FunctionVisibility>
|
|
418
487
|
| FunctionHandle<FunctionType, DefaultFunctionArgs>,
|
|
419
|
-
|
|
420
|
-
|
|
488
|
+
opts:
|
|
489
|
+
| (EnqueueOptions & Partial<Config> & { retryBehavior?: RetryBehavior })
|
|
490
|
+
| undefined
|
|
421
491
|
) {
|
|
422
492
|
const [fnHandle, fnName] =
|
|
423
493
|
typeof fn === "string" && fn.startsWith("function://")
|
|
424
|
-
? [fn, name ?? fn]
|
|
425
|
-
: [await createFunctionHandle(fn), name ?? safeFunctionName(fn)];
|
|
494
|
+
? [fn, opts?.name ?? fn]
|
|
495
|
+
: [await createFunctionHandle(fn), opts?.name ?? safeFunctionName(fn)];
|
|
496
|
+
const onComplete: OnComplete | undefined = opts?.onComplete
|
|
497
|
+
? {
|
|
498
|
+
fnHandle: await createFunctionHandle(opts.onComplete),
|
|
499
|
+
context: opts.context,
|
|
500
|
+
}
|
|
501
|
+
: undefined;
|
|
426
502
|
return {
|
|
427
503
|
fnHandle,
|
|
428
504
|
fnName,
|
|
505
|
+
onComplete,
|
|
506
|
+
runAt: getRunAt(opts),
|
|
507
|
+
retryBehavior: opts?.retryBehavior,
|
|
429
508
|
config: {
|
|
430
|
-
logLevel: logLevel ?? DEFAULT_LOG_LEVEL,
|
|
431
|
-
maxParallelism: maxParallelism ?? DEFAULT_MAX_PARALLELISM,
|
|
509
|
+
logLevel: opts?.logLevel ?? DEFAULT_LOG_LEVEL,
|
|
510
|
+
maxParallelism: opts?.maxParallelism ?? DEFAULT_MAX_PARALLELISM,
|
|
432
511
|
},
|
|
433
512
|
};
|
|
434
513
|
}
|
|
435
514
|
|
|
436
|
-
function getRunAt(
|
|
515
|
+
function getRunAt(
|
|
516
|
+
options:
|
|
517
|
+
| {
|
|
518
|
+
runAt?: number;
|
|
519
|
+
runAfter?: number;
|
|
520
|
+
}
|
|
521
|
+
| undefined
|
|
522
|
+
): number {
|
|
437
523
|
if (!options) {
|
|
438
524
|
return Date.now();
|
|
439
525
|
}
|
|
440
|
-
if (
|
|
526
|
+
if (options.runAt !== undefined) {
|
|
441
527
|
return options.runAt;
|
|
442
528
|
}
|
|
443
|
-
if (
|
|
529
|
+
if (options.runAfter !== undefined) {
|
|
444
530
|
return Date.now() + options.runAfter;
|
|
445
531
|
}
|
|
446
532
|
return Date.now();
|
|
447
533
|
}
|
|
534
|
+
|
|
535
|
+
export async function enqueueBatch<
|
|
536
|
+
FnType extends FunctionType,
|
|
537
|
+
Args extends DefaultFunctionArgs,
|
|
538
|
+
ReturnType,
|
|
539
|
+
>(
|
|
540
|
+
component: UseApi<Mounts>,
|
|
541
|
+
ctx: RunMutationCtx,
|
|
542
|
+
fnType: FnType,
|
|
543
|
+
fn: FunctionReference<FnType, FunctionVisibility, Args, ReturnType>,
|
|
544
|
+
fnArgsArray: Array<Args>,
|
|
545
|
+
options: EnqueueOptions & {
|
|
546
|
+
retryBehavior?: RetryBehavior;
|
|
547
|
+
maxParallelism?: number;
|
|
548
|
+
logLevel?: LogLevel;
|
|
549
|
+
}
|
|
550
|
+
): Promise<WorkId[]> {
|
|
551
|
+
const { config, ...defaults } = await enqueueArgs(fn, options);
|
|
552
|
+
const ids = await ctx.runMutation(component.lib.enqueueBatch, {
|
|
553
|
+
items: fnArgsArray.map((fnArgs) => ({
|
|
554
|
+
...defaults,
|
|
555
|
+
fnArgs,
|
|
556
|
+
fnType,
|
|
557
|
+
})),
|
|
558
|
+
config,
|
|
559
|
+
});
|
|
560
|
+
return ids as WorkId[];
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export async function enqueue<
|
|
564
|
+
FnType extends FunctionType,
|
|
565
|
+
Args extends DefaultFunctionArgs,
|
|
566
|
+
ReturnType,
|
|
567
|
+
>(
|
|
568
|
+
component: UseApi<Mounts>,
|
|
569
|
+
ctx: RunMutationCtx,
|
|
570
|
+
fnType: FnType,
|
|
571
|
+
fn: FunctionReference<FnType, FunctionVisibility, Args, ReturnType>,
|
|
572
|
+
fnArgs: Args,
|
|
573
|
+
options: EnqueueOptions & {
|
|
574
|
+
retryBehavior?: RetryBehavior;
|
|
575
|
+
maxParallelism?: number;
|
|
576
|
+
logLevel?: LogLevel;
|
|
577
|
+
}
|
|
578
|
+
): Promise<WorkId> {
|
|
579
|
+
const id = await ctx.runMutation(component.lib.enqueue, {
|
|
580
|
+
...(await enqueueArgs(fn, options)),
|
|
581
|
+
fnArgs,
|
|
582
|
+
fnType,
|
|
583
|
+
});
|
|
584
|
+
return id as WorkId;
|
|
585
|
+
}
|
|
@@ -89,6 +89,30 @@ export type Mounts = {
|
|
|
89
89
|
},
|
|
90
90
|
string
|
|
91
91
|
>;
|
|
92
|
+
enqueueBatch: FunctionReference<
|
|
93
|
+
"mutation",
|
|
94
|
+
"public",
|
|
95
|
+
{
|
|
96
|
+
config: {
|
|
97
|
+
logLevel: "DEBUG" | "TRACE" | "INFO" | "REPORT" | "WARN" | "ERROR";
|
|
98
|
+
maxParallelism: number;
|
|
99
|
+
};
|
|
100
|
+
items: Array<{
|
|
101
|
+
fnArgs: any;
|
|
102
|
+
fnHandle: string;
|
|
103
|
+
fnName: string;
|
|
104
|
+
fnType: "action" | "mutation" | "query";
|
|
105
|
+
onComplete?: { context?: any; fnHandle: string };
|
|
106
|
+
retryBehavior?: {
|
|
107
|
+
base: number;
|
|
108
|
+
initialBackoffMs: number;
|
|
109
|
+
maxAttempts: number;
|
|
110
|
+
};
|
|
111
|
+
runAt: number;
|
|
112
|
+
}>;
|
|
113
|
+
},
|
|
114
|
+
Array<string>
|
|
115
|
+
>;
|
|
92
116
|
status: FunctionReference<
|
|
93
117
|
"query",
|
|
94
118
|
"public",
|
|
@@ -97,6 +121,16 @@ export type Mounts = {
|
|
|
97
121
|
| { previousAttempts: number; state: "running" }
|
|
98
122
|
| { state: "finished" }
|
|
99
123
|
>;
|
|
124
|
+
statusBatch: FunctionReference<
|
|
125
|
+
"query",
|
|
126
|
+
"public",
|
|
127
|
+
{ ids: Array<string> },
|
|
128
|
+
Array<
|
|
129
|
+
| { previousAttempts: number; state: "pending" }
|
|
130
|
+
| { previousAttempts: number; state: "running" }
|
|
131
|
+
| { state: "finished" }
|
|
132
|
+
>
|
|
133
|
+
>;
|
|
100
134
|
};
|
|
101
135
|
};
|
|
102
136
|
// For now fullApiWithMounts is only fullApi which provides
|
package/src/component/kick.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
fromSegment,
|
|
10
10
|
getCurrentSegment,
|
|
11
11
|
getNextSegment,
|
|
12
|
+
SECOND,
|
|
13
|
+
toSegment,
|
|
12
14
|
} from "./shared.js";
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -40,7 +42,7 @@ export async function kickMainLoop(
|
|
|
40
42
|
);
|
|
41
43
|
return next;
|
|
42
44
|
}
|
|
43
|
-
if (runStatus.state.segment <=
|
|
45
|
+
if (runStatus.state.segment <= toSegment(Date.now() + SECOND)) {
|
|
44
46
|
console.debug(
|
|
45
47
|
`[${source}] main is scheduled to run soon enough, so we don't need to kick it`
|
|
46
48
|
);
|
package/src/component/lib.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
84
|
+
returns: v.array(v.id("work")),
|
|
85
|
+
handler: async (ctx, { config, items }) => {
|
|
86
|
+
validateConfig(config);
|
|
37
87
|
const console = createLogger(config.logLevel);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
|
@@ -119,27 +150,38 @@ export const cancelAll = mutation({
|
|
|
119
150
|
export const status = query({
|
|
120
151
|
args: { id: v.id("work") },
|
|
121
152
|
returns: statusValidator,
|
|
122
|
-
handler:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
handler: statusHandler,
|
|
154
|
+
});
|
|
155
|
+
async function statusHandler(ctx: QueryCtx, { id }: { id: Id<"work"> }) {
|
|
156
|
+
const work = await ctx.db.get(id);
|
|
157
|
+
if (!work) {
|
|
158
|
+
return { state: "finished" } as const;
|
|
159
|
+
}
|
|
160
|
+
const pendingStart = await ctx.db
|
|
161
|
+
.query("pendingStart")
|
|
162
|
+
.withIndex("workId", (q) => q.eq("workId", id))
|
|
163
|
+
.unique();
|
|
164
|
+
if (pendingStart) {
|
|
165
|
+
return { state: "pending", previousAttempts: work.attempts } as const;
|
|
166
|
+
}
|
|
167
|
+
const pendingCompletion = await ctx.db
|
|
168
|
+
.query("pendingCompletion")
|
|
169
|
+
.withIndex("workId", (q) => q.eq("workId", id))
|
|
170
|
+
.unique();
|
|
171
|
+
if (pendingCompletion?.retry) {
|
|
172
|
+
return { state: "pending", previousAttempts: work.attempts } as const;
|
|
173
|
+
}
|
|
174
|
+
// Assume it's in progress. It could be pending cancelation
|
|
175
|
+
return { state: "running", previousAttempts: work.attempts } as const;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const statusBatch = query({
|
|
179
|
+
args: { ids: v.array(v.id("work")) },
|
|
180
|
+
returns: v.array(statusValidator),
|
|
181
|
+
handler: async (ctx, { ids }) => {
|
|
182
|
+
return await Promise.all(
|
|
183
|
+
ids.map(async (id) => await statusHandler(ctx, { id }))
|
|
184
|
+
);
|
|
143
185
|
},
|
|
144
186
|
});
|
|
145
187
|
|