Skip to content

Commit aed0ed2

Browse files
committed
add events
1 parent 17d516a commit aed0ed2

File tree

13 files changed

+867
-195
lines changed

13 files changed

+867
-195
lines changed

example/convex/_generated/api.d.ts

Lines changed: 158 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import type * as admin from "../admin.js";
1212
import type * as example from "../example.js";
1313
import type * as transcription from "../transcription.js";
14+
import type * as userConfirmation from "../userConfirmation.js";
1415

1516
import type {
1617
ApiFromModules,
@@ -30,6 +31,7 @@ declare const fullApi: ApiFromModules<{
3031
admin: typeof admin;
3132
example: typeof example;
3233
transcription: typeof transcription;
34+
userConfirmation: typeof userConfirmation;
3335
}>;
3436
declare const fullApiWithMounts: typeof fullApi;
3537

@@ -44,6 +46,38 @@ export declare const internal: FilterApi<
4446

4547
export declare const components: {
4648
workflow: {
49+
event: {
50+
create: FunctionReference<
51+
"mutation",
52+
"internal",
53+
{ name: string; workflowId: string },
54+
string
55+
>;
56+
send: FunctionReference<
57+
"mutation",
58+
"internal",
59+
{
60+
eventId?: string;
61+
name?: string;
62+
result:
63+
| { kind: "success"; returnValue: any }
64+
| { error: string; kind: "failed" }
65+
| { kind: "canceled" };
66+
workflowId: string;
67+
workpoolOptions?: {
68+
defaultRetryBehavior?: {
69+
base: number;
70+
initialBackoffMs: number;
71+
maxAttempts: number;
72+
};
73+
logLevel?: "DEBUG" | "TRACE" | "INFO" | "REPORT" | "WARN" | "ERROR";
74+
maxParallelism?: number;
75+
retryActionsByDefault?: boolean;
76+
};
77+
},
78+
string
79+
>;
80+
};
4781
journal: {
4882
load: FunctionReference<
4983
"query",
@@ -54,21 +88,37 @@ export declare const components: {
5488
journalEntries: Array<{
5589
_creationTime: number;
5690
_id: string;
57-
step: {
58-
args: any;
59-
argsSize: number;
60-
completedAt?: number;
61-
functionType: "query" | "mutation" | "action";
62-
handle: string;
63-
inProgress: boolean;
64-
name: string;
65-
runResult?:
66-
| { kind: "success"; returnValue: any }
67-
| { error: string; kind: "failed" }
68-
| { kind: "canceled" };
69-
startedAt: number;
70-
workId?: string;
71-
};
91+
step:
92+
| {
93+
args: any;
94+
argsSize: number;
95+
completedAt?: number;
96+
functionType: "query" | "mutation" | "action";
97+
handle: string;
98+
inProgress: boolean;
99+
kind?: "function";
100+
name: string;
101+
runResult?:
102+
| { kind: "success"; returnValue: any }
103+
| { error: string; kind: "failed" }
104+
| { kind: "canceled" };
105+
startedAt: number;
106+
workId?: string;
107+
}
108+
| {
109+
args: { eventId?: string };
110+
argsSize: number;
111+
completedAt?: number;
112+
inProgress: boolean;
113+
kind: "event";
114+
name: string;
115+
runResult?:
116+
| { kind: "success"; returnValue: any }
117+
| { error: string; kind: "failed" }
118+
| { kind: "canceled" };
119+
startedAt: number;
120+
workId?: string;
121+
};
72122
stepNumber: number;
73123
workflowId: string;
74124
}>;
@@ -102,21 +152,37 @@ export declare const components: {
102152
| boolean
103153
| { base: number; initialBackoffMs: number; maxAttempts: number };
104154
schedulerOptions?: { runAt?: number } | { runAfter?: number };
105-
step: {
106-
args: any;
107-
argsSize: number;
108-
completedAt?: number;
109-
functionType: "query" | "mutation" | "action";
110-
handle: string;
111-
inProgress: boolean;
112-
name: string;
113-
runResult?:
114-
| { kind: "success"; returnValue: any }
115-
| { error: string; kind: "failed" }
116-
| { kind: "canceled" };
117-
startedAt: number;
118-
workId?: string;
119-
};
155+
step:
156+
| {
157+
args: any;
158+
argsSize: number;
159+
completedAt?: number;
160+
functionType: "query" | "mutation" | "action";
161+
handle: string;
162+
inProgress: boolean;
163+
kind?: "function";
164+
name: string;
165+
runResult?:
166+
| { kind: "success"; returnValue: any }
167+
| { error: string; kind: "failed" }
168+
| { kind: "canceled" };
169+
startedAt: number;
170+
workId?: string;
171+
}
172+
| {
173+
args: { eventId?: string };
174+
argsSize: number;
175+
completedAt?: number;
176+
inProgress: boolean;
177+
kind: "event";
178+
name: string;
179+
runResult?:
180+
| { kind: "success"; returnValue: any }
181+
| { error: string; kind: "failed" }
182+
| { kind: "canceled" };
183+
startedAt: number;
184+
workId?: string;
185+
};
120186
}>;
121187
workflowId: string;
122188
workpoolOptions?: {
@@ -133,21 +199,37 @@ export declare const components: {
133199
Array<{
134200
_creationTime: number;
135201
_id: string;
136-
step: {
137-
args: any;
138-
argsSize: number;
139-
completedAt?: number;
140-
functionType: "query" | "mutation" | "action";
141-
handle: string;
142-
inProgress: boolean;
143-
name: string;
144-
runResult?:
145-
| { kind: "success"; returnValue: any }
146-
| { error: string; kind: "failed" }
147-
| { kind: "canceled" };
148-
startedAt: number;
149-
workId?: string;
150-
};
202+
step:
203+
| {
204+
args: any;
205+
argsSize: number;
206+
completedAt?: number;
207+
functionType: "query" | "mutation" | "action";
208+
handle: string;
209+
inProgress: boolean;
210+
kind?: "function";
211+
name: string;
212+
runResult?:
213+
| { kind: "success"; returnValue: any }
214+
| { error: string; kind: "failed" }
215+
| { kind: "canceled" };
216+
startedAt: number;
217+
workId?: string;
218+
}
219+
| {
220+
args: { eventId?: string };
221+
argsSize: number;
222+
completedAt?: number;
223+
inProgress: boolean;
224+
kind: "event";
225+
name: string;
226+
runResult?:
227+
| { kind: "success"; returnValue: any }
228+
| { error: string; kind: "failed" }
229+
| { kind: "canceled" };
230+
startedAt: number;
231+
workId?: string;
232+
};
151233
stepNumber: number;
152234
workflowId: string;
153235
}>
@@ -200,21 +282,37 @@ export declare const components: {
200282
inProgress: Array<{
201283
_creationTime: number;
202284
_id: string;
203-
step: {
204-
args: any;
205-
argsSize: number;
206-
completedAt?: number;
207-
functionType: "query" | "mutation" | "action";
208-
handle: string;
209-
inProgress: boolean;
210-
name: string;
211-
runResult?:
212-
| { kind: "success"; returnValue: any }
213-
| { error: string; kind: "failed" }
214-
| { kind: "canceled" };
215-
startedAt: number;
216-
workId?: string;
217-
};
285+
step:
286+
| {
287+
args: any;
288+
argsSize: number;
289+
completedAt?: number;
290+
functionType: "query" | "mutation" | "action";
291+
handle: string;
292+
inProgress: boolean;
293+
kind?: "function";
294+
name: string;
295+
runResult?:
296+
| { kind: "success"; returnValue: any }
297+
| { error: string; kind: "failed" }
298+
| { kind: "canceled" };
299+
startedAt: number;
300+
workId?: string;
301+
}
302+
| {
303+
args: { eventId?: string };
304+
argsSize: number;
305+
completedAt?: number;
306+
inProgress: boolean;
307+
kind: "event";
308+
name: string;
309+
runResult?:
310+
| { kind: "success"; returnValue: any }
311+
| { error: string; kind: "failed" }
312+
| { kind: "canceled" };
313+
startedAt: number;
314+
workId?: string;
315+
};
218316
stepNumber: number;
219317
workflowId: string;
220318
}>;

example/convex/userConfirmation.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { defineEvent, vWorkflowId } from "@convex-dev/workflow";
2+
import { v } from "convex/values";
3+
import { internal } from "./_generated/api";
4+
import { internalAction, mutation } from "./_generated/server";
5+
import { workflow } from "./example";
6+
7+
const approvalEvent = defineEvent({
8+
name: "approval",
9+
validator: v.union(
10+
v.object({ approved: v.literal(true), choice: v.number() }),
11+
v.object({ approved: v.literal(false), reason: v.string() }),
12+
),
13+
});
14+
15+
export const confirmationWorkflow = workflow.define({
16+
args: { prompt: v.string() },
17+
returns: v.string(),
18+
handler: async (step, args): Promise<string> => {
19+
const proposals = await step.runAction(
20+
internal.userConfirmation.generateProposals,
21+
{ prompt: args.prompt },
22+
{ retry: true },
23+
);
24+
const approval = await step.awaitEvent(approvalEvent);
25+
if (!approval.approved) {
26+
return "rejected: " + approval.reason;
27+
}
28+
const choice = proposals[approval.choice];
29+
return choice;
30+
},
31+
});
32+
33+
export const generateProposals = internalAction({
34+
args: { prompt: v.string() },
35+
handler: async (ctx, args) => {
36+
// imagine this is a call to an LLM
37+
return ["proposal1", "proposal2", "proposal3"];
38+
},
39+
});
40+
41+
export const chooseProposal = mutation({
42+
args: { workflowId: vWorkflowId, choice: v.number() },
43+
handler: async (ctx, args) => {
44+
await workflow.sendEvent(ctx, args.workflowId, approvalEvent, {
45+
approved: true,
46+
choice: args.choice,
47+
});
48+
return true;
49+
},
50+
});

src/client/events.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { EventId, EventSpec, VEventId } from "../types.js";
2+
import { v, type Infer, type Validator } from "convex/values";
3+
4+
/**
5+
* Define a named event with a validator.
6+
* @param spec - The event spec.
7+
* @returns Utility functions to specify type-safe events and results.
8+
*/
9+
export function defineEvent<
10+
Name extends string,
11+
V extends Validator<any, any, any>,
12+
>(spec: {
13+
name: Name;
14+
validator?: V;
15+
}): EventSpec<Name, Infer<V>> & {
16+
/**
17+
* A validator for the named event ID.
18+
*/
19+
vEventId: VEventId<Name>;
20+
/**
21+
* Use this to provide an ID to `awaitEvent` or `sendEvent`.
22+
*/
23+
withId: (id: EventId<Name>) => EventSpec<Name, Infer<V>>;
24+
} {
25+
return {
26+
...spec,
27+
withId: (id: EventId<Name>) => ({ ...spec, id }),
28+
vEventId: v.string() as VEventId<Name>,
29+
};
30+
}
31+
32+
export type TypedRunResult<T> =
33+
| { kind: "success"; returnValue: T }
34+
| { kind: "failed"; error: string }
35+
| { kind: "canceled" };

0 commit comments

Comments
 (0)