diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 2fafe201eb..ca5cf158a2 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -14,6 +14,8 @@ "@tanstack/react-start": "^1.121.0-alpha.19", "react": "^19.0.0", "react-dom": "^19.0.0", + "seroval": "^1.3.1", + "seroval-plugins": "^1.3.1", "tailwind-merge": "^2.6.0", "vite": "^6.3.5", "zod": "^3.24.2" diff --git a/examples/react/start-basic/src/router.tsx b/examples/react/start-basic/src/router.tsx index c76eb0210c..e0b6ee8792 100644 --- a/examples/react/start-basic/src/router.tsx +++ b/examples/react/start-basic/src/router.tsx @@ -2,6 +2,16 @@ import { createRouter as createTanStackRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' +import { serovalSerializer } from '@tanstack/react-router' +import { FormDataPlugin, HeadersPlugin } from 'seroval-plugins/web' +import { createServerFn, createStart } from '@tanstack/react-start' + +const fn = createServerFn().handler(() => { + return { + formData: new FormData(), + headers: new Headers(), + } +}) export function createRouter() { const router = createTanStackRouter({ @@ -15,6 +25,16 @@ export function createRouter() { return router } +const start = createStart({ + serializer: serovalSerializer({ plugins: [FormDataPlugin, HeadersPlugin] }), +}) + +declare module '@tanstack/react-start' { + interface Register { + start: typeof start + } +} + declare module '@tanstack/react-router' { interface Register { router: ReturnType diff --git a/packages/react-router/src/fileRoute.ts b/packages/react-router/src/fileRoute.ts index 0b77e986e7..9b45d06c6a 100644 --- a/packages/react-router/src/fileRoute.ts +++ b/packages/react-router/src/fileRoute.ts @@ -17,7 +17,7 @@ import type { AnyRouter, Constrain, ConstrainLiteral, - FileBaseRouteOptions, + LifecycleRouteOptions, FileRoutesByPath, LazyRouteOptions, RegisteredRouter, @@ -29,6 +29,7 @@ import type { RouteLoaderFn, UpdatableRouteOptions, UseNavigateResult, + StartRegister, } from '@tanstack/router-core' import type { UseLoaderDepsRoute } from './useLoaderDeps' import type { UseLoaderDataRoute } from './useLoaderData' @@ -76,6 +77,7 @@ export class FileRoute< } createRoute = < + TStart = StartRegister, TSearchValidator = undefined, TParams = ResolveParams, TRouteContextFn = AnyContext, @@ -83,8 +85,10 @@ export class FileRoute< TLoaderDeps extends Record = {}, TLoaderFn = undefined, TChildren = unknown, + TLifecycleSerialization = unknown, >( - options?: FileBaseRouteOptions< + options?: LifecycleRouteOptions< + TStart, TParentRoute, TId, TPath, @@ -94,7 +98,8 @@ export class FileRoute< TLoaderFn, AnyContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization > & UpdatableRouteOptions< TParentRoute, @@ -109,6 +114,7 @@ export class FileRoute< TBeforeLoadFn >, ): Route< + TStart, TParentRoute, TPath, TFullPath, @@ -122,7 +128,8 @@ export class FileRoute< TLoaderDeps, TLoaderFn, TChildren, - unknown + unknown, + TLifecycleSerialization > => { warning( this.silent, diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx index c662c21fc0..95ace662a2 100644 --- a/packages/react-router/src/index.tsx +++ b/packages/react-router/src/index.tsx @@ -35,16 +35,17 @@ export { createControlledPromise, retainSearchParams, stripSearchParams, + serovalSerializer, } from '@tanstack/router-core' export type { AnyRoute, StartSerializer, - Serializable, - SerializerParse, - SerializerParseBy, - SerializerStringify, - SerializerStringifyBy, + DefaultTypeSerializable, + TypeSerializerParse, + TypeSerializerParseBy, + TypeSerializerStringify, + TypeSerializerStringifyBy, DeferredPromiseState, DeferredPromise, ParsedLocation, @@ -208,13 +209,11 @@ export type { MakeRouteMatchUnion, RouteMatch, AnyRouteMatch, - RouteContextFn, RouteContextOptions, - BeforeLoadFn, BeforeLoadContextOptions, ContextOptions, RouteOptions, - FileBaseRouteOptions, + LifecycleRouteOptions, BaseRouteOptions, UpdatableRouteOptions, RouteLoaderFn, diff --git a/packages/react-router/src/route.tsx b/packages/react-router/src/route.tsx index d3730e819e..9ea4a7cf2a 100644 --- a/packages/react-router/src/route.tsx +++ b/packages/react-router/src/route.tsx @@ -35,6 +35,7 @@ import type { RouteOptions, RouteTypesById, RouterCore, + StartRegister, ToMaskOptions, UseNavigateResult, } from '@tanstack/router-core' @@ -150,6 +151,7 @@ export class RouteApi< } export class Route< + in out TStart = StartRegister, in out TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute, in out TPath extends RouteConstraints['TPath'] = '/', in out TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath< @@ -171,8 +173,10 @@ export class Route< in out TLoaderFn = undefined, in out TChildren = unknown, in out TFileRouteTypes = unknown, + in out TLifecycleSerialization = unknown, > extends BaseRoute< + TStart, TParentRoute, TPath, TFullPath, @@ -186,10 +190,12 @@ export class Route< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > implements RouteCore< + TStart, TParentRoute, TPath, TFullPath, @@ -203,7 +209,8 @@ export class Route< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > { /** @@ -211,6 +218,7 @@ export class Route< */ constructor( options?: RouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -222,7 +230,8 @@ export class Route< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization >, ) { super(options) @@ -283,6 +292,7 @@ export class Route< } export function createRoute< + TStart = StartRegister, TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute, TPath extends RouteConstraints['TPath'] = '/', TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath< @@ -302,8 +312,10 @@ export function createRoute< TLoaderDeps extends Record = {}, TLoaderFn = undefined, TChildren = unknown, + TLifecycleSerialization = unknown, >( options: RouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -315,9 +327,11 @@ export function createRoute< TLoaderFn, AnyContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization >, ): Route< + TStart, TParentRoute, TPath, TFullPath, @@ -330,9 +344,11 @@ export function createRoute< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TChildren + TChildren, + TLifecycleSerialization > { return new Route< + TStart, TParentRoute, TPath, TFullPath, @@ -345,36 +361,53 @@ export function createRoute< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TChildren + TChildren, + TLifecycleSerialization >(options) } -export type AnyRootRoute = RootRoute +export type AnyRootRoute = RootRoute< + any, + any, + any, + any, + any, + any, + any, + any, + any +> export function createRootRouteWithContext() { return < + TStart = StartRegister, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, TSearchValidator = undefined, TLoaderDeps extends Record = {}, TLoaderFn = undefined, + TLifecycleSerialization = unknown, >( options?: RootRouteOptions< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + TLifecycleSerialization >, ) => { return createRootRoute< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + TLifecycleSerialization >(options as any) } } @@ -385,6 +418,7 @@ export function createRootRouteWithContext() { export const rootRouteWithContext = createRootRouteWithContext export class RootRoute< + in out TStart = StartRegister, in out TSearchValidator = undefined, in out TRouterContext = {}, in out TRouteContextFn = AnyContext, @@ -393,8 +427,10 @@ export class RootRoute< in out TLoaderFn = undefined, in out TChildren = unknown, in out TFileRouteTypes = unknown, + in out TLifecycleSerialization = unknown, > extends BaseRootRoute< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, @@ -402,10 +438,12 @@ export class RootRoute< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > implements RootRouteCore< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, @@ -413,7 +451,8 @@ export class RootRoute< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > { /** @@ -421,12 +460,14 @@ export class RootRoute< */ constructor( options?: RootRouteOptions< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + TLifecycleSerialization >, ) { super(options) @@ -487,22 +528,27 @@ export class RootRoute< } export function createRootRoute< + TStart = StartRegister, TSearchValidator = undefined, TRouterContext = {}, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, TLoaderDeps extends Record = {}, TLoaderFn = undefined, + TLifecycleSerialization = unknown, >( options?: RootRouteOptions< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + TLifecycleSerialization >, ): RootRoute< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, @@ -510,15 +556,20 @@ export function createRootRoute< TLoaderDeps, TLoaderFn, unknown, - unknown + unknown, + TLifecycleSerialization > { return new RootRoute< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + unknown, + unknown, + TLifecycleSerialization >(options) } @@ -551,6 +602,7 @@ export type ErrorRouteComponent = RouteComponent export type NotFoundRouteComponent = SyncRouteComponent export class NotFoundRoute< + TStart, TParentRoute extends AnyRootRoute, TRouterContext = AnyContext, TRouteContextFn = AnyContext, @@ -559,7 +611,9 @@ export class NotFoundRoute< TLoaderDeps extends Record = {}, TLoaderFn = undefined, TChildren = unknown, + TLifecycleSerialization = unknown, > extends Route< + TStart, TParentRoute, '/404', '/404', @@ -577,6 +631,7 @@ export class NotFoundRoute< constructor( options: Omit< RouteOptions< + TStart, TParentRoute, string, string, @@ -588,7 +643,8 @@ export class NotFoundRoute< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization >, | 'caseSensitive' | 'parseParams' diff --git a/packages/react-start-client/src/index.tsx b/packages/react-start-client/src/index.tsx index 3aebc26282..a8e54ab40f 100644 --- a/packages/react-start-client/src/index.tsx +++ b/packages/react-start-client/src/index.tsx @@ -9,6 +9,7 @@ export { serverOnly, clientOnly, json, + createStart, } from '@tanstack/start-client-core' export { type DehydratedRouter, @@ -60,6 +61,7 @@ export { type FunctionMiddlewareServerFnOptions, type FunctionMiddlewareServerNextFn, type FunctionServerResultWithContext, + type Register, } from '@tanstack/start-client-core' export { Meta } from './Meta' export { Scripts } from './Scripts' diff --git a/packages/router-core/package.json b/packages/router-core/package.json index 5a3ea10fd7..c7cae11086 100644 --- a/packages/router-core/package.json +++ b/packages/router-core/package.json @@ -61,6 +61,7 @@ "dependencies": { "@tanstack/history": "workspace:*", "@tanstack/store": "^0.7.0", - "tiny-invariant": "^1.3.3" + "tiny-invariant": "^1.3.3", + "seroval": "^1.3.1" } } diff --git a/packages/router-core/src/fileRoute.ts b/packages/router-core/src/fileRoute.ts index 2c689c3929..f3e291a0f5 100644 --- a/packages/router-core/src/fileRoute.ts +++ b/packages/router-core/src/fileRoute.ts @@ -2,12 +2,13 @@ import type { AnyContext, AnyPathParams, AnyRoute, - FileBaseRouteOptions, + LifecycleRouteOptions, ResolveParams, Route, RouteConstraints, UpdatableRouteOptions, } from './route' +import type { StartRegister } from './start' import type { AnyValidator } from './validators' export interface FileRouteTypes { @@ -33,6 +34,7 @@ export interface FileRoutesByPath { } export interface FileRouteOptions< + TStart, TFilePath extends string, TParentRoute extends AnyRoute, TId extends RouteConstraints['TId'], @@ -44,7 +46,9 @@ export interface FileRouteOptions< TBeforeLoadFn = AnyContext, TLoaderDeps extends Record = {}, TLoaderFn = undefined, -> extends FileBaseRouteOptions< + TLifecycleSerialization = unknown, +> extends LifecycleRouteOptions< + TStart, TParentRoute, TId, TPath, @@ -54,7 +58,9 @@ export interface FileRouteOptions< TLoaderFn, AnyContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + AnyContext, + TLifecycleSerialization >, UpdatableRouteOptions< TParentRoute, @@ -76,6 +82,8 @@ export type CreateFileRoute< TPath extends RouteConstraints['TPath'], TFullPath extends RouteConstraints['TFullPath'], > = < + TLifecycleSerialization, + TStart = StartRegister, TSearchValidator = undefined, TParams = ResolveParams, TRouteContextFn = AnyContext, @@ -84,6 +92,7 @@ export type CreateFileRoute< TLoaderFn = undefined, >( options?: FileRouteOptions< + TStart, TFilePath, TParentRoute, TId, @@ -94,9 +103,11 @@ export type CreateFileRoute< TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + TLifecycleSerialization >, ) => Route< + TStart, TParentRoute, TPath, TFullPath, @@ -110,7 +121,8 @@ export type CreateFileRoute< TLoaderDeps, TLoaderFn, unknown, - unknown + unknown, + TLifecycleSerialization > export type LazyRouteOptions = Pick< diff --git a/packages/router-core/src/index.ts b/packages/router-core/src/index.ts index c0dc5cb2f0..dff77bdc29 100644 --- a/packages/router-core/src/index.ts +++ b/packages/router-core/src/index.ts @@ -67,14 +67,19 @@ export type { export type { StartSerializer, - Serializable, - SerializerParse, - SerializerParseBy, - SerializerStringify, - SerializerStringifyBy, + TypeSerializerParse, + TypeSerializerParseBy, + TypeSerializerStringify, + TypeSerializerStringifyBy, SerializerExtensions, + DefaultTypeSerializable, + DefaultTypeSerializer, + Serializer, + SerovalSerializer, } from './serializer' +export { serovalSerializer } from './serializer' + export type { ParsedLocation } from './location' export type { Manifest, RouterManagedTag } from './manifest' export { isMatch } from './Matches' @@ -175,13 +180,11 @@ export type { ResolveFullPath, AnyRouteWithContext, RouteOptions, - FileBaseRouteOptions, + LifecycleRouteOptions, BaseRouteOptions, UpdatableRouteOptions, RouteLoaderFn, LoaderFnContext, - RouteContextFn, - BeforeLoadFn, ContextOptions, RouteContextOptions, BeforeLoadContextOptions, @@ -418,3 +421,13 @@ export type { ValidateUseSearchResult, ValidateUseParamsResult, } from './typePrimitives' + +export type { + CreateStartConfig, + StartConfig, + StartConfigTypes, + DefaultStartRegister, + StartRegister, + AnyStartConfig, + InferSerializer, +} from './start' diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index 4d55371437..64960d60b7 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -40,6 +40,11 @@ import type { ValidatorFn, ValidatorObj, } from './validators' +import type { + LifecycleSerialization, + StartRegister, + TypeSerializerStringifyReturnType, +} from './start' export type AnyPathParams = {} @@ -390,6 +395,7 @@ export interface RouteTypes< in out TLoaderFn, in out TChildren, in out TFileRouteTypes, + in out TLifecycleSerialization, > { parentRoute: TParentRoute path: TPath @@ -421,6 +427,7 @@ export interface RouteTypes< loaderData: ResolveLoaderData loaderDeps: TLoaderDeps fileRouteTypes: TFileRouteTypes + lifecycleSerialization: TLifecycleSerialization } export type ResolveFullPath< @@ -439,6 +446,7 @@ export type RouteLazyFn = ( ) => TRoute export type RouteAddChildrenFn< + in out TStart, in out TParentRoute extends AnyRoute, in out TPath extends string, in out TFullPath extends string, @@ -452,12 +460,14 @@ export type RouteAddChildrenFn< in out TLoaderDeps extends Record, in out TLoaderFn, in out TFileRouteTypes, + in out TLifecycleSerialization, > = ( children: Constrain< TNewChildren, ReadonlyArray | Record >, ) => Route< + TStart, TParentRoute, TPath, TFullPath, @@ -471,10 +481,12 @@ export type RouteAddChildrenFn< TLoaderDeps, TLoaderFn, TNewChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > export type RouteAddFileChildrenFn< + in out TStart, in out TParentRoute extends AnyRoute, in out TPath extends string, in out TFullPath extends string, @@ -488,9 +500,11 @@ export type RouteAddFileChildrenFn< in out TLoaderDeps extends Record, in out TLoaderFn, in out TFileRouteTypes, + in out TLifecycleSerialization, > = ( children: TNewChildren, ) => Route< + TStart, TParentRoute, TPath, TFullPath, @@ -504,10 +518,12 @@ export type RouteAddFileChildrenFn< TLoaderDeps, TLoaderFn, TNewChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > export type RouteAddFileTypesFn< + TStart, TParentRoute extends AnyRoute, TPath extends string, TFullPath extends string, @@ -521,7 +537,9 @@ export type RouteAddFileTypesFn< TLoaderDeps extends Record, TLoaderFn, TChildren, + TLifecycleSerialization, > = () => Route< + TStart, TParentRoute, TPath, TFullPath, @@ -535,10 +553,12 @@ export type RouteAddFileTypesFn< TLoaderDeps, TLoaderFn, TChildren, - TNewFileRouteTypes + TNewFileRouteTypes, + TLifecycleSerialization > export interface Route< + in out TStart, in out TParentRoute extends AnyRoute, in out TPath extends string, in out TFullPath extends string, @@ -553,6 +573,7 @@ export interface Route< in out TLoaderFn, in out TChildren, in out TFileRouteTypes, + in out TLifecycleSerialization, > extends RouteExtensions { path: TPath parentRoute: TParentRoute @@ -571,9 +592,11 @@ export interface Route< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > options: RouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -585,13 +608,15 @@ export interface Route< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization > isRoot: TParentRoute extends AnyRoute ? true : false _componentsPromise?: Promise> lazyFn?: () => Promise< LazyRoute< Route< + TStart, TParentRoute, TPath, TFullPath, @@ -605,7 +630,8 @@ export interface Route< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > > > @@ -629,6 +655,7 @@ export interface Route< ) => this lazy: RouteLazyFn< Route< + TStart, TParentRoute, TPath, TFullPath, @@ -642,10 +669,12 @@ export interface Route< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > > addChildren: RouteAddChildrenFn< + TStart, TParentRoute, TPath, TFullPath, @@ -658,9 +687,11 @@ export interface Route< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > _addFileChildren: RouteAddFileChildrenFn< + TStart, TParentRoute, TPath, TFullPath, @@ -673,9 +704,11 @@ export interface Route< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > _addFileTypes: RouteAddFileTypesFn< + TStart, TParentRoute, TPath, TFullPath, @@ -688,7 +721,8 @@ export interface Route< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TChildren + TChildren, + TLifecycleSerialization > } @@ -706,6 +740,8 @@ export type AnyRoute = Route< any, any, any, + any, + any, any > @@ -714,6 +750,7 @@ export type AnyRouteWithContext = AnyRoute & { } export type RouteOptions< + TStart = StartRegister, TParentRoute extends AnyRoute = AnyRoute, TId extends string = string, TCustomId extends string = string, @@ -726,7 +763,9 @@ export type RouteOptions< TRouterContext = {}, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, + TLifecycleSerialization = unknown, > = BaseRouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -737,7 +776,8 @@ export type RouteOptions< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization > & UpdatableRouteOptions< NoInfer, @@ -752,49 +792,21 @@ export type RouteOptions< NoInfer > -export type RouteContextFn< - in out TParentRoute extends AnyRoute, - in out TSearchValidator, - in out TParams, - in out TRouterContext, -> = ( - ctx: RouteContextOptions< - TParentRoute, - TSearchValidator, - TParams, - TRouterContext - >, -) => any - -export type BeforeLoadFn< - in out TParentRoute extends AnyRoute, - in out TSearchValidator, - in out TParams, - in out TRouterContext, - in out TRouteContextFn, -> = ( - ctx: BeforeLoadContextOptions< - TParentRoute, - TSearchValidator, - TParams, - TRouterContext, - TRouteContextFn - >, -) => any - -export type FileBaseRouteOptions< - TParentRoute extends AnyRoute = AnyRoute, - TId extends string = string, - TPath extends string = string, - TSearchValidator = undefined, - TParams = {}, - TLoaderDeps extends Record = {}, - TLoaderFn = undefined, - TRouterContext = {}, - TRouteContextFn = AnyContext, - TBeforeLoadFn = AnyContext, - TRemountDepsFn = AnyContext, -> = ParamsOptions & { +export interface LifecycleRouteOptions< + in out TStart = StartRegister, + in out TParentRoute extends AnyRoute = AnyRoute, + in out TId extends string = string, + in out TPath extends string = string, + in out TSearchValidator = undefined, + in out TParams = {}, + in out TLoaderDeps extends Record = {}, + in out TLoaderFn = undefined, + in out TRouterContext = {}, + in out TRouteContextFn = AnyContext, + in out TBeforeLoadFn = AnyContext, + in out TRemountDepsFn = AnyContext, + in out TLifecycleSerialization = unknown, +> extends ParamsOptions { validateSearch?: Constrain shouldReload?: @@ -820,7 +832,12 @@ export type FileBaseRouteOptions< TRouterContext, TLoaderDeps >, - ) => any + ) => TypeSerializerStringifyReturnType< + TStart, + TLoaderFn, + TLifecycleSerialization, + 'context' + > > // This async function is called before a route is loaded. @@ -837,7 +854,12 @@ export type FileBaseRouteOptions< TRouterContext, TRouteContextFn >, - ) => any + ) => TypeSerializerStringifyReturnType< + TStart, + TBeforeLoadFn, + TLifecycleSerialization, + 'beforeLoad' + > > loaderDeps?: ( @@ -868,11 +890,22 @@ export type FileBaseRouteOptions< TRouteContextFn, TBeforeLoadFn >, - ) => any + ) => TypeSerializerStringifyReturnType< + TStart, + TLoaderFn, + TLifecycleSerialization, + 'loader' + > + > + + lifecycleSerialization?: Constrain< + TLifecycleSerialization, + LifecycleSerialization > } export type BaseRouteOptions< + TStart = StartRegister, TParentRoute extends AnyRoute = AnyRoute, TId extends string = string, TCustomId extends string = string, @@ -884,8 +917,10 @@ export type BaseRouteOptions< TRouterContext = {}, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, + TLifecycleSerialization = unknown, > = RoutePathOptions & - FileBaseRouteOptions< + LifecycleRouteOptions< + TStart, TParentRoute, TId, TPath, @@ -895,7 +930,8 @@ export type BaseRouteOptions< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization > & { getParentRoute: () => TParentRoute } @@ -1206,14 +1242,17 @@ export interface LoaderFnContext< } export type RootRouteOptions< + TStart = StartRegister, TSearchValidator = undefined, TRouterContext = {}, TRouteContextFn = AnyContext, TBeforeLoadFn = AnyContext, TLoaderDeps extends Record = {}, TLoaderFn = undefined, + TLifecycleSerialization = unknown, > = Omit< RouteOptions< + TStart, any, // TParentRoute RootRouteId, // TId RootRouteId, // TCustomId @@ -1225,7 +1264,8 @@ export type RootRouteOptions< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization >, | 'path' | 'id' @@ -1290,6 +1330,7 @@ export type NotFoundRouteProps = { } export class BaseRoute< + in out TStart = StartRegister, in out TParentRoute extends AnyRoute = AnyRoute, in out TPath extends string = '/', in out TFullPath extends string = ResolveFullPath, @@ -1304,9 +1345,11 @@ export class BaseRoute< in out TLoaderFn = undefined, in out TChildren = unknown, in out TFileRouteTypes = unknown, + in out TLifecycleSerialization = unknown, > { isRoot: TParentRoute extends AnyRoute ? true : false options: RouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -1318,7 +1361,8 @@ export class BaseRoute< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization > // The following properties are set up in this.init() @@ -1356,6 +1400,7 @@ export class BaseRoute< lazyFn?: () => Promise< LazyRoute< Route< + TStart, TParentRoute, TPath, TFullPath, @@ -1369,7 +1414,8 @@ export class BaseRoute< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > > > @@ -1378,6 +1424,7 @@ export class BaseRoute< constructor( options?: RouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -1389,7 +1436,8 @@ export class BaseRoute< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization >, ) { this.options = (options as any) || {} @@ -1414,7 +1462,8 @@ export class BaseRoute< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > init = (opts: { originalIndex: number; defaultSsr?: boolean }): void => { @@ -1422,6 +1471,7 @@ export class BaseRoute< const options = this.options as | (RouteOptions< + TStart, TParentRoute, TId, TCustomId, @@ -1433,7 +1483,8 @@ export class BaseRoute< TLoaderFn, TRouterContext, TRouteContextFn, - TBeforeLoadFn + TBeforeLoadFn, + TLifecycleSerialization > & RoutePathOptionsIntersection) | undefined @@ -1497,6 +1548,7 @@ export class BaseRoute< } addChildren: RouteAddChildrenFn< + TStart, TParentRoute, TPath, TFullPath, @@ -1509,12 +1561,14 @@ export class BaseRoute< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > = (children) => { return this._addFileChildren(children) as any } _addFileChildren: RouteAddFileChildrenFn< + TStart, TParentRoute, TPath, TFullPath, @@ -1527,7 +1581,8 @@ export class BaseRoute< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > = (children) => { if (Array.isArray(children)) { this.children = children as TChildren @@ -1541,6 +1596,7 @@ export class BaseRoute< } _addFileTypes: RouteAddFileTypesFn< + TStart, TParentRoute, TPath, TFullPath, @@ -1553,7 +1609,8 @@ export class BaseRoute< TBeforeLoadFn, TLoaderDeps, TLoaderFn, - TChildren + TChildren, + TLifecycleSerialization > = () => { return this as any } @@ -1574,6 +1631,7 @@ export class BaseRoute< }) => { Object.assign(this.options, options) return this as unknown as BaseRoute< + TStart, TParentRoute, TPath, TFullPath, @@ -1587,7 +1645,8 @@ export class BaseRoute< TLoaderDeps, TNewLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > } @@ -1611,6 +1670,7 @@ export class BaseRoute< lazy: RouteLazyFn< Route< + TStart, TParentRoute, TPath, TFullPath, @@ -1624,7 +1684,8 @@ export class BaseRoute< TLoaderDeps, TLoaderFn, TChildren, - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > > = (lazyFn) => { this.lazyFn = lazyFn @@ -1645,6 +1706,7 @@ export class BaseRouteApi { } export interface RootRoute< + in out TStart = StartRegister, in out TSearchValidator = undefined, in out TRouterContext = {}, in out TRouteContextFn = AnyContext, @@ -1653,7 +1715,9 @@ export interface RootRoute< in out TLoaderFn = undefined, in out TChildren = unknown, in out TFileRouteTypes = unknown, + in out TLifecycleSerialization = unknown, > extends Route< + TStart, any, // TParentRoute '/', // TPath '/', // TFullPath @@ -1667,10 +1731,12 @@ export interface RootRoute< TLoaderDeps, TLoaderFn, TChildren, // TChildren - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > {} export class BaseRootRoute< + in out TStart = StartRegister, in out TSearchValidator = undefined, in out TRouterContext = {}, in out TRouteContextFn = AnyContext, @@ -1679,7 +1745,9 @@ export class BaseRootRoute< in out TLoaderFn = undefined, in out TChildren = unknown, in out TFileRouteTypes = unknown, + in out TLifecycleSerialization = unknown, > extends BaseRoute< + TStart, any, // TParentRoute '/', // TPath '/', // TFullPath @@ -1693,16 +1761,19 @@ export class BaseRootRoute< TLoaderDeps, TLoaderFn, TChildren, // TChildren - TFileRouteTypes + TFileRouteTypes, + TLifecycleSerialization > { constructor( options?: RootRouteOptions< + TStart, TSearchValidator, TRouterContext, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, - TLoaderFn + TLoaderFn, + TLifecycleSerialization >, ) { super(options as any) diff --git a/packages/router-core/src/serializer.ts b/packages/router-core/src/serializer.ts index 58b886bd89..c9844e6df0 100644 --- a/packages/router-core/src/serializer.ts +++ b/packages/router-core/src/serializer.ts @@ -1,3 +1,6 @@ +import type { Plugin } from 'seroval' +import type { Constrain } from './utils' + export interface StartSerializer { stringify: (obj: unknown) => string parse: (str: string) => unknown @@ -5,19 +8,29 @@ export interface StartSerializer { decode: (value: T) => T } -export type SerializerStringifyBy = T extends TSerializable - ? T - : T extends (...args: Array) => any - ? 'Function is not serializable' - : { [K in keyof T]: SerializerStringifyBy } +// help me fill in the runtime +export interface Serializer { + '~types': { + serializer: unknown + } + stringify: (obj: unknown) => string + parse: (str: string) => unknown +} + +export type TypeSerializerStringifyBy = + T extends TSerializable + ? T + : T extends (...args: Array) => any + ? 'Function is not serializable' + : { [K in keyof T]: TypeSerializerStringifyBy } -export type SerializerParseBy = T extends TSerializable +export type TypeSerializerParseBy = T extends TSerializable ? T : unknown extends SerializerExtensions['ReadableStream'] - ? { [K in keyof T]: SerializerParseBy } + ? { [K in keyof T]: TypeSerializerParseBy } : T extends SerializerExtensions['ReadableStream'] ? ReadableStream - : { [K in keyof T]: SerializerParseBy } + : { [K in keyof T]: TypeSerializerParseBy } export interface DefaultSerializerExtensions { ReadableStream: unknown @@ -25,8 +38,117 @@ export interface DefaultSerializerExtensions { export interface SerializerExtensions extends DefaultSerializerExtensions {} -export type Serializable = Date | undefined | Error | FormData | bigint +export type DefaultTypeSerializable = + | Date + | undefined + | Error + | FormData + | bigint + +export type DefaultTypeSerializerStringify = TypeSerializerStringifyBy< + T, + DefaultTypeSerializable +> + +export type DefaultTypeSerializerParse = TypeSerializerParseBy< + T, + DefaultTypeSerializable +> + +export interface TypeSerializer { + value: unknown + parse: unknown + stringify: unknown +} + +export interface TypeSerializerValue { + value: T +} + +export type TypeSerializerApply< + TSerializer extends TypeSerializer, + T, +> = TSerializer & TypeSerializerValue + +export interface DefaultTypeSerializer extends TypeSerializer { + parse: DefaultTypeSerializerParse + stringify: DefaultTypeSerializerStringify +} + +export type TypeSerializerParse< + TSerializer extends TypeSerializer, + T, +> = TypeSerializerApply['parse'] + +export type TypeSerializerStringify< + TSerializer extends TypeSerializer, + T, +> = TypeSerializerApply['stringify'] + +export interface SerovalSerializerOptions { + plugins?: Constrain>> +} + +export const serovalSerializer = ( + options?: SerovalSerializerOptions, +): SerovalSerializer => { + // please help with the runtime + return undefined as any +} + +export interface SerovalSerializer extends Serializer { + '~types': { + serializer: SerovalTypeSerializer + } +} + +export interface SerovalTypeSerializer extends TypeSerializer { + stringify: SerovalTypeStringify + parse: DefaultTypeSerializerParse +} + +export type SerovalTypeStringify = unknown extends T + ? T + : T extends SerovalTypeSerializable + ? T + : T extends (...args: Array) => any + ? 'Function is not serializable' + : T extends Set + ? SerovalTypeSetStringify + : T extends Map + ? SerovalTypeMapStringify + : { [K in keyof T]: SerovalTypeStringify } + +export type SerovalTypeSerializable = + | RegExp + | Date + | undefined + | string + | number + | bigint + | Error + | null + | SerovalPluginsTypeSerializable + +export type SerovalTypeSetStringify = + T extends Set + ? Set> + : never -export type SerializerStringify = SerializerStringifyBy +export type SerovalTypeMapStringify = + T extends Map + ? Map< + SerovalTypeStringify, + SerovalTypeStringify + > + : never -export type SerializerParse = SerializerParseBy +export type SerovalPluginsTypeSerializable = unknown extends TPlugins + ? never + : TPlugins extends ReadonlyArray> + ? TPlugins[number] extends infer TPlugin + ? TPlugin extends Plugin + ? TValue + : never + : never + : never diff --git a/packages/router-core/src/start.ts b/packages/router-core/src/start.ts new file mode 100644 index 0000000000..f186dd17aa --- /dev/null +++ b/packages/router-core/src/start.ts @@ -0,0 +1,77 @@ +import type { + Serializer, + SerovalTypeSerializer, + TypeSerializerStringify, +} from './serializer' +import type { LooseAsyncReturnType, LooseReturnType } from './utils' + +export interface StartConfigTypes { + serializer: TSerializer +} + +export interface StartConfig { + _types: StartConfigTypes +} + +export type AnyStartConfig = StartConfig + +export interface CreateStartConfig { + serializer: TSerializer +} + +export interface DefaultStartRegister { + start: AnyStartConfig + ssr: boolean +} + +export interface StartRegister extends DefaultStartRegister {} + +export type SSREnabled = TStart extends DefaultStartRegister + ? TStart['ssr'] + : never + +export type InferSerializer = TStart extends DefaultStartRegister + ? unknown extends TStart['start']['_types']['serializer'] + ? SerovalTypeSerializer + : TStart['start']['_types']['serializer']['~types']['serializer'] + : never + +export type TypeSerializerStringifyReturnType< + TStart, + TFn, + TLifecycleSerialization, + TLifecycle extends keyof LifecycleSerialization, +> = + false extends SSREnabled + ? any + : false extends IsSerializationEnabled + ? any + : TFn extends (...args: Array) => Promise + ? Promise< + TypeSerializerStringify< + InferSerializer, + LooseAsyncReturnType + > + > + : TypeSerializerStringify, LooseReturnType> + +export interface LifecycleSerialization { + context?: boolean + beforeLoad?: boolean + loader?: boolean +} + +export interface DefaultLifecycleSerialization { + context: false + beforeLoad: true + loader: true +} + +export type IsSerializationEnabled< + TLifecycleSerialization, + TLifecycle extends keyof LifecycleSerialization, +> = TLifecycleSerialization extends LifecycleSerialization + ? unknown extends TLifecycleSerialization[TLifecycle] + ? DefaultLifecycleSerialization[TLifecycle] + : TLifecycleSerialization[TLifecycle] + : DefaultLifecycleSerialization[TLifecycle] diff --git a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx index a84f318bc6..039177c28d 100644 --- a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx +++ b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx @@ -98,6 +98,7 @@ function RouteComp({ routerState: Accessor< RouterState< Route< + any, any, '/', '/', @@ -111,7 +112,8 @@ function RouteComp({ {}, undefined, any, - FileRouteTypes + FileRouteTypes, + any >, MakeRouteMatchUnion > diff --git a/packages/solid-router/src/index.tsx b/packages/solid-router/src/index.tsx index 2d7204db62..a7daa25b35 100644 --- a/packages/solid-router/src/index.tsx +++ b/packages/solid-router/src/index.tsx @@ -39,11 +39,11 @@ export { export type { StartSerializer, - Serializable, - SerializerParse, - SerializerParseBy, - SerializerStringify, - SerializerStringifyBy, + DefaultTypeSerializable, + TypeSerializerParse, + TypeSerializerParseBy, + TypeSerializerStringify, + TypeSerializerStringifyBy, DeferredPromiseState, DeferredPromise, ParsedLocation, diff --git a/packages/start-client-core/src/createMiddleware.ts b/packages/start-client-core/src/createMiddleware.ts index 357adea2f2..1ac65896fe 100644 --- a/packages/start-client-core/src/createMiddleware.ts +++ b/packages/start-client-core/src/createMiddleware.ts @@ -7,31 +7,39 @@ import type { import type { Assign, Constrain, + DefaultStartRegister, Expand, + InferSerializer, IntersectAssign, ResolveValidatorInput, ResolveValidatorOutput, - SerializerStringify, + StartRegister, + TypeSerializerStringify, } from '@tanstack/router-core' -export function createMiddleware( +export function createMiddleware< + TType extends MiddlewareType, + TStart extends DefaultStartRegister = StartRegister, +>( options: { type: TType validateClient?: boolean }, __opts?: FunctionMiddlewareOptions< + TStart, unknown, undefined, undefined, undefined, ServerFnResponseType >, -): CreateMiddlewareResult { +): CreateMiddlewareResult { // const resolvedOptions = (__opts || options) as MiddlewareOptions< const resolvedOptions = { type: 'function', ...(__opts || (options as FunctionMiddlewareOptions< + TStart, unknown, undefined, undefined, @@ -66,31 +74,44 @@ export function createMiddleware( Object.assign(resolvedOptions, { server }), ) as any }, - } as unknown as CreateMiddlewareResult + } as unknown as CreateMiddlewareResult } export type MiddlewareType = 'request' | 'function' -export type CreateMiddlewareResult = - 'function' extends TType - ? FunctionMiddleware - : RequestMiddleware +export type CreateMiddlewareResult< + TStart extends DefaultStartRegister, + TType extends MiddlewareType, +> = 'function' extends TType + ? FunctionMiddleware + : RequestMiddleware export interface FunctionMiddleware< + TStart extends DefaultStartRegister, TServerFnResponseType extends ServerFnResponseType, -> extends FunctionMiddlewareAfterMiddleware { +> extends FunctionMiddlewareAfterMiddleware< + TStart, + unknown, + TServerFnResponseType + > { middleware: ( middlewares: Constrain< TNewMiddlewares, ReadonlyArray >, - ) => FunctionMiddlewareAfterMiddleware + ) => FunctionMiddlewareAfterMiddleware< + TStart, + TNewMiddlewares, + TServerFnResponseType + > } export interface FunctionMiddlewareAfterMiddleware< + TStart extends DefaultStartRegister, TMiddlewares, TServerFnResponseType extends ServerFnResponseType, > extends FunctionMiddlewareWithTypes< + TStart, TMiddlewares, undefined, undefined, @@ -100,16 +121,23 @@ export interface FunctionMiddlewareAfterMiddleware< TServerFnResponseType >, FunctionMiddlewareServer< + TStart, TMiddlewares, undefined, undefined, undefined, TServerFnResponseType >, - FunctionMiddlewareClient, - FunctionMiddlewareValidator {} + FunctionMiddlewareClient< + TStart, + TMiddlewares, + undefined, + TServerFnResponseType + >, + FunctionMiddlewareValidator {} export interface FunctionMiddlewareWithTypes< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerContext, @@ -127,6 +155,7 @@ export interface FunctionMiddlewareWithTypes< TClientSendContext > options: FunctionMiddlewareOptions< + TStart, TMiddlewares, TValidator, TServerContext, @@ -217,6 +246,7 @@ export type AnyFunctionMiddleware = FunctionMiddlewareWithTypes< any, any, any, + any, any > @@ -308,6 +338,7 @@ export type AssignAllClientSendContext< > export interface FunctionMiddlewareOptions< + in out TStart extends DefaultStartRegister, in out TMiddlewares, in out TValidator, in out TServerContext, @@ -318,6 +349,7 @@ export interface FunctionMiddlewareOptions< middleware?: TMiddlewares validator?: ConstrainValidator client?: FunctionMiddlewareClientFn< + TStart, TMiddlewares, TValidator, TServerContext, @@ -325,6 +357,7 @@ export interface FunctionMiddlewareOptions< TServerFnResponseType > server?: FunctionMiddlewareServerFn< + TStart, TMiddlewares, TValidator, TServerContext, @@ -334,18 +367,19 @@ export interface FunctionMiddlewareOptions< > } -export type FunctionMiddlewareClientNextFn = < - TSendContext = undefined, - TNewClientContext = undefined, ->(ctx?: { +export type FunctionMiddlewareClientNextFn< + TStart extends DefaultStartRegister, + TMiddlewares, +> = (ctx?: { context?: TNewClientContext - sendContext?: SerializerStringify + sendContext?: TypeSerializerStringify, TSendContext> headers?: HeadersInit }) => Promise< FunctionClientResultWithContext > export interface FunctionMiddlewareServer< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerSendContext, @@ -354,6 +388,7 @@ export interface FunctionMiddlewareServer< > { server: ( server: FunctionMiddlewareServerFn< + TStart, TMiddlewares, TValidator, TServerSendContext, @@ -362,6 +397,7 @@ export interface FunctionMiddlewareServer< TServerFnResponseType >, ) => FunctionMiddlewareAfterServer< + TStart, TMiddlewares, TValidator, TNewServerContext, @@ -371,7 +407,9 @@ export interface FunctionMiddlewareServer< ServerFnResponseType > } + export type FunctionMiddlewareServerFn< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerSendContext, @@ -380,6 +418,7 @@ export type FunctionMiddlewareServerFn< TServerFnResponseType extends ServerFnResponseType, > = ( options: FunctionMiddlewareServerFnOptions< + TStart, TMiddlewares, TValidator, TServerSendContext, @@ -393,23 +432,25 @@ export type FunctionMiddlewareServerFn< > export interface RequestMiddlewareServerFnOptions< + in out TStart extends DefaultStartRegister, in out TMiddlewares, in out TServerSendContext, > { request: Request context: Expand> - next: FunctionMiddlewareServerNextFn + next: FunctionMiddlewareServerNextFn response: Response method: Method signal: AbortSignal } -export type FunctionMiddlewareServerNextFn = < - TNewServerContext = undefined, - TSendContext = undefined, ->(ctx?: { +export type FunctionMiddlewareServerNextFn< + TStart extends DefaultStartRegister, + TMiddlewares, + TServerSendContext, +> = (ctx?: { context?: TNewServerContext - sendContext?: SerializerStringify + sendContext?: TypeSerializerStringify, TSendContext> }) => Promise< FunctionServerResultWithContext< TMiddlewares, @@ -437,6 +478,7 @@ export type FunctionServerResultWithContext< } export interface FunctionMiddlewareServerFnOptions< + in out TStart extends DefaultStartRegister, in out TMiddlewares, in out TValidator, in out TServerSendContext, @@ -444,7 +486,7 @@ export interface FunctionMiddlewareServerFnOptions< > { data: Expand> context: Expand> - next: FunctionMiddlewareServerNextFn + next: FunctionMiddlewareServerNextFn response: TServerFnResponseType method: Method filename: string @@ -474,6 +516,7 @@ export type FunctionMiddlewareServerFnResult< > export interface FunctionMiddlewareAfterServer< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerContext, @@ -482,6 +525,7 @@ export interface FunctionMiddlewareAfterServer< TClientSendContext, TServerFnResponseType extends ServerFnResponseType, > extends FunctionMiddlewareWithTypes< + TStart, TMiddlewares, TValidator, TServerContext, @@ -492,12 +536,14 @@ export interface FunctionMiddlewareAfterServer< > {} export interface FunctionMiddlewareClient< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerFnResponseType extends ServerFnResponseType, > { client: ( client: FunctionMiddlewareClientFn< + TStart, TMiddlewares, TValidator, TSendServerContext, @@ -505,6 +551,7 @@ export interface FunctionMiddlewareClient< TServerFnResponseType >, ) => FunctionMiddlewareAfterClient< + TStart, TMiddlewares, TValidator, TSendServerContext, @@ -514,6 +561,7 @@ export interface FunctionMiddlewareClient< } export type FunctionMiddlewareClientFn< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TSendContext, @@ -521,6 +569,7 @@ export type FunctionMiddlewareClientFn< TServerFnResponseType extends ServerFnResponseType, > = ( options: FunctionMiddlewareClientFnOptions< + TStart, TMiddlewares, TValidator, TServerFnResponseType @@ -532,6 +581,7 @@ export type FunctionMiddlewareClientFn< > export interface FunctionMiddlewareClientFnOptions< + in out TStart extends DefaultStartRegister, in out TMiddlewares, in out TValidator, in out TServerFnResponseType extends ServerFnResponseType, @@ -542,7 +592,7 @@ export interface FunctionMiddlewareClientFnOptions< method: Method response: TServerFnResponseType signal: AbortSignal - next: FunctionMiddlewareClientNextFn + next: FunctionMiddlewareClientNextFn filename: string functionId: string type: ServerFnTypeOrTypeFn< @@ -579,12 +629,14 @@ export type FunctionClientResultWithContext< } export interface FunctionMiddlewareAfterClient< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerSendContext, TClientContext, TServerFnResponseType extends ServerFnResponseType, > extends FunctionMiddlewareWithTypes< + TStart, TMiddlewares, TValidator, undefined, @@ -594,6 +646,7 @@ export interface FunctionMiddlewareAfterClient< TServerFnResponseType >, FunctionMiddlewareServer< + TStart, TMiddlewares, TValidator, TServerSendContext, @@ -602,12 +655,14 @@ export interface FunctionMiddlewareAfterClient< > {} export interface FunctionMiddlewareValidator< + TStart extends DefaultStartRegister, TMiddlewares, TServerFnResponseType extends ServerFnResponseType, > { validator: ( input: ConstrainValidator, ) => FunctionMiddlewareAfterValidator< + TStart, TMiddlewares, TNewValidator, TServerFnResponseType @@ -615,10 +670,12 @@ export interface FunctionMiddlewareValidator< } export interface FunctionMiddlewareAfterValidator< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TServerFnResponseType extends ServerFnResponseType, > extends FunctionMiddlewareWithTypes< + TStart, TMiddlewares, TValidator, undefined, @@ -628,13 +685,19 @@ export interface FunctionMiddlewareAfterValidator< ServerFnResponseType >, FunctionMiddlewareServer< + TStart, TMiddlewares, TValidator, undefined, undefined, TServerFnResponseType >, - FunctionMiddlewareClient {} + FunctionMiddlewareClient< + TStart, + TMiddlewares, + TValidator, + ServerFnResponseType + > {} export interface RequestMiddleware extends RequestMiddlewareAfterMiddleware { diff --git a/packages/start-client-core/src/createServerFn.ts b/packages/start-client-core/src/createServerFn.ts index 14c1b26001..3b32ffdde6 100644 --- a/packages/start-client-core/src/createServerFn.ts +++ b/packages/start-client-core/src/createServerFn.ts @@ -7,11 +7,14 @@ import { globalMiddleware } from './registerGlobalMiddleware' import type { AnyValidator, Constrain, + DefaultStartRegister, Expand, + InferSerializer, ResolveValidatorInput, - SerializerParse, - SerializerStringify, - SerializerStringifyBy, + StartRegister, + TypeSerializerParse, + TypeSerializerStringify, + TypeSerializerStringifyBy, Validator, } from '@tanstack/router-core' import type { Readable } from 'node:stream' @@ -33,6 +36,7 @@ export function createServerFn< TResponse = unknown, TMiddlewares = undefined, TValidator = undefined, + TStart extends DefaultStartRegister = StartRegister, >( options?: { method?: TMethod @@ -40,14 +44,16 @@ export function createServerFn< type?: ServerFnType }, __opts?: ServerFnBaseOptions< + TStart, TMethod, TServerFnResponseType, TResponse, TMiddlewares, TValidator >, -): ServerFnBuilder { +): ServerFnBuilder { const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions< + TStart, TMethod, ServerFnResponseType, TResponse, @@ -67,7 +73,8 @@ export function createServerFn< ServerFnResponseType, TResponse, TMiddlewares, - TValidator + TValidator, + TStart >(undefined, Object.assign(resolvedOptions, { middleware })) as any }, validator: (validator) => { @@ -76,7 +83,8 @@ export function createServerFn< ServerFnResponseType, TResponse, TMiddlewares, - TValidator + TValidator, + TStart >(undefined, Object.assign(resolvedOptions, { validator })) as any }, type: (type) => { @@ -85,7 +93,8 @@ export function createServerFn< ServerFnResponseType, TResponse, TMiddlewares, - TValidator + TValidator, + TStart >(undefined, Object.assign(resolvedOptions, { type })) as any }, handler: (...args) => { @@ -93,8 +102,9 @@ export function createServerFn< // in the babel plugin. We need to cast it to the correct // function signature post-transformation const [extractedFn, serverFn] = args as unknown as [ - CompiledFetcherFn, + CompiledFetcherFn, ServerFn< + TStart, TMethod, TServerFnResponseType, TMiddlewares, @@ -113,7 +123,7 @@ export function createServerFn< const resolvedMiddleware = [ ...(resolvedOptions.middleware || []), - serverFnBaseToMiddleware(resolvedOptions), + serverFnBaseToMiddleware(resolvedOptions as never), ] // We want to make sure the new function has the same @@ -292,6 +302,7 @@ export type CompiledFetcherFnOptions = { } export type Fetcher< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TResponse, @@ -299,12 +310,14 @@ export type Fetcher< > = undefined extends IntersectAllValidatorInputs ? OptionalFetcher< + TStart, TMiddlewares, TValidator, TResponse, TServerFnResponseType > : RequiredFetcher< + TStart, TMiddlewares, TValidator, TResponse, @@ -324,16 +337,18 @@ export interface FetcherBase { } export type FetchResult< + TStart extends DefaultStartRegister, TMiddlewares, TResponse, TServerFnResponseType extends ServerFnResponseType, > = TServerFnResponseType extends 'raw' ? Promise : TServerFnResponseType extends 'full' - ? Promise> - : Promise> + ? Promise> + : Promise> export interface OptionalFetcher< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TResponse, @@ -341,10 +356,11 @@ export interface OptionalFetcher< > extends FetcherBase { ( options?: OptionalFetcherDataOptions, - ): FetchResult + ): FetchResult } export interface RequiredFetcher< + TStart extends DefaultStartRegister, TMiddlewares, TValidator, TResponse, @@ -352,7 +368,7 @@ export interface RequiredFetcher< > extends FetcherBase { ( opts: RequiredFetcherDataOptions, - ): FetchResult + ): FetchResult } export type FetcherBaseOptions = { @@ -373,16 +389,23 @@ export interface RequiredFetcherDataOptions data: Expand> } -export interface FullFetcherData { +export interface FullFetcherData< + TStart extends DefaultStartRegister, + TMiddlewares, + TResponse, +> { error: unknown - result: FetcherData + result: FetcherData context: AssignAllClientSendContext } -export type FetcherData = +export type FetcherData = TResponse extends JsonResponse - ? SerializerParse> - : SerializerParse + ? TypeSerializerParse< + InferSerializer, + ReturnType + > + : TypeSerializerParse, TResponse> export type RscStream = { __cacheState: T @@ -395,13 +418,17 @@ export type ServerFnResponseType = 'data' | 'full' | 'raw' export type RawResponse = Response | ReadableStream | Readable | null | string export type ServerFnReturnType< + TStart extends DefaultStartRegister, TServerFnResponseType extends ServerFnResponseType, TResponse, > = TServerFnResponseType extends 'raw' ? RawResponse | Promise - : Promise> | SerializerStringify + : + | Promise, TResponse>> + | TypeSerializerStringify, TResponse> export type ServerFn< + TStart extends DefaultStartRegister, TMethod, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, @@ -409,13 +436,13 @@ export type ServerFn< TResponse, > = ( ctx: ServerFnCtx, -) => ServerFnReturnType +) => ServerFnReturnType export interface ServerFnCtx< - TMethod, - TServerFnResponseType extends ServerFnResponseType, - TMiddlewares, - TValidator, + in out TMethod, + in out TServerFnResponseType extends ServerFnResponseType, + in out TMiddlewares, + in out TValidator, > { method: TMethod response: TServerFnResponseType @@ -425,17 +452,19 @@ export interface ServerFnCtx< } export type CompiledFetcherFn< + TStart extends DefaultStartRegister, TResponse, TServerFnResponseType extends ServerFnResponseType, > = { ( opts: CompiledFetcherFnOptions & - ServerFnBaseOptions, + ServerFnBaseOptions, ): Promise url: string } export type ServerFnBaseOptions< + TStart extends DefaultStartRegister, TMethod extends Method = 'GET', TServerFnResponseType extends ServerFnResponseType = 'data', TResponse = unknown, @@ -447,8 +476,9 @@ export type ServerFnBaseOptions< validateClient?: boolean middleware?: Constrain> validator?: ConstrainValidator - extractedFn?: CompiledFetcherFn + extractedFn?: CompiledFetcherFn serverFn?: ServerFn< + TStart, TMethod, TServerFnResponseType, TMiddlewares, @@ -464,7 +494,7 @@ export type ServerFnBaseOptions< > } -export type ValidatorInputStringify = SerializerStringifyBy< +export type ValidatorInputStringify = TypeSerializerStringifyBy< ResolveValidatorInput, Date | undefined | FormData > @@ -483,6 +513,7 @@ export type ConstrainValidator = | ValidatorSerializerStringify export interface ServerFnMiddleware< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TValidator, @@ -493,6 +524,7 @@ export interface ServerFnMiddleware< ReadonlyArray >, ) => ServerFnAfterMiddleware< + TStart, TMethod, TServerFnResponseType, TNewMiddlewares, @@ -501,21 +533,41 @@ export interface ServerFnMiddleware< } export interface ServerFnAfterMiddleware< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, TValidator, -> extends ServerFnValidator, - ServerFnTyper, - ServerFnHandler {} +> extends ServerFnValidator< + TStart, + TMethod, + TServerFnResponseType, + TMiddlewares + >, + ServerFnTyper< + TStart, + TMethod, + TServerFnResponseType, + TMiddlewares, + TValidator + >, + ServerFnHandler< + TStart, + TMethod, + TServerFnResponseType, + TMiddlewares, + TValidator + > {} export type ValidatorFn< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, > = ( validator: ConstrainValidator, ) => ServerFnAfterValidator< + TStart, TMethod, TServerFnResponseType, TMiddlewares, @@ -523,24 +575,44 @@ export type ValidatorFn< > export interface ServerFnValidator< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, > { - validator: ValidatorFn + validator: ValidatorFn } export interface ServerFnAfterValidator< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, TValidator, -> extends ServerFnMiddleware, - ServerFnTyper, - ServerFnHandler {} +> extends ServerFnMiddleware< + TStart, + TMethod, + TServerFnResponseType, + TValidator + >, + ServerFnTyper< + TStart, + TMethod, + TServerFnResponseType, + TMiddlewares, + TValidator + >, + ServerFnHandler< + TStart, + TMethod, + TServerFnResponseType, + TMiddlewares, + TValidator + > {} // Typer export interface ServerFnTyper< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, @@ -554,6 +626,7 @@ export interface ServerFnTyper< TValidator >, ) => ServerFnAfterTyper< + TStart, TMethod, TServerFnResponseType, TMiddlewares, @@ -578,11 +651,13 @@ export type ServerFnTypeOrTypeFn< ) => ServerFnType) export interface ServerFnAfterTyper< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, TValidator, > extends ServerFnHandler< + TStart, TMethod, TServerFnResponseType, TMiddlewares, @@ -591,6 +666,7 @@ export interface ServerFnAfterTyper< // Handler export interface ServerFnHandler< + TStart extends DefaultStartRegister, TMethod extends Method, TServerFnResponseType extends ServerFnResponseType, TMiddlewares, @@ -598,23 +674,38 @@ export interface ServerFnHandler< > { handler: ( fn?: ServerFn< + TStart, TMethod, TServerFnResponseType, TMiddlewares, TValidator, TNewResponse >, - ) => Fetcher + ) => Fetcher< + TStart, + TMiddlewares, + TValidator, + TNewResponse, + TServerFnResponseType + > } export interface ServerFnBuilder< + TStart extends DefaultStartRegister, TMethod extends Method = 'GET', TServerFnResponseType extends ServerFnResponseType = 'data', -> extends ServerFnMiddleware, - ServerFnValidator, - ServerFnTyper, - ServerFnHandler { +> extends ServerFnMiddleware, + ServerFnValidator, + ServerFnTyper, + ServerFnHandler< + TStart, + TMethod, + TServerFnResponseType, + undefined, + undefined + > { options: ServerFnBaseOptions< + TStart, TMethod, TServerFnResponseType, unknown, diff --git a/packages/start-client-core/src/createStart.ts b/packages/start-client-core/src/createStart.ts new file mode 100644 index 0000000000..19844de8a8 --- /dev/null +++ b/packages/start-client-core/src/createStart.ts @@ -0,0 +1,17 @@ +import type { + CreateStartConfig as CreateStartConfigCore, + Serializer, + StartConfig as StartConfigCore, +} from '@tanstack/router-core' + +export interface CreateStartConfig + extends CreateStartConfigCore {} + +export interface StartConfig + extends StartConfigCore {} + +export const createStart = ( + config: CreateStartConfig, +): StartConfig => { + return undefined as unknown as StartConfig +} diff --git a/packages/start-client-core/src/index.tsx b/packages/start-client-core/src/index.tsx index 5c409dab00..84fec1f42a 100644 --- a/packages/start-client-core/src/index.tsx +++ b/packages/start-client-core/src/index.tsx @@ -86,3 +86,9 @@ export { serverFnStaticCache, executeMiddleware, } from './createServerFn' + +export type { StartRegister as Register } from '@tanstack/router-core' + +export type { CreateStartConfig, StartConfig } from './createStart' + +export { createStart } from './createStart' diff --git a/packages/start-client-core/src/tests/createServerMiddleware.test-d.ts b/packages/start-client-core/src/tests/createServerMiddleware.test-d.ts index 5e61c970c8..0b6b3f2cdf 100644 --- a/packages/start-client-core/src/tests/createServerMiddleware.test-d.ts +++ b/packages/start-client-core/src/tests/createServerMiddleware.test-d.ts @@ -442,7 +442,7 @@ test('createMiddleware merges server context and client context, sends server co fromClient3: string fromClient4: string } - sendContext: { toServer1: 'toServer1' } + sendContext: { toServer1: string } headers: HeadersInit }>() @@ -453,7 +453,7 @@ test('createMiddleware merges server context and client context, sends server co fromServer1: string fromServer2: string fromServer3: string - toServer1: 'toServer1' + toServer1: string }>() const result = await options.next({ @@ -468,7 +468,7 @@ test('createMiddleware merges server context and client context, sends server co fromServer4: string } sendContext: { - toClient1: 'toClient1' + toClient1: string } } context: { @@ -476,9 +476,9 @@ test('createMiddleware merges server context and client context, sends server co fromServer2: string fromServer3: string fromServer4: string - toServer1: 'toServer1' + toServer1: string } - sendContext: { toClient1: 'toClient1' } + sendContext: { toClient1: string } }>() return result @@ -507,9 +507,9 @@ test('createMiddleware merges server context and client context, sends server co fromClient3: string fromClient4: string fromClient5: string - toClient1: 'toClient1' + toClient1: string } - sendContext: { toServer1: 'toServer1'; toServer2: 'toServer2' } + sendContext: { toServer1: string; toServer2: string } headers: HeadersInit }>() @@ -521,8 +521,8 @@ test('createMiddleware merges server context and client context, sends server co fromServer2: string fromServer3: string fromServer4: string - toServer1: 'toServer1' - toServer2: 'toServer2' + toServer1: string + toServer2: string }>() const result = await options.next({ @@ -537,7 +537,7 @@ test('createMiddleware merges server context and client context, sends server co fromServer5: string } sendContext: { - toClient2: 'toClient2' + toClient2: string } } context: { @@ -546,10 +546,10 @@ test('createMiddleware merges server context and client context, sends server co fromServer3: string fromServer4: string fromServer5: string - toServer1: 'toServer1' - toServer2: 'toServer2' + toServer1: string + toServer2: string } - sendContext: { toClient1: 'toClient1'; toClient2: 'toClient2' } + sendContext: { toClient1: string; toClient2: string } }>() return result diff --git a/packages/start-server-core/src/index.tsx b/packages/start-server-core/src/index.tsx index 9337b7c4cd..6e8cc456ba 100644 --- a/packages/start-server-core/src/index.tsx +++ b/packages/start-server-core/src/index.tsx @@ -20,3 +20,9 @@ export * from './h3' export { createServerRoute, createServerFileRoute } from './serverRoute' export type { CreateServerFileRoute } from './serverRoute' + +declare module '@tanstack/router-core' { + export interface StartRegister { + ssr: true + } +} diff --git a/packages/start-server-functions-fetcher/src/index.ts b/packages/start-server-functions-fetcher/src/index.ts index 8908d0652c..03b72bb56e 100644 --- a/packages/start-server-functions-fetcher/src/index.ts +++ b/packages/start-server-functions-fetcher/src/index.ts @@ -18,7 +18,12 @@ export async function serverFnFetcher( // If createServerFn was used to wrap the fetcher, // We need to handle the arguments differently if (isPlainObject(_first) && _first.method) { - const first = _first as FunctionMiddlewareClientFnOptions & { + const first = _first as FunctionMiddlewareClientFnOptions< + any, + any, + any, + any + > & { headers: HeadersInit } const type = first.data instanceof FormData ? 'formData' : 'payload' @@ -92,7 +97,7 @@ export async function serverFnFetcher( } function getFetcherRequestOptions( - opts: FunctionMiddlewareClientFnOptions, + opts: FunctionMiddlewareClientFnOptions, ) { if (opts.method === 'POST') { if (opts.data instanceof FormData) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26c3cc387a..a16aafd83e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4297,6 +4297,12 @@ importers: react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) + seroval: + specifier: ^1.3.1 + version: 1.3.1 + seroval-plugins: + specifier: ^1.3.1 + version: 1.3.1(seroval@1.3.1) tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -6053,6 +6059,9 @@ importers: '@tanstack/store': specifier: ^0.7.0 version: 0.7.0 + seroval: + specifier: ^1.3.1 + version: 1.3.1 tiny-invariant: specifier: ^1.3.3 version: 1.3.3 @@ -13974,14 +13983,14 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - seroval-plugins@1.2.0: - resolution: {integrity: sha512-hULTbfzSe81jGWLH8TAJjkEvw6JWMqOo9Uq+4V4vg+HNq53hyHldM9ZOfjdzokcFysiTp9aFdV2vJpZFqKeDjQ==} + seroval-plugins@1.3.1: + resolution: {integrity: sha512-dOlUoiI3fgZbQIcj6By+l865pzeWdP3XCSLdI3xlKnjCk5983yLWPsXytFOUI0BUZKG9qwqbj78n9yVcVwUqaQ==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.2.0: - resolution: {integrity: sha512-GURoU99ko2UiAgUC3qDCk59Jb3Ss4Po8VIMGkG8j5PFo2Q7y0YSMP8QG9NuL/fJCoTz9V1XZUbpNIMXPOfaGpA==} + seroval@1.3.1: + resolution: {integrity: sha512-F+T9EQPdLzgdewgxnBh4mSc+vde+EOkU6dC9BDuu/bfGb+UyUlqM6t8znFCTPQSuai/ZcfFg0gu79h+bVW2O0w==} engines: {node: '>=10'} serve-index@1.9.1: @@ -23207,11 +23216,11 @@ snapshots: dependencies: randombytes: 2.1.0 - seroval-plugins@1.2.0(seroval@1.2.0): + seroval-plugins@1.3.1(seroval@1.3.1): dependencies: - seroval: 1.2.0 + seroval: 1.3.1 - seroval@1.2.0: {} + seroval@1.3.1: {} serve-index@1.9.1: dependencies: @@ -23345,8 +23354,8 @@ snapshots: solid-js@1.9.5: dependencies: csstype: 3.1.3 - seroval: 1.2.0 - seroval-plugins: 1.2.0(seroval@1.2.0) + seroval: 1.3.1 + seroval-plugins: 1.3.1(seroval@1.3.1) solid-refresh@0.6.3(solid-js@1.9.5): dependencies: