From 7d22fb75b3471a2982c1b772e530e9a8c7aaa080 Mon Sep 17 00:00:00 2001 From: "bethel." <71951344+Bethel-nz@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:22:46 +0100 Subject: [PATCH] fix: Add default type parameter to prevent infinite type recursion (fixes #53) Add `= Empty` default type parameter to newHttpBatchRpcSession to match newWebSocketRpcSession and prevent "Type instantiation is excessively deep and possibly infinite" TypeScript error when calling without explicit type parameters. Before: newHttpBatchRpcSession>(...) After: newHttpBatchRpcSession = Empty>(...) This allows untyped usage like: const session = newHttpBatchRpcSession("http://example.com"); - Add comprehensive test coverage for type instantiation issue - Ensure typed sessions continue to work correctly - Validate that untyped sessions return proper RpcStub --- __tests__/issue53.test.ts | 66 +++++++++++++++++++++++++++++++++++++++ src/index.ts | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 __tests__/issue53.test.ts diff --git a/__tests__/issue53.test.ts b/__tests__/issue53.test.ts new file mode 100644 index 0000000..64a0ecc --- /dev/null +++ b/__tests__/issue53.test.ts @@ -0,0 +1,66 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the MIT license found in the LICENSE.txt file or at: +// https://opensource.org/license/mit + +import { expect, it, describe } from "vitest"; +import { newHttpBatchRpcSession, newWebSocketRpcSession } from "../src/index.js"; + +describe("Issue #53: Type instantiation excessively deep for untyped sessions", () => { + it("should not cause type instantiation errors for untyped HTTP batch sessions", () => { + // Before the fix, this would cause: + // "Type instantiation is excessively deep and possibly infinite.ts(2589)" + const httpSession = newHttpBatchRpcSession("http://example.com"); + expect(httpSession).toBeDefined(); + + // RPC stubs are proxied functions, so they appear as "function" type + expect(typeof httpSession).toBe("function"); + }); + + it("should not cause type instantiation errors for untyped WebSocket sessions", () => { + // This should work (already had default type parameter) + const wsSession = newWebSocketRpcSession("ws://example.com"); + expect(wsSession).toBeDefined(); + + // RPC stubs are proxied functions, so they appear as "function" type + expect(typeof wsSession).toBe("function"); + }); + + it("should work with explicit type parameters (typed sessions)", () => { + // Explicit typing should continue to work as before + interface MyApi { + ping(): Promise; + getData(): Promise; + } + + const typedHttp = newHttpBatchRpcSession("http://example.com"); + const typedWs = newWebSocketRpcSession("ws://example.com"); + + expect(typedHttp).toBeDefined(); + expect(typedWs).toBeDefined(); + expect(typeof typedHttp).toBe("function"); + expect(typeof typedWs).toBe("function"); + + // These should have the correct types (checked at compile time) + // The calls themselves would fail at runtime since we're not connected to real servers, + // but TypeScript should accept them syntactically + expect(typeof typedHttp.ping).toBe("function"); + expect(typeof typedWs.getData).toBe("function"); + }); + + it("should return Empty-typed stubs for untyped sessions", () => { + // The untyped sessions should return RpcStub which behaves like unknown + const httpSession = newHttpBatchRpcSession("http://example.com"); + const wsSession = newWebSocketRpcSession("ws://example.com"); + + expect(httpSession).toBeDefined(); + expect(wsSession).toBeDefined(); + + // These should be functions with RPC stub behavior (due to proxy) + expect(typeof httpSession).toBe("function"); + expect(typeof wsSession).toBe("function"); + + // They should have the basic stub methods + expect(typeof httpSession.dup).toBe("function"); + expect(typeof wsSession.dup).toBe("function"); + }); +}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 32f6ec8..67bd6cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -117,7 +117,7 @@ export let newWebSocketRpcSession: = Empty> * value is an RpcStub. You can customize anything about the request except for the method * (it will always be set to POST) and the body (which the RPC system will fill in). */ -export let newHttpBatchRpcSession:> +export let newHttpBatchRpcSession: = Empty> (urlOrRequest: string | Request, init?: RequestInit) => RpcStub = newHttpBatchRpcSessionImpl;