diff --git a/.changeset/quiet-plums-help.md b/.changeset/quiet-plums-help.md new file mode 100644 index 0000000000..f240c4fee7 --- /dev/null +++ b/.changeset/quiet-plums-help.md @@ -0,0 +1,5 @@ +--- +'@tanstack/router-core': patch +--- + +fix(router-core): use search validator output type for search middleware context diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index b92401b652..27db7fc350 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -1274,9 +1274,7 @@ export interface UpdatableRouteOptions< preloadGcTime?: number search?: { middlewares?: Array< - SearchMiddleware< - ResolveFullSearchSchemaInput - > + SearchMiddleware> > } /** diff --git a/packages/router-core/src/searchMiddleware.ts b/packages/router-core/src/searchMiddleware.ts index 1d60cc9587..212b41210f 100644 --- a/packages/router-core/src/searchMiddleware.ts +++ b/packages/router-core/src/searchMiddleware.ts @@ -45,9 +45,7 @@ export function retainSearchParams( export function stripSearchParams< TSearchSchema, TOptionalProps = PickOptional>, - const TValues = - | Partial> - | Array, + const TValues = Partial> | Array, const TInput = IsRequiredParams extends never ? TValues | true : TValues, diff --git a/packages/router-core/tests/searchMiddleware.test-d.ts b/packages/router-core/tests/searchMiddleware.test-d.ts new file mode 100644 index 0000000000..8f68d91b21 --- /dev/null +++ b/packages/router-core/tests/searchMiddleware.test-d.ts @@ -0,0 +1,76 @@ +import { describe, expectTypeOf, test } from 'vitest' +import { + BaseRootRoute, + BaseRoute, + stripSearchParams, + type ValidatorAdapter, +} from '../src' + +describe('search middlewares', () => { + test('should use search validator output for middleware context', () => { + type SearchInput = { + page?: string + } + + type SearchOutput = { + page: number + } + + const searchValidator: ValidatorAdapter = { + types: undefined as never, + parse: () => ({ page: 1 }), + } + + const rootRoute = new BaseRootRoute({}) + + new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + validateSearch: searchValidator, + search: { + middlewares: [ + ({ search, next }) => { + expectTypeOf(search).toEqualTypeOf() + expectTypeOf(next).toBeCallableWith({ page: 2 }) + expectTypeOf(next).parameter(0).toEqualTypeOf() + expectTypeOf(next({ page: 2 })).toEqualTypeOf() + + return search + }, + ], + }, + }) + }) + + test('should allow stripping default-valued validator output keys', () => { + type RootSearch = { + root?: string + } + + type SearchInput = { + foo?: string + } + + type SearchOutput = { + foo: string + } + + const searchValidator: ValidatorAdapter = { + types: undefined as never, + parse: () => ({ foo: 'default' }), + } + + const rootRoute = new BaseRootRoute({ + validateSearch: (search: RootSearch): RootSearch => search, + }) + + new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + validateSearch: searchValidator, + search: { + middlewares: [stripSearchParams({ foo: 'default' })], + }, + }) + }) +})