diff --git a/.changeset/polite-dodos-happen.md b/.changeset/polite-dodos-happen.md new file mode 100644 index 0000000000000..1f9bfef4bc31a --- /dev/null +++ b/.changeset/polite-dodos-happen.md @@ -0,0 +1,9 @@ +--- +"miniflare": patch +--- + +fix: Allow the magic proxy to proxy objects containing functions indexed by symbols + +In https://github.com/cloudflare/workers-sdk/pull/5670 we introduced the possibility +of the magic proxy to handle object containing functions, the implementation didn't +account for functions being indexed by symbols, address such issue 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 3d58b938f955e..4bbfabdd51585 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 540f908c63ac6..0d284d9fed94d 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 ae400da5ebeeb..0283af16f59e2 100644 --- a/packages/miniflare/src/workers/core/proxy.worker.ts +++ b/packages/miniflare/src/workers/core/proxy.worker.ts @@ -69,7 +69,15 @@ function isPlainObject(value: unknown) { ); } function objectContainsFunctions(obj: Record): boolean { - for (const [, entry] of Object.entries(obj)) { + const propertyNames = Object.getOwnPropertyNames(obj); + const propertySymbols = Object.getOwnPropertySymbols(obj); + const properties = [...propertyNames, ...propertySymbols]; + + for (const property of properties) { + // @ts-ignore - ignoring the following line since TypeScript seems to + // incorrectly error if `property` is a symbol + // (see: https://github.com/Microsoft/TypeScript/issues/24587) + const entry = obj[property]; if (typeof entry === "function") { return true; } diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 302c55cff1313..614a49abc1bad 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -821,6 +821,51 @@ 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 o = await bindings.RPC_SERVICE.getObject(); + t.deepEqual(o.isPlainObject, true); + t.deepEqual(o.value, 123); +}); + test("Miniflare: custom outbound service", async (t) => { const mf = new Miniflare({ workers: [