Skip to content
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

Implement automatic self-referential bindings via ctx.exports. #3447

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kentonv
Copy link
Member

@kentonv kentonv commented Feb 1, 2025

For every top-level export, ctx.exports will contain a corresponding property which acts as a binding to that export. If the export is a simple object or WorkerEntrypoint, then the ctx.exports property is a service binding. If the export is a Durable Object class (and it has been configured with storage), the ctx.exports property is a DO namespace binding.

So you can do: ctx.exports.MyEntrypoint.someRpc()

And it calls someRpc() on the entrypoint named MyEntrypoint, as if you had called it over a service binding.

This is marked experimental for now since it won't work on the edge until further changes are made to our control plane.

@kentonv kentonv requested review from a team as code owners February 1, 2025 00:02
@kentonv kentonv requested review from dom96 and hoodmane February 1, 2025 00:02
@kentonv
Copy link
Member Author

kentonv commented Feb 1, 2025

cc @geelen @irvinebroque

@kentonv kentonv force-pushed the kenton/ctx-exports branch from 3089e76 to e228c36 Compare February 1, 2025 00:06
Copy link

github-actions bot commented Feb 1, 2025

The generated output of @cloudflare/workers-types has been changed by this PR. If this is intentional, run bazel build //types && rm -rf types/generated-snapshot && cp -r bazel-bin/types/definitions types/generated-snapshot to update the snapshot. Alternatively, you can download the full generated types: https://github.com/cloudflare/workerd/actions/runs/13209840682/artifacts/2557262691

Full Type Diff
diff -r bazel-bin/types/definitions/experimental/index.d.ts types/generated-snapshot/experimental/index.d.ts
568d567
<   exports: any;
diff -r bazel-bin/types/definitions/experimental/index.ts types/generated-snapshot/experimental/index.ts
573d572
<   exports: any;

@geelen
Copy link
Contributor

geelen commented Feb 3, 2025

I think we need to extend the typedefs for WorkerEntrypoint and DurableObject and ExecutionContext:

type ExportBindings<T> = {
  [K in keyof T]: T[K] extends typeof DurableObject 
    ? DurableObjectNamespace<InstanceType<T[K]>>
    : T[K] extends typeof WorkerEntrypoint
      ? Service<InstanceType<T[K]>>
      : never;
  }

  export abstract class WorkerEntrypoint<Env = unknown, Exports extends Record<string, unknown>>
    implements Rpc.WorkerEntrypointBranded
  {
    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;
    protected ctx: ExecutionContext<ExportBindings<Exports>>;
    // ...

'tested' here

Similar change required for DurableObject base class

Usage:

import { WorkerEntrypoint, DurableObject } from "cloudflare:workers";

// These can be autogenerated by `wrangler types`
type Env = {}
type Exports = { MyEntrypoint, AnotherEntrypoint, MyActor, UnconfiguredActor }

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext<Exports>) {
    // ...
  }
}
export class MyEntrypoint extends WorkerEntrypoint<Env, Exports> {
  foo(i) { return `foo: ${i}` }
}
export class AnotherEntrypoint extends WorkerEntrypoint<Env, Exports> {
  bar(i) { return `bar: ${i}` }
}
export class MyActor extends DurableObject<Env, Exports> {
  setValue(i) { this.value = i; }
  baz() { return `baz: ${this.value}` }
}
export class UnconfiguredActor extends DurableObject<Env, Exports> {
  qux(i) { return `qux: ${i}` }
}

Can bump this to a second PR when we make this non-experimental, if you like? I just wanted to jot down the definition of ExportBindings<Exports> while I thought of it


Edit: I just realised we're going to need a type param for ctx.props too, in all three places. Would be nice to have a single argument so you can leave any of them unspecified like Hono: WorkerEntrypoint<{ Env, Exports, Props }>. I wonder how if we can do that without a breaking change :grimace:

@kentonv kentonv force-pushed the kenton/ctx-exports branch 2 times, most recently from f0d4f70 to bdae24f Compare February 5, 2025 22:00
@kentonv kentonv requested a review from a team as a code owner February 5, 2025 22:00
@kentonv kentonv requested a review from jasnell February 5, 2025 22:06
@kentonv
Copy link
Member Author

kentonv commented Feb 5, 2025

@jasnell Sorry to always assign you on everything but when I looked at the history of the files I'm modifying it's basically all me and you!

@kentonv kentonv force-pushed the kenton/ctx-exports branch from bdae24f to 02177f0 Compare February 6, 2025 17:48
@kentonv
Copy link
Member Author

kentonv commented Feb 6, 2025

(Added a test for ctx.exports.default.)

@kentonv
Copy link
Member Author

kentonv commented Feb 6, 2025

(Added commit to add ctx.exports to Durable Objects' ctx as well.)

@kentonv kentonv force-pushed the kenton/ctx-exports branch 2 times, most recently from fc4398e to 9de75d7 Compare February 6, 2025 21:42
For every top-level export, `ctx.exports` will contain a corresponding property which acts as a binding to that export. If the export is a simple object or `WorkerEntrypoint`, then the `ctx.exports` property is a service binding. If the export is a Durable Object class (and it has been configured with storage), the `ctx.exports` property is a DO namespace binding.

So you can do: `ctx.exports.MyEntrypoint.someRpc()`

And it calls `someRpc()` on the entrypoint named `MyEntrypoint`, as if you had called it over a service binding.

This is marked experimental for now since it won't work on the edge until further changes are made to our control plane.
@kentonv kentonv force-pushed the kenton/ctx-exports branch from 9de75d7 to 33cf70d Compare February 7, 2025 23:36
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.

3 participants