@@ -3,6 +3,7 @@ import type {
33 WorkpoolOptions ,
44 WorkpoolRetryOptions ,
55} from "@convex-dev/workpool" ;
6+ import { parse } from "convex-helpers/validators" ;
67import {
78 createFunctionHandle ,
89 type FunctionArgs ,
@@ -25,24 +26,22 @@ import type {
2526import type { Step } from "../component/schema.js" ;
2627import type {
2728 EventId ,
28- EventSpec ,
2929 OnCompleteArgs ,
3030 WorkflowId ,
3131 WorkflowStep ,
3232} from "../types.js" ;
3333import { safeFunctionName } from "./safeFunctionName.js" ;
34- import type { OpaqueIds , WorkflowComponent , WorkflowCtx } from "./types.js" ;
34+ import type { OpaqueIds , WorkflowComponent } from "./types.js" ;
35+ import type { WorkflowCtx } from "./workflowContext.js" ;
3536import { workflowMutation } from "./workflowMutation.js" ;
36- import { parse } from "convex-helpers/validators" ;
3737
3838export {
3939 vWorkflowId ,
40- type WorkflowId ,
4140 vWorkflowStep ,
41+ type WorkflowId ,
4242 type WorkflowStep ,
4343} from "../types.js" ;
44- export type { RunOptions , WorkflowCtx } from "./types.js" ;
45- export { defineEvent } from "./events.js" ;
44+ export type { RunOptions , WorkflowCtx } from "./workflowContext.js" ;
4645
4746export type CallbackOptions = {
4847 /**
@@ -263,51 +262,93 @@ export class WorkflowManager {
263262 /**
264263 * Send an event to a workflow.
265264 *
266- * @param ctx - Either ctx from a mutation/action or a workflow step.
267- * @param args - The event arguments.
265+ * @param ctx - From a mutation, action or workflow step.
266+ * @param args - Either send an event by its ID, or by name and workflow ID.
267+ * If you have a validator, you must provide a value.
268+ * If you provide an error string, awaiting the event will throw an error.
268269 */
269270 async sendEvent < T = null , Name extends string = string > (
270271 ctx : RunMutationCtx ,
271- {
272- workflowId,
273- event,
274- value,
275- } : (
276- | { workflowId : WorkflowId ; event : EventSpec < Name , T > }
277- | { workflowId ?: undefined ; event : EventSpec < Name , T > & { id : string } }
272+ args : (
273+ | { workflowId : WorkflowId ; name : Name ; id ?: EventId < Name > }
274+ | { workflowId ?: undefined ; name ?: Name ; id : EventId < Name > }
278275 ) &
279276 (
280- | ( T extends null
281- ? { value ?: null ; error ?: undefined }
282- : { value : T ; error ?: undefined } )
277+ | { validator ?: undefined ; value ?: T }
278+ | { validator : Validator < T , any , any > ; value : T }
283279 | { error : string ; value ?: undefined }
284280 ) ,
285281 ) : Promise < EventId < Name > > {
286- let result = {
287- kind : "success" as const ,
288- returnValue : event . validator ? parse ( event . validator , value ) : value ,
289- } satisfies RunResult ;
282+ let result : RunResult =
283+ "error" in args
284+ ? {
285+ kind : "failed" ,
286+ error : args . error ,
287+ }
288+ : {
289+ kind : "success" as const ,
290+ returnValue : args . validator
291+ ? parse ( args . validator , args . value )
292+ : "value" in args
293+ ? args . value
294+ : null ,
295+ } ;
290296 return ( await ctx . runMutation ( this . component . event . send , {
291- eventId : event . id ,
297+ eventId : args . id ,
292298 result,
293- name : event . name ,
294- workflowId : workflowId ,
299+ name : args . name ,
300+ workflowId : args . workflowId ,
295301 workpoolOptions : this . options ?. workpoolOptions ,
296302 } ) ) as EventId < Name > ;
297303 }
298304
305+ /**
306+ * Create an event ahead of time, enabling awaiting a specific event by ID.
307+ * @param ctx - From an action, mutation or workflow step.
308+ * @param args - The name of the event and what workflow it belongs to.
309+ * @returns The event ID, which can be used to send the event or await it.
310+ */
299311 async createEvent < Name extends string > (
300312 ctx : RunMutationCtx ,
301- component : WorkflowComponent ,
302313 args : { name : Name ; workflowId : WorkflowId } ,
303314 ) : Promise < EventId < Name > > {
304- return ( await ctx . runMutation ( component . event . create , {
315+ return ( await ctx . runMutation ( this . component . event . create , {
305316 name : args . name ,
306317 workflowId : args . workflowId ,
307318 } ) ) as EventId < Name > ;
308319 }
309320}
310321
322+ /**
323+ * Define an event specification: a name and a validator.
324+ * This helps share definitions between workflow.sendEvent and ctx.awaitEvent.
325+ * e.g.
326+ * ```ts
327+ * const approvalEvent = defineEvent({
328+ * name: "approval",
329+ * validator: v.object({ approved: v.boolean() }),
330+ * });
331+ * ```
332+ * Then you can await it in a workflow:
333+ * ```ts
334+ * const result = await ctx.awaitEvent(approvalEvent);
335+ * ```
336+ * And send from somewhere else:
337+ * ```ts
338+ * await workflow.sendEvent(ctx, {
339+ * ...approvalEvent,
340+ * workflowId,
341+ * value: { approved: true },
342+ * });
343+ * ```
344+ */
345+ export function defineEvent <
346+ Name extends string ,
347+ V extends Validator < unknown , "required" , string > ,
348+ > ( spec : { name : Name ; validator : V } ) {
349+ return spec ;
350+ }
351+
311352type RunQueryCtx = {
312353 runQuery : GenericQueryCtx < GenericDataModel > [ "runQuery" ] ;
313354} ;
0 commit comments