diff --git a/packages/remix-react/single-fetch.tsx b/packages/remix-react/single-fetch.tsx index c67e9444679..927f329cf82 100644 --- a/packages/remix-react/single-fetch.tsx +++ b/packages/remix-react/single-fetch.tsx @@ -358,10 +358,13 @@ export function decodeViaTurboStream( return { value: { [SingleFetchRedirectSymbol]: rest[0] } }; } }, - (type) => { + (type, value) => { if (type === "SingleFetchFallback") { return { value: undefined }; } + if (type === "SingleFetchClassInstance") { + return { value }; + } }, ], }); diff --git a/packages/remix-server-runtime/serialize.ts b/packages/remix-server-runtime/serialize.ts index 6f2280661f6..b0c0bd9b908 100644 --- a/packages/remix-server-runtime/serialize.ts +++ b/packages/remix-server-runtime/serialize.ts @@ -9,11 +9,9 @@ import { type Expect, type Equal } from "./typecheck"; import { type SerializeFrom as SingleFetch_SerializeFrom } from "./single-fetch"; import type { Future } from "./future"; -type SingleFetchEnabled = Future extends { - singleFetch: infer T extends boolean; -} - ? T - : false; +// prettier-ignore +type SingleFetchEnabled = + Future extends { singleFetch: infer T extends boolean } ? T : false // prettier-ignore /** @@ -26,8 +24,9 @@ type SingleFetchEnabled = Future extends { */ export type SerializeFrom = SingleFetchEnabled extends true ? - T extends (...args: []) => unknown ? SingleFetch_SerializeFrom : - never + T extends (...args: []) => unknown ? + SingleFetch_SerializeFrom : + never : T extends (...args: any[]) => infer Output ? Parameters extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ? @@ -39,7 +38,6 @@ export type SerializeFrom = : // Back compat: manually defined data type, not inferred from loader nor action Jsonify> -; // note: cannot be inlined as logic requires union distribution // prettier-ignore diff --git a/packages/remix-server-runtime/single-fetch.ts b/packages/remix-server-runtime/single-fetch.ts index 4bb631fbdaa..aed068c4028 100644 --- a/packages/remix-server-runtime/single-fetch.ts +++ b/packages/remix-server-runtime/single-fetch.ts @@ -12,6 +12,7 @@ import { } from "@remix-run/router"; import { encode } from "turbo-stream"; +import { type Expect, type Equal } from "./typecheck"; import type { ServerBuild } from "./build"; import type { AppLoadContext } from "./data"; import { sanitizeError, sanitizeErrors } from "./errors"; @@ -20,6 +21,11 @@ import { ServerMode } from "./mode"; import type { TypedResponse } from "./responses"; import { isRedirectStatusCode, isResponse } from "./responses"; import type { Jsonify } from "./jsonify"; +import type { + ClientActionFunctionArgs, + ClientLoaderFunctionArgs, + LoaderFunctionArgs, +} from "./routeModules"; export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); @@ -350,7 +356,18 @@ export function encodeViaTurboStream( } }, ], - postPlugins: [() => ["SingleFetchFallback"]], + postPlugins: [ + (value) => { + if (!value) return; + if (typeof value !== "object") return; + + return [ + "SingleFetchClassInstance", + Object.fromEntries(Object.entries(value)), + ]; + }, + () => ["SingleFetchFallback"], + ], }); } @@ -417,6 +434,153 @@ type Fn = (...args: any[]) => unknown; // json/defer will be removed so everything will use the new simplified typings. // prettier-ignore export type SerializeFrom = + Parameters extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ? + ReturnType extends TypedResponse ? Jsonify : + Awaited> + : Awaited> extends TypedResponse> ? Jsonify : Awaited> extends DataWithResponseInit ? Serialize : Serialize>>; + +type ServerLoader = (args: LoaderFunctionArgs) => T; +type ClientLoader = (args: ClientLoaderFunctionArgs) => T; + +class TestClass { + constructor(public a: string, public b: Date) { + this.a = a; + this.b = b; + } + + testmethod() {} +} + +interface TestInterface { + undefined: undefined; + null: null; + boolean: boolean; + string: string; + symbol: symbol; + number: number; + bigint: bigint; + Date: Date; + URL: URL; + RegExp: RegExp; + Error: Error; + Set: Set; + Map: Map; +} + +type Recursive = { + a: string; + b: Date; + recursive?: Recursive; +}; + +// prettier-ignore +// eslint-disable-next-line +type _tests = [ + Expect>, undefined>>, + Expect, + Map: Map, + TestInterface: TestInterface, + Recursive: Recursive + }>>, { + undefined: undefined, + null: null, + boolean: boolean, + string: string, + symbol: symbol, + number: number, + bigint: bigint, + Date: Date, + URL: URL, + RegExp: RegExp, + Error: Error, + Set: Set, + Map: Map, + TestInterface: TestInterface + Recursive: Recursive + }>>, + Expect>, [ + undefined, + null, + boolean, + string, + symbol, + number, + bigint, + Date, + URL, + RegExp, + Error + ]>>, + Expect>>, [ + undefined, + null, + boolean, + string, + symbol, + number, + bigint, + Date, + URL, + RegExp, + Error + ]>>, + + Expect void, + class: TestClass + }>>, { + function: undefined, + class: { + a: string + b: Date, + testmethod: undefined + }, + }>>, + + Expect void, + class: TestClass + }>>, { + function: () => void, + class: TestClass + }>>, +]