Skip to content

Commit c2ed7c2

Browse files
authored
Use capnweb native ReadableStream support for remote Media bindings (#12582)
1 parent e8fddc5 commit c2ed7c2

File tree

16 files changed

+192
-268
lines changed

16 files changed

+192
-268
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"miniflare": patch
3+
"wrangler": patch
4+
---
5+
6+
Internal refactor to use capnweb's native `ReadableStream` support to power remote Media and Dispatch Namespace bindings.

packages/miniflare/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"acorn": "8.14.0",
7979
"acorn-walk": "8.3.2",
8080
"capnp-es": "catalog:default",
81-
"capnweb": "^0.1.0",
81+
"capnweb": "catalog:default",
8282
"chokidar": "^4.0.1",
8383
"ci-info": "catalog:default",
8484
"concurrently": "^8.2.2",

packages/miniflare/scripts/build.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ const embedWorkersPlugin = {
174174
if (builder === undefined) {
175175
builder = await esbuild.context({
176176
platform: "node", // Marks `node:*` imports as external
177+
conditions: ["workerd", "worker", "browser"],
177178
format: "esm",
178179
target: "esnext",
179180
bundle: true,
@@ -186,7 +187,7 @@ const embedWorkersPlugin = {
186187
minifySyntax: true,
187188
outdir: build.initialOptions.outdir,
188189
outbase: pkgRoot,
189-
// Apply the node-to-internal rewrite only for shared extension workers
190+
// Shared extension workers need node:* → node-internal:*
190191
plugins:
191192
args.path === miniflareSharedExtensionPath ||
192193
args.path === miniflareZodExtensionPath

packages/miniflare/src/plugins/dispatch-namespace/index.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import LOCAL_DISPATCH_NAMESPACE from "worker:dispatch-namespace/dispatch-namespace";
1+
import SCRIPT_DISPATCH_NAMESPACE from "worker:dispatch-namespace/dispatch-namespace";
2+
import SCRIPT_DISPATCH_NAMESPACE_PROXY from "worker:dispatch-namespace/dispatch-namespace-proxy";
23
import { z } from "zod";
34
import { Worker_Binding } from "../../runtime";
45
import {
6+
getUserBindingServiceName,
57
Plugin,
68
ProxyNodeBinding,
9+
remoteProxyClientWorker,
710
RemoteProxyConnectionString,
811
} from "../shared";
912

@@ -22,6 +25,18 @@ export const DispatchNamespaceOptionsSchema = z.object({
2225

2326
export const DISPATCH_NAMESPACE_PLUGIN_NAME = "dispatch-namespace";
2427

28+
/** Service name for the proxy client worker backing a dispatch namespace. */
29+
function getProxyServiceName(
30+
name: string,
31+
remoteProxyConnectionString?: RemoteProxyConnectionString
32+
): string {
33+
return getUserBindingServiceName(
34+
`${DISPATCH_NAMESPACE_PLUGIN_NAME}-proxy`,
35+
name,
36+
remoteProxyConnectionString
37+
);
38+
}
39+
2540
export const DISPATCH_NAMESPACE_PLUGIN: Plugin<
2641
typeof DispatchNamespaceOptionsSchema
2742
> = {
@@ -39,17 +54,14 @@ export const DISPATCH_NAMESPACE_PLUGIN: Plugin<
3954
wrapped: {
4055
moduleName: `${DISPATCH_NAMESPACE_PLUGIN_NAME}:local-dispatch-namespace`,
4156
innerBindings: [
42-
...(config.remoteProxyConnectionString?.href
43-
? [
44-
{
45-
name: "remoteProxyConnectionString",
46-
text: config.remoteProxyConnectionString.href,
47-
},
48-
]
49-
: []),
5057
{
51-
name: "binding",
52-
text: name,
58+
name: "proxyClient",
59+
service: {
60+
name: getProxyServiceName(
61+
name,
62+
config.remoteProxyConnectionString
63+
),
64+
},
5365
},
5466
],
5567
},
@@ -68,8 +80,20 @@ export const DISPATCH_NAMESPACE_PLUGIN: Plugin<
6880
])
6981
);
7082
},
71-
async getServices() {
72-
return [];
83+
async getServices({ options }) {
84+
if (!options.dispatchNamespaces) {
85+
return [];
86+
}
87+
88+
return Object.entries(options.dispatchNamespaces).map(([name, config]) => ({
89+
name: getProxyServiceName(name, config.remoteProxyConnectionString),
90+
worker: remoteProxyClientWorker(
91+
config.remoteProxyConnectionString,
92+
name,
93+
undefined,
94+
SCRIPT_DISPATCH_NAMESPACE_PROXY
95+
),
96+
}));
7397
},
7498
getExtensions({ options }) {
7599
if (!options.some((o) => o.dispatchNamespaces)) {
@@ -81,7 +105,7 @@ export const DISPATCH_NAMESPACE_PLUGIN: Plugin<
81105
modules: [
82106
{
83107
name: `${DISPATCH_NAMESPACE_PLUGIN_NAME}:local-dispatch-namespace`,
84-
esModule: LOCAL_DISPATCH_NAMESPACE(),
108+
esModule: SCRIPT_DISPATCH_NAMESPACE(),
85109
internal: true,
86110
},
87111
],

packages/miniflare/src/plugins/media/index.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import BINDING from "worker:media/binding";
21
import { z } from "zod";
32
import {
43
getUserBindingServiceName,
@@ -61,34 +60,6 @@ export const MEDIA_PLUGIN: Plugin<typeof MediaOptionsSchema> = {
6160
options.media.binding,
6261
options.media.remoteProxyConnectionString
6362
),
64-
worker: {
65-
compatibilityDate: "2025-01-01",
66-
modules: [
67-
{
68-
name: "index.worker.js",
69-
esModule: BINDING(),
70-
},
71-
],
72-
bindings: [
73-
{
74-
name: "remote",
75-
service: {
76-
name: getUserBindingServiceName(
77-
`${MEDIA_PLUGIN_NAME}:remote`,
78-
options.media.binding,
79-
options.media.remoteProxyConnectionString
80-
),
81-
},
82-
},
83-
],
84-
},
85-
},
86-
{
87-
name: getUserBindingServiceName(
88-
`${MEDIA_PLUGIN_NAME}:remote`,
89-
options.media.binding,
90-
options.media.remoteProxyConnectionString
91-
),
9263
worker: remoteProxyClientWorker(
9364
options.media.remoteProxyConnectionString,
9465
options.media.binding

packages/miniflare/src/plugins/shared/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,15 @@ export function objectEntryWorker(
7676
export function remoteProxyClientWorker(
7777
remoteProxyConnectionString: RemoteProxyConnectionString | undefined,
7878
binding: string,
79-
bindingType?: string
79+
bindingType?: string,
80+
script?: () => string
8081
) {
8182
return {
8283
compatibilityDate: "2025-01-01",
8384
modules: [
8485
{
8586
name: "index.worker.js",
86-
esModule: SCRIPT_REMOTE_PROXY_CLIENT(),
87+
esModule: (script ?? SCRIPT_REMOTE_PROXY_CLIENT)(),
8788
},
8889
],
8990
bindings: [
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { WorkerEntrypoint } from "cloudflare:workers";
2+
import {
3+
makeRemoteProxyStub,
4+
RemoteBindingEnv,
5+
throwRemoteRequired,
6+
} from "../shared/remote-bindings-utils";
7+
8+
/** Proxy client for dispatch namespace bindings. */
9+
export default class DispatchNamespaceProxy extends WorkerEntrypoint<RemoteBindingEnv> {
10+
get(
11+
name: string,
12+
args?: { [key: string]: unknown },
13+
options?: DynamicDispatchOptions
14+
): Fetcher {
15+
if (!this.env.remoteProxyConnectionString) {
16+
throwRemoteRequired(this.env.binding);
17+
}
18+
return makeRemoteProxyStub(
19+
this.env.remoteProxyConnectionString,
20+
this.env.binding,
21+
{
22+
"MF-Dispatch-Namespace-Options": JSON.stringify({
23+
name,
24+
args,
25+
options,
26+
}),
27+
}
28+
);
29+
}
30+
}
Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,21 @@
1-
import { newWebSocketRpcSession } from "capnweb";
2-
import { makeFetch } from "../shared/remote-bindings-utils";
1+
/**
2+
* Wrapped binding extension for dispatch namespaces.
3+
*
4+
* Delegates to {@link DispatchNamespaceProxy} via a service binding.
5+
*/
36

47
interface Env {
5-
remoteProxyConnectionString: string | undefined;
6-
binding: string;
8+
proxyClient: DispatchNamespace;
79
}
810

9-
export default function (env: Env) {
11+
export default function (env: Env): DispatchNamespace {
1012
return {
1113
get(
1214
name: string,
13-
args?: { [key: string]: any },
15+
args?: { [key: string]: unknown },
1416
options?: DynamicDispatchOptions
1517
): Fetcher {
16-
if (!env.remoteProxyConnectionString) {
17-
throw new Error(`Binding ${env.binding} needs to be run remotely`);
18-
}
19-
const url = new URL(env.remoteProxyConnectionString);
20-
url.protocol = "ws:";
21-
url.searchParams.set("MF-Binding", env.binding);
22-
url.searchParams.set(
23-
"MF-Dispatch-Namespace-Options",
24-
JSON.stringify({ name, args, options })
25-
);
26-
27-
type ProxiedService = Omit<Service, "connect" | "fetch"> & {
28-
fetch: typeof fetch;
29-
connect: never;
30-
};
31-
const stub = newWebSocketRpcSession<ProxiedService>(url.href);
32-
33-
return new Proxy<ProxiedService>(stub, {
34-
get(_, p) {
35-
// We don't want to wrap direct .fetch() calls on a customer worker in a JSRPC layer
36-
// Instead, intercept accesses to the specific `fetch` key, and send them directly
37-
if (p === "fetch") {
38-
return makeFetch(
39-
env.remoteProxyConnectionString,
40-
env.binding,
41-
new Headers({
42-
"MF-Dispatch-Namespace-Options": JSON.stringify({
43-
name,
44-
args,
45-
options,
46-
}),
47-
})
48-
);
49-
}
50-
51-
return Reflect.get(stub, p);
52-
},
53-
});
18+
return env.proxyClient.get(name, args, options);
5419
},
5520
} satisfies DispatchNamespace;
5621
}

packages/miniflare/src/workers/media/binding.worker.ts

Lines changed: 0 additions & 104 deletions
This file was deleted.

0 commit comments

Comments
 (0)