Skip to content

Commit a1ba376

Browse files
committed
list steps
1 parent 3029210 commit a1ba376

File tree

8 files changed

+283
-15
lines changed

8 files changed

+283
-15
lines changed

example/convex/_generated/api.d.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,45 @@ export declare const components: {
395395
};
396396
}
397397
>;
398+
listSteps: FunctionReference<
399+
"query",
400+
"internal",
401+
{
402+
order: "asc" | "desc";
403+
paginationOpts: {
404+
cursor: string | null;
405+
endCursor?: string | null;
406+
id?: number;
407+
maximumBytesRead?: number;
408+
maximumRowsRead?: number;
409+
numItems: number;
410+
};
411+
workflowId: string;
412+
},
413+
{
414+
continueCursor: string;
415+
isDone: boolean;
416+
page: Array<{
417+
args: any;
418+
completedAt?: number;
419+
eventId?: string;
420+
kind: "function" | "workflow" | "event";
421+
name: string;
422+
nestedWorkflowId?: string;
423+
runResult?:
424+
| { kind: "success"; returnValue: any }
425+
| { error: string; kind: "failed" }
426+
| { kind: "canceled" };
427+
startedAt: number;
428+
stepId: string;
429+
stepNumber: number;
430+
workId?: string;
431+
workflowId: string;
432+
}>;
433+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
434+
splitCursor?: string | null;
435+
}
436+
>;
398437
};
399438
};
400439
};

src/client/index.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
type GenericDataModel,
1111
type GenericMutationCtx,
1212
type GenericQueryCtx,
13+
type PaginationOptions,
14+
type PaginationResult,
1315
type RegisteredMutation,
1416
type ReturnValueForOptionalValidator,
1517
} from "convex/server";
@@ -20,13 +22,19 @@ import type {
2022
EventSpec,
2123
OnCompleteArgs,
2224
WorkflowId,
25+
WorkflowStep,
2326
} from "../types.js";
2427
import { safeFunctionName } from "./safeFunctionName.js";
25-
import type { OpaqueIds, WorkflowComponent, WorkflowStep } from "./types.js";
28+
import type { OpaqueIds, WorkflowComponent, WorkflowCtx } from "./types.js";
2629
import { workflowMutation } from "./workflowMutation.js";
2730
import { parse } from "convex-helpers/validators";
2831

29-
export { vWorkflowId, type WorkflowId } from "../types.js";
32+
export {
33+
vWorkflowId,
34+
type WorkflowId,
35+
vWorkflowStep,
36+
type WorkflowStep,
37+
} from "../types.js";
3038
export type { RunOptions } from "./types.js";
3139
export { defineEvent } from "./events.js";
3240

@@ -67,7 +75,7 @@ export type WorkflowDefinition<
6775
> = {
6876
args?: ArgsValidator;
6977
handler: (
70-
step: WorkflowStep,
78+
step: WorkflowCtx,
7179
args: ObjectType<ArgsValidator>,
7280
) => Promise<ReturnValueForOptionalValidator<ReturnsValidator>>;
7381
returns?: ReturnsValidator;
@@ -201,6 +209,36 @@ export class WorkflowManager {
201209
});
202210
}
203211

