-
Notifications
You must be signed in to change notification settings - Fork 158
fix: add missing workflow fucntionality for queues #4165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import { Loop } from "@rivetkit/workflow-engine"; | ||
| import { actor } from "@/actor/mod"; | ||
| import { db } from "@/db/mod"; | ||
| import { WORKFLOW_GUARD_KV_KEY } from "@/workflow/constants"; | ||
| import { workflow, workflowQueueName } from "@/workflow/mod"; | ||
| import type { registry } from "./registry"; | ||
|
Comment on lines
+3
to
+6
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imports are not sorted alphabetically. Biome linter typically requires imports to be sorted. Reorder these imports alphabetically. Spotted by Graphite Agent (based on CI logs) |
||
|
|
||
| const WORKFLOW_QUEUE_NAME = "workflow-default"; | ||
|
|
||
|
|
@@ -55,19 +57,95 @@ export const workflowQueueActor = actor({ | |
| name: "queue", | ||
| run: async (loopCtx) => { | ||
| const actorLoopCtx = loopCtx as any; | ||
| const payload = await loopCtx.listen( | ||
| const message = await loopCtx.listen( | ||
| "queue-wait", | ||
| WORKFLOW_QUEUE_NAME, | ||
| ); | ||
| await loopCtx.step("store-message", async () => { | ||
| actorLoopCtx.state.received.push(payload); | ||
| actorLoopCtx.state.received.push(message.body); | ||
| await message.complete({ echo: message.body }); | ||
| }); | ||
| return Loop.continue(undefined); | ||
| }, | ||
| }); | ||
| }), | ||
| actions: { | ||
| getMessages: (c) => c.state.received, | ||
| sendAndWait: async (c, payload: unknown) => { | ||
| const client = c.client<typeof registry>(); | ||
| const handle = client.workflowQueueActor.getForId(c.actorId); | ||
| return await handle.queue[workflowQueueName(WORKFLOW_QUEUE_NAME)].send( | ||
| payload, | ||
| { wait: true, timeout: 1_000 }, | ||
| ); | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| export const workflowAccessActor = actor({ | ||
| db: db({ | ||
| onMigrate: async (rawDb) => { | ||
| await rawDb.execute(` | ||
| CREATE TABLE IF NOT EXISTS workflow_access_log ( | ||
| id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| created_at INTEGER NOT NULL | ||
| ) | ||
| `); | ||
| }, | ||
| }), | ||
| state: { | ||
| outsideDbError: null as string | null, | ||
| outsideClientError: null as string | null, | ||
| insideDbCount: 0, | ||
| insideClientAvailable: false, | ||
| }, | ||
| run: workflow(async (ctx) => { | ||
| await ctx.loop({ | ||
| name: "access", | ||
| run: async (loopCtx) => { | ||
| const actorLoopCtx = loopCtx as any; | ||
| let outsideDbError: string | null = null; | ||
| let outsideClientError: string | null = null; | ||
|
|
||
| try { | ||
| // Accessing db outside a step should throw. | ||
| // biome-ignore lint/style/noUnusedExpressions: intentionally checking accessor. | ||
| actorLoopCtx.db; | ||
| } catch (error) { | ||
| outsideDbError = | ||
| error instanceof Error ? error.message : String(error); | ||
| } | ||
|
|
||
| try { | ||
| actorLoopCtx.client<typeof registry>(); | ||
| } catch (error) { | ||
| outsideClientError = | ||
| error instanceof Error ? error.message : String(error); | ||
| } | ||
|
|
||
| await loopCtx.step("access-step", async () => { | ||
| await actorLoopCtx.db.execute( | ||
| `INSERT INTO workflow_access_log (created_at) VALUES (${Date.now()})`, | ||
| ); | ||
| const counts = (await actorLoopCtx.db.execute( | ||
| `SELECT COUNT(*) as count FROM workflow_access_log`, | ||
| )) as Array<{ count: number }>; | ||
| const client = actorLoopCtx.client<typeof registry>(); | ||
|
|
||
| actorLoopCtx.state.outsideDbError = outsideDbError; | ||
| actorLoopCtx.state.outsideClientError = outsideClientError; | ||
| actorLoopCtx.state.insideDbCount = counts[0]?.count ?? 0; | ||
| actorLoopCtx.state.insideClientAvailable = | ||
| typeof client.workflowQueueActor.getForId === "function"; | ||
| }); | ||
|
|
||
| await loopCtx.sleep("idle", 25); | ||
| return Loop.continue(undefined); | ||
| }, | ||
| }); | ||
| }), | ||
| actions: { | ||
| getState: (c) => c.state, | ||
| }, | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| import type { RunContext } from "@/actor/contexts/run"; | ||
| import type { AnyDatabaseProvider } from "@/actor/database"; | ||
| import type { Client } from "@/client/client"; | ||
| import type { Registry } from "@/registry"; | ||
| import type { AnyDatabaseProvider, InferDatabaseClient } from "@/actor/database"; | ||
|
Comment on lines
+2
to
+4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imports are not sorted alphabetically. Biome linter typically requires imports to be sorted. Reorder these imports alphabetically. Spotted by Graphite Agent (based on CI logs) |
||
| import type { WorkflowContextInterface } from "@rivetkit/workflow-engine"; | ||
| import type { | ||
| BranchConfig, | ||
|
|
@@ -8,6 +10,7 @@ import type { | |
| LoopConfig, | ||
| LoopResult, | ||
| StepConfig, | ||
| WorkflowListenMessage, | ||
| } from "@rivetkit/workflow-engine"; | ||
| import { WORKFLOW_GUARD_KV_KEY } from "./constants"; | ||
|
|
||
|
|
@@ -42,27 +45,27 @@ export class ActorWorkflowContext< | |
| return this.#inner.abortSignal; | ||
| } | ||
|
|
||
| async step<T>( | ||
| nameOrConfig: string | Parameters<WorkflowContextInterface["step"]>[0], | ||
| run?: () => Promise<T>, | ||
| ): Promise<T> { | ||
| async step<T>( | ||
| nameOrConfig: string | Parameters<WorkflowContextInterface["step"]>[0], | ||
| run?: () => Promise<T>, | ||
| ): Promise<T> { | ||
| if (typeof nameOrConfig === "string") { | ||
| if (!run) { | ||
| throw new Error("Step run function missing"); | ||
| } | ||
| return await this.#wrapActive(() => | ||
| this.#inner.step(nameOrConfig, () => | ||
| this.#withActorAccess(run), | ||
| ), | ||
| ); | ||
| } | ||
| return await this.#wrapActive(() => | ||
| this.#inner.step(nameOrConfig, () => | ||
| this.#withActorAccess(run), | ||
| ), | ||
| ); | ||
| } | ||
|
Comment on lines
+48
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The step method is incorrectly indented. Remove the extra indentation at the beginning of the method definition to align it with other class methods. Spotted by Graphite Agent (based on CI logs)
Comment on lines
+48
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation is inconsistent in the step method implementation. The return statement and its content are indented more than they should be. Fix the indentation to match the surrounding code structure. Spotted by Graphite Agent (based on CI logs) |
||
| const stepConfig = nameOrConfig as StepConfig<T>; | ||
| const config: StepConfig<T> = { | ||
| ...stepConfig, | ||
| run: () => this.#withActorAccess(stepConfig.run), | ||
| }; | ||
| return await this.#wrapActive(() => this.#inner.step(config)); | ||
| } | ||
| } | ||
|
|
||
| async loop<T>( | ||
| name: string, | ||
|
|
@@ -103,7 +106,10 @@ export class ActorWorkflowContext< | |
| return this.#inner.sleepUntil(name, timestampMs); | ||
| } | ||
|
|
||
| listen<T>(name: string, messageName: string): Promise<T> { | ||
| listen<T>( | ||
| name: string, | ||
| messageName: string | string[], | ||
| ): Promise<WorkflowListenMessage<T>> { | ||
| return this.#inner.listen(name, messageName); | ||
| } | ||
|
|
||
|
|
@@ -212,6 +218,18 @@ export class ActorWorkflowContext< | |
| return this.#runCtx.vars as TVars extends never ? never : TVars; | ||
| } | ||
|
|
||
| client<R extends Registry<any>>(): Client<R> { | ||
| this.#ensureActorAccess("client"); | ||
| return this.#runCtx.client<R>(); | ||
| } | ||
|
|
||
| get db(): TDatabase extends never ? never : InferDatabaseClient<TDatabase> { | ||
| this.#ensureActorAccess("db"); | ||
| return this.#runCtx.db as TDatabase extends never | ||
| ? never | ||
| : InferDatabaseClient<TDatabase>; | ||
| } | ||
|
|
||
| get log() { | ||
| return this.#runCtx.log; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The imports are not sorted according to Biome's rules. They should be sorted alphabetically. Run 'biome check --apply' to automatically fix the import sorting.
Spotted by Graphite Agent (based on CI logs)

Is this helpful? React 👍 or 👎 to let us know.