diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx index 79987033b83..6b2d9bec3e0 100644 --- a/packages/react-router/src/index.tsx +++ b/packages/react-router/src/index.tsx @@ -290,7 +290,13 @@ export { export type { SearchSerializer, SearchParser } from './searchParams' export { defaultTransformer } from './transformer' -export type { RouterTransformer } from './transformer' +export type { + RouterTransformer, + TransformerParse, + TransformerStringify, + DefaultTransformerParse, + DefaultTransformerStringify, +} from './transformer' export { useBlocker, Block } from './useBlocker' diff --git a/packages/react-router/src/transformer.ts b/packages/react-router/src/transformer.ts index 2277a3dbb8d..15a6af55e77 100644 --- a/packages/react-router/src/transformer.ts +++ b/packages/react-router/src/transformer.ts @@ -47,3 +47,22 @@ const transformers = [ parse: () => undefined, }, ] as const + +export type TransformerStringify = T extends TSerializable + ? T + : T extends (...args: Array) => any + ? 'Function is not serializable' + : { [K in keyof T]: TransformerStringify } + +export type TransformerParse = T extends TSerializable + ? T + : T extends JSX.Element + ? ReadableStream + : { [K in keyof T]: TransformerParse } + +export type DefaultTransformerStringify = TransformerStringify< + T, + Date | undefined +> + +export type DefaultTransformerParse = TransformerParse diff --git a/packages/start/src/client/createMiddleware.ts b/packages/start/src/client/createMiddleware.ts index 61af24f88fa..52e352282a3 100644 --- a/packages/start/src/client/createMiddleware.ts +++ b/packages/start/src/client/createMiddleware.ts @@ -1,7 +1,7 @@ -import type { Method } from './createServerFn' +import type { ConstrainValidator, Method } from './createServerFn' import type { - AnyValidator, Constrain, + DefaultTransformerStringify, Expand, MergeAll, ResolveValidatorInput, @@ -129,7 +129,7 @@ export interface MiddlewareOptions< > { validateClient?: boolean middleware?: TMiddlewares - validator?: Constrain + validator?: ConstrainValidator client?: MiddlewareClientFn< TMiddlewares, TValidator, @@ -166,7 +166,7 @@ export type MiddlewareServerFn< TNewClientAfterContext = undefined, >(ctx?: { context?: TNewServerContext - sendContext?: TNewClientAfterContext + sendContext?: DefaultTransformerStringify }) => Promise< ServerResultWithContext > @@ -186,7 +186,7 @@ export type MiddlewareClientFn< method: Method next: (ctx?: { context?: TNewClientContext - sendContext?: TNewServerContext + sendContext?: DefaultTransformerStringify headers?: HeadersInit }) => Promise> }) => @@ -272,7 +272,7 @@ export interface MiddlewareValidator< TClientAfterContext, > { validator: ( - input: TNewValidator, + input: ConstrainValidator, ) => MiddlewareAfterMiddleware< TId, TMiddlewares, diff --git a/packages/start/src/client/createServerFn.ts b/packages/start/src/client/createServerFn.ts index d450abcfb80..1bc91498126 100644 --- a/packages/start/src/client/createServerFn.ts +++ b/packages/start/src/client/createServerFn.ts @@ -1,7 +1,14 @@ import invariant from 'tiny-invariant' import { defaultTransformer } from '@tanstack/react-router' import { mergeHeaders } from './headers' -import type { AnyValidator, Constrain } from '@tanstack/react-router' +import type { + AnyValidator, + Constrain, + DefaultTransformerParse, + DefaultTransformerStringify, + ResolveValidatorInput, + Validator, +} from '@tanstack/react-router' import type { AnyMiddleware, MergeAllServerContext, @@ -61,20 +68,10 @@ export interface OptionalFetcherDataOptions extends FetcherBaseOptions { data?: TInput } -export type FetcherData = WrapRSCs< +export type FetcherData = DefaultTransformerParse< TResponse extends JsonResponse ? TData : TResponse > -export type WrapRSCs = T extends JSX.Element - ? ReadableStream - : T extends Record - ? { - [K in keyof T]: WrapRSCs - } - : T extends Array - ? Array> - : T - export type RscStream = { __cacheState: T } @@ -83,7 +80,9 @@ export type Method = 'GET' | 'POST' export type ServerFn = ( ctx: ServerFnCtx, -) => Promise | TResponse +) => + | Promise> + | DefaultTransformerStringify export type ServerFnCtx = { method: TMethod @@ -105,13 +104,23 @@ type ServerFnBaseOptions< method: TMethod validateClient?: boolean middleware?: Constrain> - validator?: Constrain + validator?: ConstrainValidator extractedFn?: CompiledFetcherFn serverFn?: ServerFn filename: string functionId: string } +export type ConstrainValidator = unknown extends TValidator + ? TValidator + : Constrain< + TValidator, + Validator< + DefaultTransformerStringify>, + any + > + > + type ServerFnBase< TMethod extends Method = 'GET', TResponse = unknown, @@ -126,7 +135,7 @@ type ServerFnBase< 'validator' | 'handler' > validator: ( - validator: Constrain, + validator: ConstrainValidator, ) => Pick< ServerFnBase, 'handler' | 'middleware' diff --git a/packages/start/src/client/index.tsx b/packages/start/src/client/index.tsx index bdedb47f085..5a2361e8e66 100644 --- a/packages/start/src/client/index.tsx +++ b/packages/start/src/client/index.tsx @@ -9,7 +9,6 @@ export { type CompiledFetcherFn, type Fetcher, type RscStream, - type WrapRSCs, type FetcherImpl, type FetcherData, type FetcherBaseOptions, diff --git a/packages/start/src/client/tests/createServerFn.test-d.ts b/packages/start/src/client/tests/createServerFn.test-d.tsx similarity index 75% rename from packages/start/src/client/tests/createServerFn.test-d.ts rename to packages/start/src/client/tests/createServerFn.test-d.tsx index 88853e7d901..f2907a809c6 100644 --- a/packages/start/src/client/tests/createServerFn.test-d.ts +++ b/packages/start/src/client/tests/createServerFn.test-d.tsx @@ -1,6 +1,7 @@ import { expectTypeOf, test } from 'vitest' import { createServerFn } from '../createServerFn' import { createMiddleware } from '../createMiddleware' +import type { Constrain, Validator } from '@tanstack/react-router' test('createServerFn without middleware', () => { createServerFn({ method: 'GET' }).handler((options) => { @@ -178,3 +179,56 @@ test('createServerFn where data is optional if there is no validator', () => { | undefined >() }) + +test('createServerFn returns Date', () => { + const fn = createServerFn().handler(() => ({ + dates: [new Date(), new Date()] as const, + })) + + expectTypeOf(fn()).toEqualTypeOf>() +}) + +test('createServerFn returns RSC', () => { + const fn = createServerFn().handler(() => ({ + rscs: [ +
I'm an RSC
, +
I'm an RSC
, + ] as const, + })) + + expectTypeOf(fn()).toEqualTypeOf< + Promise<{ rscs: readonly [ReadableStream, ReadableStream] }> + >() +}) + +test('createServerFn returns undefined', () => { + const fn = createServerFn().handler(() => ({ + nothing: undefined, + })) + + expectTypeOf(fn()).toEqualTypeOf>() +}) + +test('createServerFn cannot return function', () => { + expectTypeOf(createServerFn().handler<{ func: () => 'func' }>) + .parameter(0) + .returns.toEqualTypeOf< + | { func: 'Function is not serializable' } + | Promise<{ func: 'Function is not serializable' }> + >() +}) + +test('createServerFn cannot validate function', () => { + const validator = createServerFn().validator< + (input: { func: () => 'string' }) => { output: 'string' } + > + + expectTypeOf(validator) + .parameter(0) + .toEqualTypeOf< + Constrain< + (input: { func: () => 'string' }) => { output: 'string' }, + Validator<{ func: 'Function is not serializable' }, any> + > + >() +}) diff --git a/packages/start/src/client/tests/createServerMiddleware.test-d.ts b/packages/start/src/client/tests/createServerMiddleware.test-d.ts index d3f464e9957..c714a91c29d 100644 --- a/packages/start/src/client/tests/createServerMiddleware.test-d.ts +++ b/packages/start/src/client/tests/createServerMiddleware.test-d.ts @@ -1,5 +1,6 @@ import { expectTypeOf, test } from 'vitest' import { createMiddleware } from '../createMiddleware' +import type { Constrain, Validator } from '@tanstack/react-router' test('createServeMiddleware removes middleware after middleware,', () => { const middleware = createMiddleware() @@ -228,7 +229,7 @@ test('createMiddleware merges server context and client context, sends server co fromServer1: string fromServer2: string fromServer3: string - toServer1: string + toServer1: 'toServer1' }>() return next({ context: { fromServer4: 'fromServer4' }, @@ -244,8 +245,45 @@ test('createMiddleware merges server context and client context, sends server co fromClient3: string clientAfter3: string fromClient4: string - toClient1: string + toClient1: 'toClient1' }> return next({ context: { clientAfter4: 'clientAfter4' } }) }) }) + +test('createMiddleware sendContext cannot send a function', () => { + createMiddleware() + .client(({ next }) => { + expectTypeOf(next<{ func: () => 'func' }>) + .parameter(0) + .exclude() + .toHaveProperty('sendContext') + .toEqualTypeOf<{ func: 'Function is not serializable' } | undefined>() + + return next() + }) + .server(({ next }) => { + expectTypeOf(next 'func' }>) + .parameter(0) + .exclude() + .toHaveProperty('sendContext') + .toEqualTypeOf<{ func: 'Function is not serializable' } | undefined>() + + return next() + }) +}) + +test('createMiddleware cannot validate function', () => { + const validator = createMiddleware().validator< + (input: { func: () => 'string' }) => { output: 'string' } + > + + expectTypeOf(validator) + .parameter(0) + .toEqualTypeOf< + Constrain< + (input: { func: () => 'string' }) => { output: 'string' }, + Validator<{ func: 'Function is not serializable' }, any> + > + >() +})