212+
/**
213+
* List the steps in a workflow, including their name, args, return value etc.
214+
*
215+
* @param ctx - The Convex context from a query, mutation, or action.
216+
* @param workflowId - The workflow ID.
217+
* @param opts - How many steps to fetch and in what order.
218+
* e.g. `{ order: "desc", paginationOpts: { cursor: null, numItems: 10 } }`
219+
* will get the last 10 steps in descending order.
220+
* Defaults to 100 steps in ascending order.
221+
* @returns The pagination result with per-step data.
222+
*/
223+
async listSteps(
224+
ctx: RunQueryCtx,
225+
workflowId: WorkflowId,
226+
opts?: {
227+
order?: "asc" | "desc";
228+
paginationOpts?: PaginationOptions;
229+
},
230+
): Promise<PaginationResult<WorkflowStep>> {
231+
const steps = await ctx.runQuery(this.component.workflow.listSteps, {
232+
workflowId,
233+
order: opts?.order ?? "asc",
234+
paginationOpts: opts?.paginationOpts ?? {
235+
cursor: null,
236+
numItems: 100,
237+
},
238+
});
239+
return steps as PaginationResult<WorkflowStep>;
240+
}
241+
204242
/**
205243
* Clean up a completed workflow's storage.
206244
*

src/client/step.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type StepRequest = {
3636
}
3737
| {
3838
kind: "event";
39-
args: { eventId?: EventId<string> };
39+
args: { eventId?: EventId };
4040
}
4141
| {
4242
kind: "workflow";

src/client/stepContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import type {
88
import { safeFunctionName } from "./safeFunctionName.js";
99
import type { StepRequest } from "./step.js";
1010
import type { RetryOption } from "@convex-dev/workpool";
11-
import type { RunOptions, WorkflowStep } from "./types.js";
11+
import type { RunOptions, WorkflowCtx } from "./types.js";
1212
import type { EventSpec, WorkflowId } from "../types.js";
1313
import { parse } from "convex-helpers/validators";
1414

15-
export class StepContext implements WorkflowStep {
15+
export class StepContext implements WorkflowCtx {
1616
constructor(
1717
public workflowId: WorkflowId,
1818
private sender: BaseChannel<StepRequest>,

src/client/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
} from "convex/server";
88
import type { api } from "../component/_generated/api.js";
99
import type { GenericId } from "convex/values";
10-
import type { EventSpec, WorkflowId } from "../types.js";
10+
import type { EventId, EventSpec, WorkflowId } from "../types.js";
1111

1212
export type WorkflowComponent = UseApi<typeof api>;
1313

@@ -38,7 +38,7 @@ export type SchedulerOptions =
3838
runAfter?: number;
3939
};
4040

41-
export type WorkflowStep = {
41+
export type WorkflowCtx = {
4242
/**
4343
* The ID of the workflow currently running.
4444
*/
@@ -132,7 +132,7 @@ export type UseApi<API> = Expand<{
132132
export type OpaqueIds<T> =
133133
T extends GenericId<infer _T>
134134
? string
135-
: T extends WorkId
135+
: T extends WorkId | WorkflowId | EventId<any>
136136
? string
137137
: T extends (infer U)[]
138138
? OpaqueIds<U>[]

src/component/_generated/api.d.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,45 @@ export type Mounts = {
389389
};
390390
}
391391
>;
392+
listSteps: FunctionReference<
393+
"query",
394+
"public",
395+
{
396+
order: "asc" | "desc";
397+
paginationOpts: {
398+
cursor: string | null;
399+
endCursor?: string | null;
400+
id?: number;
401+
maximumBytesRead?: number;
402+
maximumRowsRead?: number;
403+
numItems: number;
404+
};
405+
workflowId: string;
406+
},
407+
{
408+
continueCursor: string;
409+
isDone: boolean;
410+
page: Array<{
411+
args: any;
412+
completedAt?: number;
413+
eventId?: string;
414+
kind: "function" | "workflow" | "event";
415+
name: string;
416+
nestedWorkflowId?: string;
417+
runResult?:
418+
| { kind: "success"; returnValue: any }
419+
| { error: string; kind: "failed" }
420+
| { kind: "canceled" };
421+
startedAt: number;
422+
stepId: string;
423+
stepNumber: number;
424+
workId?: string;
425+
workflowId: string;
426+
}>;
427+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
428+
splitCursor?: string | null;
429+
}
430+
>;
392431
};
393432
};
394433
// For now fullApiWithMounts is only fullApi which provides
@@ -422,6 +461,7 @@ export declare const components: {
422461
"internal",
423462
{
424463
before?: number;
464+
limit?: number;
425465
logLevel: "DEBUG" | "TRACE" | "INFO" | "REPORT" | "WARN" | "ERROR";
426466
},
427467
any

src/component/workflow.ts

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
11
import { vResultValidator } from "@convex-dev/workpool";
22
import { assert } from "convex-helpers";
3-
import type { FunctionHandle } from "convex/server";
3+
import {
4+
paginationOptsValidator,
5+
type FunctionHandle,
6+
type PaginationResult,
7+
} from "convex/server";
48
import { type Infer, v } from "convex/values";
59
import { mutation, type MutationCtx, query } from "./_generated/server.js";
610
import { type Logger, logLevel } from "./logging.js";
711
import { getWorkflow } from "./model.js";
812
import { getWorkpool } from "./pool.js";
9-
import { journalDocument, vOnComplete, workflowDocument } from "./schema.js";
13+
import schema, {
14+
journalDocument,
15+
vOnComplete,
16+
workflowDocument,
17+
type JournalEntry,
18+
} from "./schema.js";
1019
import { getDefaultLogger } from "./utils.js";
11-
import type { WorkflowId, OnCompleteArgs } from "../types.js";
20+
import {
21+
type WorkflowId,
22+
type OnCompleteArgs,
23+
type WorkflowStep,
24+
type EventId,
25+
vPaginationResult,
26+
vWorkflowStep,
27+
} from "../types.js";
1228
import { api, internal } from "./_generated/api.js";
1329
import { formatErrorWithStack } from "../shared.js";
1430
import type { SchedulerOptions } from "../client/types.js";
31+
import type { Id } from "./_generated/dataModel.js";
32+
import { paginator } from "convex-helpers/server/pagination";
1533

1634
const createArgs = v.object({
1735
workflowName: v.string(),
@@ -95,6 +113,60 @@ export const getStatus = query({
95113
},
96114
});
97115

116+
function publicWorkflowId(workflowId: Id<"workflows">): WorkflowId {
117+
return workflowId as any;
118+
}
119+
120+
function publicStep(step: JournalEntry): WorkflowStep {
121+
return {
122+
workflowId: publicWorkflowId(step.workflowId),
123+
name: step.step.name,
124+
stepId: step._id,
125+
stepNumber: step.stepNumber,
126+
127+
args: step.step.args,
128+
runResult: step.step.runResult,
129+
130+
startedAt: step.step.startedAt,
131+
completedAt: step.step.completedAt,
132+
133+
...(step.step.kind === "event"
134+
? {
135+
kind: "event",
136+
eventId: step.step.eventId as unknown as EventId,
137+
}
138+
: step.step.kind === "workflow"
139+
? {
140+
kind: "workflow",
141+
nestedWorkflowId: publicWorkflowId(step.step.workflowId!),
142+
}
143+
: {
144+
kind: "function",
145+
workId: step.step.workId!,
146+
}),
147+
} satisfies WorkflowStep;
148+
}
149+
150+
export const listSteps = query({
151+
args: {
152+
workflowId: v.id("workflows"),
153+
order: v.union(v.literal("asc"), v.literal("desc")),
154+
paginationOpts: paginationOptsValidator,
155+
},
156+
returns: vPaginationResult(vWorkflowStep),
157+
handler: async (ctx, args) => {
158+
const result = await paginator(ctx.db, schema)
159+
.query("steps")
160+
.withIndex("workflow", (q) => q.eq("workflowId", args.workflowId))
161+
.order(args.order)
162+
.paginate(args.paginationOpts);
163+
return {
164+
...result,
165+
page: result.page.map(publicStep),
166+
} as PaginationResult<Infer<typeof vWorkflowStep>>;
167+
},
168+
});
169+
98170
export const cancel = mutation({
99171
args: {
100172
workflowId: v.id("workflows"),

0 commit comments

Comments
 (0)