Skip to content

feat(cloudflare): bind Workflows to async Workers - #707

Merged
sam-goodwin merged 2 commits into
mainfrom
claude/happy-mirzakhani-bac482
Jun 29, 2026
Merged

feat(cloudflare): bind Workflows to async Workers#707
sam-goodwin merged 2 commits into
mainfrom
claude/happy-mirzakhani-bac482

Conversation

@sam-goodwin

Copy link
Copy Markdown
Contributor

Bind class-based Cloudflare Workflows to async (non-Effect) Workers via env, mirroring how class-based Durable Objects already work.

export const AsyncWorker = Cloudflare.Worker("Async", {
  main: "./src/worker.ts",
  env: {
    MY_WORKFLOW: Cloudflare.Workflow<{ value: string }>("MyWorkflow", {
      className: "MyWorkflow",
    }),
  },
});
export type AsyncWorkerEnv = Cloudflare.InferEnv<typeof AsyncWorker>;
// env.MY_WORKFLOW is typed as the native Workflow<{ value: string }>
// src/worker.ts
import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from "cloudflare:workers";

export class MyWorkflow extends WorkflowEntrypoint<AsyncWorkerEnv, { value: string }> {
  async run(event: Readonly<WorkflowEvent<{ value: string }>>, step: WorkflowStep) {
    return await step.do("greet", async () => `Hello, ${event.payload.value}!`);
  }
}

export default {
  async fetch(request: Request, env: AsyncWorkerEnv) {
    const instance = await env.MY_WORKFLOW.create({ params: { value: "world" } });
    return Response.json({ instanceId: instance.id });
  },
};

What changed

Added a props-only reference form Workflow(name, { className, scriptName }) that returns a plain WorkflowLike (parallel to DurableObjectLike), then wired it through every binding layer:

  • Workflow.tsWorkflowRefProps, WorkflowLike<Params>, isWorkflowLike guard, and the props-only runtime branch (Effect.isEffect disambiguates from the existing Effect-native form, which is unchanged).
  • WorkerBinding.tsWorkflowLike added to the WorkerBindingResource union so env accepts it.
  • WorkerAsyncBindings.ts — emits the workflow binding and creates a WorkflowResource (putWorkflow) for locally-hosted workflows. Cross-script scriptName refs are binding-only (host owns the resource), same as async DOs.
  • InferEnv.ts — maps WorkflowLike<Params> to the native Workflow<Params>.
  • Platform.ts — the onCreate hook's requirement channel is widened from never to any so it may yield child resources. The requirement is always discharged by the surrounding stack provider context.
- onCreate?: (resource: R, props: any) => Effect.Effect<void>;
+ onCreate?: (resource: R, props: any) => Effect.Effect<void, never, any>;

Tests

New fixture + live test: a plain async Worker hosting a WorkflowEntrypoint class, bound via env, starts an instance and polls to complete. Passes against real Cloudflare (ALCHEMY_PROFILE=testing).

Add a props-only reference form `Workflow(name, { className, scriptName })`
that returns a plain `WorkflowLike`, parallel to async Durable Objects, so a
class-based `WorkflowEntrypoint` can be bound to a non-Effect Worker via `env`:

```ts
export const AsyncWorker = Cloudflare.Worker("Async", {
  main: "./src/worker.ts",
  env: {
    MY_WORKFLOW: Cloudflare.Workflow<{ value: string }>("MyWorkflow", {
      className: "MyWorkflow",
    }),
  },
});
export type AsyncWorkerEnv = Cloudflare.InferEnv<typeof AsyncWorker>;
```

- WorkerAsyncBindings emits the `workflow` binding and creates a
  WorkflowResource (putWorkflow) for locally-hosted workflows; cross-script
  `scriptName` refs are binding-only.
- InferEnv maps `WorkflowLike<Params>` to the native `Workflow<Params>`.
- Platform.onCreate may now yield child resources (requirement discharged by
  the surrounding stack provider context).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alchemy-version-bot

alchemy-version-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

alchemy

bun add alchemy@https://pkg.ing/alchemy/67e1060

@alchemy.run/better-auth

bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/67e1060

@alchemy.run/pr-package

bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/67e1060

Add a `test.provider` case where a consumer Worker binds a Workflow hosted by
another Worker script via `scriptName` (reference-only) and drives create+get
across the cross-script reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sam-goodwin
sam-goodwin merged commit 9c7b823 into main Jun 29, 2026
9 checks passed
@sam-goodwin
sam-goodwin deleted the claude/happy-mirzakhani-bac482 branch June 29, 2026 22:07
DavidJFelix pushed a commit to DavidJFelix/alchemy-effect that referenced this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant