diff --git a/.changeset/light-sheep-give.md b/.changeset/light-sheep-give.md new file mode 100644 index 00000000000..16e4efa030a --- /dev/null +++ b/.changeset/light-sheep-give.md @@ -0,0 +1,26 @@ +--- +"remix": patch +"@remix-run/serve": patch +"@remix-run/server-runtime": patch +--- + +Fix `TypedResponse` so that Typescript correctly shows errors for incompatible types in loaders and actions. + +Previously, when the return type of a loader or action was explicitly set to `TypedResponse`, +Typescript would not show errors when the loader or action returned an incompatible type. + +For example: + +```ts +export const action = async (args: ActionArgs): Promise> => { + return json(42); +}; +``` + +In this case, Typescript would not show an error even though `42` is clearly not a `string`. + +This happens because `json` returns a `TypedResponse`, +but because `TypedReponse` was previously just `Response & { json: () => Promise }` +and `Response` already defines `{ json: () => Promise }`, type erasure caused `Promise` to be used for `42`. + +To fix this, we explicitly omit the `Response`'s `json` property before intersecting with `{ json: () => Promise }`. diff --git a/contributors.yml b/contributors.yml index 18c8c6b362d..6a99603e613 100644 --- a/contributors.yml +++ b/contributors.yml @@ -452,3 +452,4 @@ - zachdtaylor - zainfathoni - zhe +- dabdine diff --git a/packages/remix-server-runtime/__tests__/responses-test.ts b/packages/remix-server-runtime/__tests__/responses-test.ts index a6b92b311f0..0506d28c965 100644 --- a/packages/remix-server-runtime/__tests__/responses-test.ts +++ b/packages/remix-server-runtime/__tests__/responses-test.ts @@ -44,6 +44,11 @@ describe("json", () => { expect(result).toMatchObject({ hello: "remix" }); }); + it("disallows unmatched typed responses", async () => { + let response = json("hello"); + isEqual, typeof response>(false); + }); + it("disallows unserializables", () => { // @ts-expect-error expect(() => json(124n)).toThrow(); diff --git a/packages/remix-server-runtime/responses.ts b/packages/remix-server-runtime/responses.ts index 748f98b1d86..dd1dbad3ed3 100644 --- a/packages/remix-server-runtime/responses.ts +++ b/packages/remix-server-runtime/responses.ts @@ -5,7 +5,7 @@ export type JsonFunction = ( // must be a type since this is a subtype of response // interfaces must conform to the types they extend -export type TypedResponse = Response & { +export type TypedResponse = Omit & { json(): Promise; };