Skip to content

Conversation

@penalosa
Copy link
Contributor

@penalosa penalosa commented Aug 6, 2025

Fixes https://jira.cfdata.org/browse/DEVX-2042, https://jira.cfdata.org/browse/DEVX-1929, https://jira.cfdata.org/browse/DEVX-1933

Support JSRPC bindings! This uses a vendored version of the upcoming @cloudflare/jsrpc library. See code for detailed comments.


  • Tests
    • Tests included
    • Tests not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: captured by separate ticket
  • Wrangler V3 Backport
    • Wrangler PR:
    • Not necessary because: not a V3 feature
@changeset-bot
Copy link

changeset-bot bot commented Aug 6, 2025

🦋 Changeset detected

Latest commit: ccbb30d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
miniflare Patch
wrangler Patch
@cloudflare/pages-shared Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines 29 to 34
constructor(ctx: ExecutionContext, env: Env) {
const url = new URL(env.remoteProxyConnectionString);
url.protocol = "ws:";
url.searchParams.set("MF-Binding", env.binding);
const session = rpcOverWebSocket(url.href);
const stub = session.getStub();

super(ctx, env);

return new Proxy(this, {
get(target, prop) {
if (Reflect.has(target, prop)) {
return Reflect.get(target, prop);
}

const value = Reflect.get(stub, prop);
return value;
},
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proxy code is inspired by that written for rpc in local dev with the router worker—thanks @CarmenPopoviciu!

@penalosa penalosa force-pushed the penalosa/remote-bindings-rpc branch from f729c29 to 70ce62e Compare August 6, 2025 16:32
Copy link
Member

@dario-piotrowicz dario-piotrowicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me! 😄

I just left a few comments (some just asking info so that I can understand the implementation better 🙂)


However one thing I'd like to ask however is, I can see you'd chosen to use rpcOverWebSocket, have you considered generally using rpcOverHttp instead?
It would likely be slightly slower and more noisy but since we're not talking about something that needs to handle live traffic that could be acceptable. Using non-persistent http could help us avoid persistence issues that can happen for different reasons (such as sessions restarts, weird remote preview bugs and vite environments reloads).

I am by no means saying that I think rpcOverHttp would be more appropriate/better, I'm just curious if you've considered or tested using that instead of the permanent websocket connection 🙂

@penalosa
Copy link
Contributor Author

have you considered generally using rpcOverHttp instead

I was hoping to be able to use that, yes! Unfortunately it requires an explicit batch.send() operation to actually send things over the wire, which is a non-starter for us (since we don't control the code interacting with the RPC stub and don't know when it's appropriate to initiate that)

@dario-piotrowicz
Copy link
Member

since we don't control the code interacting with the RPC stub and don't know when it's appropriate to initiate that

Couldn't we do that as part of the proxy objects? 🤔 as in, when we return a function in a Proxy getter, we could wrap that function so that after its execution it runs batch.send() no?

For example something like:

new Proxy(this, { get(target, prop) { if (Reflect.has(target, prop)) { return Reflect.get(target, prop); } const stubProp = Reflect.get(stub, prop); if (typeof stubProp === 'function') { return async (...args) => { const resPromise = stubProp(...args); batch.send(); return resPromise; }; } return stubProp; } });
@penalosa
Copy link
Contributor Author

Couldn't we do that as part of the proxy objects? 🤔 as in, when we return a function in a Proxy getter, we could wrap that function so that after its execution it runs batch.send() no?

There are lots of edge cases here:

  • What if the value is a getter? When would we send the batch?
  • What if the function returns an RpcTarget that the user then interacts with further? We'd have to do another batch.send() but there wouldn't be a natural place to do this
@petebacondarwin petebacondarwin self-assigned this Aug 12, 2025
@dario-piotrowicz
Copy link
Member

Couldn't we do that as part of the proxy objects? 🤔 as in, when we return a function in a Proxy getter, we could wrap that function so that after its execution it runs batch.send() no?

There are lots of edge cases here:

  • What if the value is a getter? When would we send the batch?
  • What if the function returns an RpcTarget that the user then interacts with further? We'd have to do another batch.send() but there wouldn't be a natural place to do this

Yeah good points 🤔👍

@penalosa penalosa added the blocked Blocked on other work label Aug 14, 2025
@penalosa penalosa force-pushed the penalosa/remote-bindings-rpc branch from a0bfd7b to 76f2038 Compare August 14, 2025 18:14
@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 14, 2025

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@10249 

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@10249 

miniflare

npm i https://pkg.pr.new/miniflare@10249 

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@10249 

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@10249 

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@10249 

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@10249 

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@10249 

wrangler

npm i https://pkg.pr.new/wrangler@10249 

commit: 357e32f

@penalosa penalosa marked this pull request as ready for review August 14, 2025 18:18
@penalosa penalosa requested a review from a team as a code owner August 14, 2025 18:18
@github-actions

This comment was marked as off-topic.

@penalosa penalosa added the skip-v3-pr Skip validation of presence of a v3 backport PR label Aug 14, 2025
@penalosa penalosa requested a review from a team as a code owner August 14, 2025 19:25
Create yellow-kangaroos-bathe.md fix checks fix checks Increase timeouts more logging static workers static workers pre-compile proxy server worker
@penalosa penalosa force-pushed the penalosa/remote-bindings-rpc branch from 87b55c2 to 5bab92a Compare August 15, 2025 12:20
sourcemap: process.env.SOURCEMAPS !== "false",
sourcesContent: false,
metafile: true,
external: ["cloudflare:email", "cloudflare:workers"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: this can take regular expressions. So maybe?

Suggested change
external: ["cloudflare:email", "cloudflare:workers"],
external: /^cloudflare:/,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's type seems to be BuildOptions.external?: string[] | undefined?

@github-project-automation github-project-automation bot moved this from Untriaged to Approved in workers-sdk Aug 15, 2025
"@cloudflare/cli": "workspace:*",
"@cloudflare/containers-shared": "workspace:*",
"@cloudflare/eslint-config-worker": "workspace:*",
"@cloudflare/jsrpc": "link:../../vendor/jsrpc",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, wouldn't it be cleaner adding `"vendor/*" directory to the packages list in "pnpm-workspace.yaml" and treat jsrpc as any other workspace package? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially? But I wanted to draw a clear difference between this and other packages—e.g. we're not running JSRPC's tests, and we're just including the built output.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the benefit of treating it as a workspace package be?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just wondering it that'd be cleaner, pnpm could also optimize something... but it is not a huge deal I'm happy with the current approach I was just wondering 🙂

@penalosa penalosa removed the blocked Blocked on other work label Aug 15, 2025
Copy link
Member

@dario-piotrowicz dario-piotrowicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing stuff! 😄 🚀

@penalosa penalosa merged commit 875197a into main Aug 15, 2025
54 of 60 checks passed
@penalosa penalosa deleted the penalosa/remote-bindings-rpc branch August 15, 2025 17:20
@github-project-automation github-project-automation bot moved this from Approved to Done in workers-sdk Aug 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-v3-pr Skip validation of presence of a v3 backport PR

3 participants