Skip to content

Commit

Permalink
types: remove SerializeFrom (#12273)
Browse files Browse the repository at this point in the history
* refactor type utils

* do not export SerializeFrom from route module types entry

also rename route module types entry to `/route-module` instead of `/types`
  • Loading branch information
pcattori authored Nov 13, 2024
1 parent 9f2b155 commit 538b958
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 129 deletions.
2 changes: 1 addition & 1 deletion packages/react-router-dev/typegen/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function generate(
// React Router generated types for route:
// ${route.file}
import * as T from "react-router/types"
import * as T from "react-router/route-module"
export type Params = {${formatParamProperties(routes, route)}}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/lib/dom/lib.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ import {
useResolvedPath,
useRouteId,
} from "../hooks";
import type { SerializeFrom } from "../types";
import type { SerializeFrom } from "../types/route-data";

////////////////////////////////////////////////////////////////////////////////
//#region Global Stuff
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/lib/dom/ssr/routeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
import type { EntryRoute } from "./routes";
import type { DataRouteMatch } from "../../context";
import type { LinkDescriptor } from "../../router/links";
import type { SerializeFrom } from "../../types";
import type { SerializeFrom } from "../../types/route-data";

export interface RouteModules {
[routeId: string]: RouteModule | undefined;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/lib/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
resolveTo,
stripBasename,
} from "./router/utils";
import type { SerializeFrom } from "./types";
import type { SerializeFrom } from "./types/route-data";

// Provided by the build system
declare const __DEV__: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/lib/router/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Equal, Expect } from "../test-types";
import type { Equal, Expect } from "../types/utils";
import type { Location, Path, To } from "./history";
import { invariant, parsePath, warning } from "./history";

Expand Down
109 changes: 109 additions & 0 deletions packages/react-router/lib/types/route-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type {
ClientLoaderFunctionArgs,
ClientActionFunctionArgs,
} from "../dom/ssr/routeModules";
import type { DataWithResponseInit } from "../router/utils";
import type { Serializable } from "../server-runtime/single-fetch";
import type { Equal, Expect, Func, IsAny, Pretty } from "./utils";

// prettier-ignore
type Serialize<T> =
// First, let type stay as-is if its already serializable...
T extends Serializable ? T :

// ...then don't allow functions to be serialized...
T extends (...args: any[]) => unknown ? undefined :

// ...lastly handle inner types for all container types allowed by `turbo-stream`

// Promise
T extends Promise<infer U> ? Promise<Serialize<U>> :

// Map & Set
T extends Map<infer K, infer V> ? Map<Serialize<K>, Serialize<V>> :
T extends Set<infer U> ? Set<Serialize<U>> :

// Array
T extends [] ? [] :
T extends readonly [infer F, ...infer R] ? [Serialize<F>, ...Serialize<R>] :
T extends Array<infer U> ? Array<Serialize<U>> :
T extends readonly unknown[] ? readonly Serialize<T[number]>[] :

// Record
T extends Record<any, any> ? {[K in keyof T]: Serialize<T[K]>} :

undefined

type VoidToUndefined<T> = Equal<T, void> extends true ? undefined : T;

// prettier-ignore
type DataFrom<T> =
IsAny<T> extends true ? undefined :
T extends Func ? VoidToUndefined<Awaited<ReturnType<T>>> :
undefined

// prettier-ignore
type ClientData<T> =
T extends DataWithResponseInit<infer U> ? U :
T

// prettier-ignore
type ServerData<T> =
T extends DataWithResponseInit<infer U> ? Serialize<U> :
Serialize<T>

export type ServerDataFrom<T> = ServerData<DataFrom<T>>;
export type ClientDataFrom<T> = ClientData<DataFrom<T>>;

export type SerializeFrom<T> = T extends (...args: infer Args) => unknown
? Args extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs]
? ClientDataFrom<T>
: ServerDataFrom<T>
: T;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type __tests = [
// ServerDataFrom
Expect<Equal<ServerDataFrom<any>, undefined>>,
Expect<
Equal<
ServerDataFrom<() => { a: string; b: Date; c: () => boolean }>,
{ a: string; b: Date; c: undefined }
>
>,
Expect<
Equal<
Pretty<
ServerDataFrom<
() =>
| { json: string; b: Date; c: () => boolean }
| DataWithResponseInit<{ data: string; b: Date; c: () => boolean }>
>
>,
| { json: string; b: Date; c: undefined }
| { data: string; b: Date; c: undefined }
>
>,

// ClientDataFrom
Expect<Equal<ClientDataFrom<any>, undefined>>,
Expect<
Equal<
ClientDataFrom<() => { a: string; b: Date; c: () => boolean }>,
{ a: string; b: Date; c: () => boolean }
>
>,
Expect<
Equal<
Pretty<
ClientDataFrom<
() =>
| { json: string; b: Date; c: () => boolean }
| DataWithResponseInit<{ data: string; b: Date; c: () => boolean }>
>
>,
| { json: string; b: Date; c: () => boolean }
| { data: string; b: Date; c: () => boolean }
>
>
];
Original file line number Diff line number Diff line change
@@ -1,47 +1,19 @@
import type {
ClientLoaderFunctionArgs,
ClientActionFunctionArgs,
} from "./dom/ssr/routeModules";
import type { DataWithResponseInit } from "./router/utils";
import type { AppLoadContext } from "./server-runtime/data";
import type { Serializable } from "./server-runtime/single-fetch";
import type { Equal, Expect } from "./test-types";
import type { AppLoadContext } from "../server-runtime/data";
import type { ClientDataFrom, ServerDataFrom } from "./route-data";
import type { Equal, Expect, Func } from "./utils";

