-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(@remix-run/cloudflare,@remix-run/deno,@remix-run/node): SerializeFrom
utility for loader and action type inference
#4013
Changes from all commits
2adec26
de502a5
6e54352
d7aaac6
6a6f624
275a170
665e0db
a6cd1c5
f6de023
978ab3d
86e6d52
a14846c
8cdedf4
f72d48e
72e0c93
439866c
638fd10
b97e972
65aceb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
--- | ||
"remix": minor | ||
"@remix-run/cloudflare": minor | ||
"@remix-run/deno": minor | ||
"@remix-run/node": minor | ||
"@remix-run/react": minor | ||
"@remix-run/serve": minor | ||
"@remix-run/server-runtime": minor | ||
--- | ||
|
||
Each runtime package (@remix-run/cloudflare,@remix-run/deno,@remix-run/node) now exports `SerializeFrom`, which is used to | ||
infer the JSON-serialized return type of loaders and actions. | ||
|
||
Example: | ||
```ts | ||
type MyLoaderData = SerializeFrom<typeof loader> | ||
type MyActionData = SerializeFrom<typeof action> | ||
``` | ||
|
||
This is what `useLoaderData<typeof loader>` and `useActionData<typeof action>` use under-the-hood. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,83 @@ | ||
import type { TypedResponse, UseDataFunctionReturn } from "../components"; | ||
import type { TypedResponse } from "../serialize"; | ||
import type { useLoaderData } from "../components"; | ||
|
||
function isEqual<A, B>( | ||
arg: A extends B ? (B extends A ? true : false) : false | ||
): void {} | ||
|
||
// not sure why `eslint` thinks the `T` generic is not used... | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
type LoaderData<T> = ReturnType<typeof useLoaderData<T>>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: this test is for testing |
||
|
||
describe("useLoaderData", () => { | ||
it("supports plain data type", () => { | ||
type AppData = { hello: string }; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, { hello: string }>(true); | ||
}); | ||
|
||
it("supports plain Response", () => { | ||
type Loader = (args: any) => Response; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, any>(true); | ||
}); | ||
|
||
it("infers type regardless of redirect", () => { | ||
type Loader = ( | ||
args: any | ||
) => TypedResponse<{ id: string }> | TypedResponse<never>; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, { id: string }>(true); | ||
}); | ||
|
||
it("supports Response-returning loader", () => { | ||
type Loader = (args: any) => TypedResponse<{ hello: string }>; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, { hello: string }>(true); | ||
}); | ||
|
||
it("supports async Response-returning loader", () => { | ||
type Loader = (args: any) => Promise<TypedResponse<{ hello: string }>>; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, { hello: string }>(true); | ||
}); | ||
|
||
it("supports data-returning loader", () => { | ||
type Loader = (args: any) => { hello: string }; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, { hello: string }>(true); | ||
}); | ||
|
||
it("supports async data-returning loader", () => { | ||
type Loader = (args: any) => Promise<{ hello: string }>; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, { hello: string }>(true); | ||
}); | ||
}); | ||
|
||
describe("type serializer", () => { | ||
it("converts Date to string", () => { | ||
type AppData = { hello: Date }; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, { hello: string }>(true); | ||
}); | ||
|
||
it("supports custom toJSON", () => { | ||
type AppData = { toJSON(): { data: string[] } }; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, { data: string[] }>(true); | ||
}); | ||
|
||
it("supports recursion", () => { | ||
type AppData = { dob: Date; parent: AppData }; | ||
type SerializedAppData = { dob: string; parent: SerializedAppData }; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, SerializedAppData>(true); | ||
}); | ||
|
||
it("supports tuples and arrays", () => { | ||
type AppData = { arr: Date[]; tuple: [string, number, Date]; empty: [] }; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual< | ||
response, | ||
{ arr: string[]; tuple: [string, number, string]; empty: [] } | ||
|
@@ -81,13 +86,13 @@ describe("type serializer", () => { | |
|
||
it("transforms unserializables to null in arrays", () => { | ||
type AppData = [Function, symbol, undefined]; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, [null, null, null]>(true); | ||
}); | ||
|
||
it("transforms unserializables to never in objects", () => { | ||
type AppData = { arg1: Function; arg2: symbol; arg3: undefined }; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, {}>(true); | ||
}); | ||
|
||
|
@@ -97,7 +102,7 @@ describe("type serializer", () => { | |
speak: () => string; | ||
} | ||
type Loader = (args: any) => TypedResponse<Test>; | ||
type response = UseDataFunctionReturn<Loader>; | ||
type response = LoaderData<Loader>; | ||
isEqual<response, { arg: string }>(true); | ||
}); | ||
|
||
|
@@ -107,7 +112,7 @@ describe("type serializer", () => { | |
arg2: number | undefined; | ||
arg3: undefined; | ||
}; | ||
type response = UseDataFunctionReturn<AppData>; | ||
type response = LoaderData<AppData>; | ||
isEqual<response, { arg1: string; arg2?: number }>(true); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,6 @@ export type { | |
IsSessionFunction, | ||
JsonFunction, | ||
RedirectFunction, | ||
TypedResponse, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Therefore, TBH the distinction between "interface" and "re-export" is subtle and I'd much prefer to not need re-exports at all once we have a pkg for exporting runtime-agnostic stuff. |
||
} from "./interface"; | ||
|
||
// Remix server runtime packages should re-export these types | ||
|
@@ -54,22 +53,24 @@ export type { | |
LinksFunction, | ||
LoaderArgs, | ||
LoaderFunction, | ||
MemoryUploadHandlerFilterArgs, | ||
MemoryUploadHandlerOptions, | ||
MetaDescriptor, | ||
MetaFunction, | ||
PageLinkDescriptor, | ||
RequestHandler, | ||
RouteComponent, | ||
RouteHandle, | ||
SerializeFrom, | ||
ServerBuild, | ||
ServerEntryModule, | ||
Session, | ||
SessionData, | ||
SessionIdStorageStrategy, | ||
SessionStorage, | ||
SignFunction, | ||
TypedResponse, | ||
UnsignFunction, | ||
UploadHandlerPart, | ||
UploadHandler, | ||
MemoryUploadHandlerOptions, | ||
MemoryUploadHandlerFilterArgs, | ||
} from "./reexport"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateRequestHandlerFunction
is a utility for authoring Remix runtime packages and was incorrectly re-exported before.