diff --git a/.changeset/polite-dodos-happen.md b/.changeset/polite-dodos-happen.md new file mode 100644 index 000000000000..ae9f81d8a05d --- /dev/null +++ b/.changeset/polite-dodos-happen.md @@ -0,0 +1,9 @@ +--- +"miniflare": patch +--- + +fix: fix the magic proxy no longer disposing of the `dispose` and `asyncDispose` symbol properties of `RpcProperty`s + +Fix the fact that from https://github.com/cloudflare/workers-sdk/pull/5670 the magic proxy no longer disposes the +`dispose` and `asyncDispose` properties found on `RpcProperty` objects causing serialization issues when trying +to proxy plain objects diff --git a/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts b/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts index 3d58b938f955..4bbfabdd5158 100644 --- a/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts +++ b/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts @@ -188,9 +188,15 @@ describe("getPlatformProxy - env", () => { rpc = env.MY_RPC as unknown as EntrypointService; return dispose; }); - it("can call RPC methods directly", async () => { + it("can call RPC methods returning a string", async () => { expect(await rpc.sum([1, 2, 3])).toMatchInlineSnapshot(`6`); }); + it("can call RPC methods returning an object", async () => { + expect(await rpc.sumObj([1, 2, 3, 5])).toEqual({ + isObject: true, + value: 11, + }); + }); it("can call RPC methods returning a Response", async () => { const resp = await rpc.asJsonResponse([1, 2, 3]); expect(resp.status).toMatchInlineSnapshot(`200`); diff --git a/fixtures/get-platform-proxy/workers/rpc-worker/index.ts b/fixtures/get-platform-proxy/workers/rpc-worker/index.ts index 540f908c63ac..0d284d9fed94 100644 --- a/fixtures/get-platform-proxy/workers/rpc-worker/index.ts +++ b/fixtures/get-platform-proxy/workers/rpc-worker/index.ts @@ -12,6 +12,14 @@ export class NamedEntrypoint extends WorkerEntrypoint { sum(args: number[]): number { return args.reduce((a, b) => a + b); } + + sumObj(args: number[]): { isObject: true; value: number } { + return { + isObject: true, + value: args.reduce((a, b) => a + b), + }; + } + asJsonResponse(args: unknown): { status: number; text: () => Promise; diff --git a/packages/miniflare/src/workers/core/proxy.worker.ts b/packages/miniflare/src/workers/core/proxy.worker.ts index ae400da5ebee..aa6a26797d48 100644 --- a/packages/miniflare/src/workers/core/proxy.worker.ts +++ b/packages/miniflare/src/workers/core/proxy.worker.ts @@ -289,7 +289,22 @@ export class ProxyServer implements DurableObject { } assert(Array.isArray(args)); try { - if (["RpcProperty", "RpcStub"].includes(func.constructor.name)) { + if (func.constructor.name === "RpcProperty") { + result = func(...args); + // Wrap RpcPromise instances with a standard promise to support serialisation + result = new Promise((resolve, reject) => + (result as Promise) + .then((v) => { + // Drop `Symbol.dispose` and `Symbol.asyncDispose` so that the resulting value can be serialised. + if (v.hasOwnProperty(Symbol.dispose)) delete v[Symbol.dispose]; + if (v.hasOwnProperty(Symbol.asyncDispose)) + delete v[Symbol.asyncDispose]; + + resolve(v); + }) + .catch(reject) + ); + } else if (func.constructor.name === "RpcStub") { // let's resolve RpcPromise instances right away (to support serialization) result = await func(...args); } else { diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 302c55cff131..e4e86b4261ee 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -821,6 +821,53 @@ test("Miniflare: service binding to named entrypoint", async (t) => { }); }); +test("Miniflare: service binding to named entrypoint that implement a method returning a plain object", async (t) => { + const mf = new Miniflare({ + workers: [ + { + name: "a", + serviceBindings: { + RPC_SERVICE: { name: "b", entrypoint: "RpcEntrypoint" }, + }, + compatibilityFlags: ["rpc"], + modules: true, + script: ` + export default { + async fetch(request, env) { + const obj = await env.RPC_SERVICE.getObject(); + return Response.json({ obj }); + } + } + `, + }, + { + name: "b", + modules: true, + script: ` + import { WorkerEntrypoint } from "cloudflare:workers"; + + export class RpcEntrypoint extends WorkerEntrypoint { + getObject() { + return { + isPlainObject: true, + value: 123, + } + } + } + `, + }, + ], + }); + t.teardown(() => mf.dispose()); + + const bindings = await mf.getBindings<{ RPC_SERVICE: any }>(); + const obj = await bindings.RPC_SERVICE.getObject(); + t.deepEqual(obj, { + isPlainObject: true, + value: 123, + }); +}); + test("Miniflare: custom outbound service", async (t) => { const mf = new Miniflare({ workers: [