type IsAny<T> = 0 extends 1 & T ? true : false;
type IsDefined<T> = Equal<T, undefined> extends true ? false : true;
type Fn = (...args: any[]) => unknown;

type RouteModule = {
loader?: Fn;
clientLoader?: Fn;
action?: Fn;
clientAction?: Fn;
loader?: Func;
clientLoader?: Func;
action?: Func;
clientAction?: Func;
HydrateFallback?: unknown;
default?: unknown;
ErrorBoundary?: unknown;
};

type VoidToUndefined<T> = Equal<T, void> extends true ? undefined : T;

// prettier-ignore
type DataFrom<T> =
IsAny<T> extends true ? undefined :
T extends Fn ? VoidToUndefined<Awaited<ReturnType<T>>> :
undefined

// prettier-ignore
type ClientData<T> =
T extends DataWithResponseInit<infer U> ? U :
T

// prettier-ignore
type ServerData<T> =
T extends DataWithResponseInit<infer U> ? Serialize<U> :
Serialize<T>

type ServerDataFrom<T> = ServerData<DataFrom<T>>;
type ClientDataFrom<T> = ClientData<DataFrom<T>>;

// prettier-ignore
type IsHydrate<ClientLoader> =
ClientLoader extends { hydrate: true } ? true :
Expand All @@ -52,7 +24,7 @@ export type CreateLoaderData<T extends RouteModule> = _CreateLoaderData<
ServerDataFrom<T["loader"]>,
ClientDataFrom<T["clientLoader"]>,
IsHydrate<T["clientLoader"]>,
T extends { HydrateFallback: Fn } ? true : false
T extends { HydrateFallback: Func } ? true : false
>;

// prettier-ignore
Expand Down Expand Up @@ -93,45 +65,6 @@ type ServerDataFunctionArgs<Params> = ClientDataFunctionArgs<Params> & {
context: AppLoadContext;
};

// prettier-ignore
type Serialize<T> =
// First, let type stay as-is if its already serializable...
T extends Serializable ? T :

// ...then don't allow functions to be serialized...
T extends (...args: any[]) => unknown ? undefined :

// ...lastly handle inner types for all container types allowed by `turbo-stream`

// Promise
T extends Promise<infer U> ? Promise<Serialize<U>> :

// Map & Set
T extends Map<infer K, infer V> ? Map<Serialize<K>, Serialize<V>> :
T extends Set<infer U> ? Set<Serialize<U>> :

// Array
T extends [] ? [] :
T extends readonly [infer F, ...infer R] ? [Serialize<F>, ...Serialize<R>] :
T extends Array<infer U> ? Array<Serialize<U>> :
T extends readonly unknown[] ? readonly Serialize<T[number]>[] :

// Record
T extends Record<any, any> ? {[K in keyof T]: Serialize<T[K]>} :

undefined

/**
* @deprecated Generics on data APIs such as `useLoaderData`, `useActionData`,
* `meta`, etc. are deprecated in favor of the `Route.*` types generated via
* `react-router typegen`
*/
export type SerializeFrom<T> = T extends (...args: infer Args) => unknown
? Args extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs]
? ClientData<DataFrom<T>>
: ServerData<DataFrom<T>>
: T;

export type CreateServerLoaderArgs<Params> = ServerDataFunctionArgs<Params>;

export type CreateClientLoaderArgs<
Expand Down Expand Up @@ -167,54 +100,8 @@ export type CreateErrorBoundaryProps<Params, LoaderData, ActionData> = {
actionData?: ActionData;
};

type Pretty<T> = { [K in keyof T]: T[K] } & {};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type __tests = [
// ServerDataFrom
Expect<Equal<ServerDataFrom<any>, undefined>>,
Expect<
Equal<
ServerDataFrom<() => { a: string; b: Date; c: () => boolean }>,
{ a: string; b: Date; c: undefined }
>
>,
Expect<
Equal<
Pretty<
ServerDataFrom<
() =>
| { json: string; b: Date; c: () => boolean }
| DataWithResponseInit<{ data: string; b: Date; c: () => boolean }>
>
>,
| { json: string; b: Date; c: undefined }
| { data: string; b: Date; c: undefined }
>
>,

// ClientDataFrom
Expect<Equal<ClientDataFrom<any>, undefined>>,
Expect<
Equal<
ClientDataFrom<() => { a: string; b: Date; c: () => boolean }>,
{ a: string; b: Date; c: () => boolean }
>
>,
Expect<
Equal<
Pretty<
ClientDataFrom<
() =>
| { json: string; b: Date; c: () => boolean }
| DataWithResponseInit<{ data: string; b: Date; c: () => boolean }>
>
>,
| { json: string; b: Date; c: () => boolean }
| { data: string; b: Date; c: () => boolean }
>
>,

// LoaderData
Expect<Equal<CreateLoaderData<{}>, undefined>>,
Expect<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ export type Expect<T extends true> = T;
export type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false

export type IsAny<T> = 0 extends 1 & T ? true : false;

export type Func = (...args: any[]) => unknown;

export type Pretty<T> = { [K in keyof T]: T[K] } & {};
4 changes: 2 additions & 2 deletions packages/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
"default": "./dist/production/index.js"
}
},
"./types": {
"types": "./dist/production/lib/types.d.ts"
"./route-module": {
"types": "./dist/production/lib/route-module.d.ts"
},
"./dom": {
"node": {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createBanner } from "../../build.utils.js";

import pkg from "./package.json";

const entry = ["index.ts", "dom-export.ts", "lib/types.ts"];
const entry = ["index.ts", "dom-export.ts", "lib/types/route-module.ts"];

const config = (enableDevWarnings: boolean) =>
defineConfig([
Expand Down

0 comments on commit 538b958

Please sign in to comment.