Last active
October 30, 2024 21:13
-
-
Save jahands/d03646e0365d3b578d0aa528f0f3590e to your computer and use it in GitHub Desktop.
Workflows auto capture to Sentry inside step.do() callback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { NonRetryableError } from 'cloudflare:workflows' | |
import { initWorkflowSentry } from '../helpers/sentry' | |
import type { WorkflowEvent, WorkflowStep, WorkflowStepConfig } from 'cloudflare:workers' | |
import type { Toucan } from 'toucan-js' | |
import type { Bindings } from '../types' | |
/** Workflow context (similar to Hono context) */ | |
export class WorkflowContext<Params = unknown> { | |
readonly ctx: ExecutionContext | |
readonly env: Bindings | |
readonly event: WorkflowEvent<Params> | |
readonly step: WorkflowStep | |
readonly sentry: Toucan | |
// TODO: Add logger | |
constructor(c: { | |
ctx: ExecutionContext | |
env: Bindings | |
event: WorkflowEvent<Params> | |
step: WorkflowStep | |
}) { | |
this.ctx = c.ctx | |
this.env = c.env | |
this.event = c.event | |
this.step = c.step | |
this.sentry = initWorkflowSentry(c.env, c.ctx) | |
} | |
/** | |
* Run a Workflows Step. | |
* | |
* Callers should avoid capturing `StepFailedError` and `NonRetryableError` | |
* because these errors are already captured to Sentry within this method. | |
*/ | |
do<T extends Rpc.Serializable<T>>(name: string, callback: () => Promise<T>): Promise<T> | |
do<T extends Rpc.Serializable<T>>( | |
name: string, | |
config: WorkflowStepConfig, | |
callback: () => Promise<T> | |
): Promise<T> | |
async do<T extends Rpc.Serializable<T>>( | |
name: string, | |
configOrCallback: WorkflowStepConfig | (() => Promise<T>), | |
callback?: () => Promise<T> | |
): Promise<T> { | |
let config: WorkflowStepConfig | |
let cb: () => Promise<T> | |
if (typeof configOrCallback === 'function') { | |
cb = configOrCallback | |
config = {} // default to empty | |
} else { | |
if (!callback) { | |
throw new Error('missing callback') | |
} | |
config = configOrCallback | |
cb = callback | |
} | |
return await this.step.do(name, config, async () => { | |
try { | |
return await cb() | |
} catch (e) { | |
this.sentry.captureException(e) | |
if (e instanceof NonRetryableError) { | |
throw e | |
} else { | |
throw new StepFailedError(e instanceof Error ? `${e.name}: ${e.message}` : 'unknown') | |
} | |
} | |
}) | |
} | |
} | |
/** | |
* StepFailedError that should be thrown within | |
* do() when we don't want to capture to Sentry | |
*/ | |
export class StepFailedError extends Error {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { MyWorkflowParams } from './workflows/my-workflow/my-workflow' | |
export type Bindings = { | |
MyWorkflow: Workflow<MyWorkflowParams> | |
KV: KVNamespace | |
} | |
// type-safe version of Workflow binding type (wanted typed params) | |
interface Workflow<Params = unknown> { | |
/** | |
* Get a handle to an existing instance of the Workflow. | |
* @param id Id for the instance of this Workflow | |
* @returns A promise that resolves with a handle for the Instance | |
*/ | |
get(id: string): Promise<WorkflowInstance> | |
/** | |
* Create a new instance and return a handle to it. If a provided id exists, an error will be thrown. | |
* @param options Options when creating an instance including id and params | |
* @returns A promise that resolves with a handle for the Instance | |
*/ | |
create(options?: WorkflowInstanceCreateOptions<Params>): Promise<WorkflowInstance> | |
} | |
interface WorkflowInstanceCreateOptions<Params> { | |
/** | |
* An id for your Workflow instance. Must be unique within the Workflow. | |
*/ | |
id?: string | |
/** | |
* The event payload the Workflow instance is triggered with | |
*/ | |
params?: Params | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { WorkflowEntrypoint } from 'cloudflare:workers' | |
import { NonRetryableError } from 'cloudflare:workflows' | |
import { z } from 'zod' | |
import { StepFailedError, WorkflowContext } from '../context' | |
import type { WorkflowEvent, WorkflowStep } from 'cloudflare:workers' | |
import type { Bindings } from '../../types' | |
export type MyWorkflowParams = z.infer<typeof MyWorkflowParams> | |
export const MyWorkflowParams = z.object({ | |
someParam: z.string(), | |
}) | |
export class MyWorkflow extends WorkflowEntrypoint<Bindings, MyWorkflowParams> { | |
async run(event: WorkflowEvent<MyWorkflowParams>, step: WorkflowStep) { | |
const c = new WorkflowContext({ | |
ctx: this.ctx, | |
env: this.env, | |
event, | |
step, | |
}) | |
try { | |
const params = MyWorkflowParams.parse(event.payload) | |
await c.do('some action that may throw error', async () => { | |
console.log(params.someParam) | |
// Do something that may fail | |
}) | |
} catch (e) { | |
// Step errors are already recorded within step.do() | |
if (!(e instanceof StepFailedError) && !(e instanceof NonRetryableError)) { | |
c.sentry.captureException(e) | |
} | |
throw e | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment