UNPKG

@tanstack/start-client-core

Version:

Modern and scalable routing for React applications

811 lines (685 loc) 22.5 kB
import { expectTypeOf, test } from 'vitest' import { createMiddleware } from '../createMiddleware' import type { RequestServerNextFn } from '../createMiddleware' import type { ConstrainValidator, CustomFetch } from '../createServerFn' import type { Register } from '@tanstack/router-core' import type { ServerFnMeta } from '../constants' test('createServeMiddleware removes middleware after middleware,', () => { const middleware = createMiddleware({ type: 'function' }) expectTypeOf(middleware).toHaveProperty('middleware') expectTypeOf(middleware).toHaveProperty('server') expectTypeOf(middleware).toHaveProperty('inputValidator') const middlewareAfterMiddleware = middleware.middleware([]) expectTypeOf(middlewareAfterMiddleware).toHaveProperty('inputValidator') expectTypeOf(middlewareAfterMiddleware).toHaveProperty('server') expectTypeOf(middlewareAfterMiddleware).not.toHaveProperty('middleware') const middlewareAfterInput = middleware.inputValidator(() => {}) expectTypeOf(middlewareAfterInput).toHaveProperty('server') expectTypeOf(middlewareAfterInput).not.toHaveProperty('middleware') const middlewareAfterServer = middleware.server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() expectTypeOf(options.data).toEqualTypeOf<undefined>() expectTypeOf(options.method).toEqualTypeOf<'GET' | 'POST'>() const result = await options.next({ context: { a: 'a', }, }) expectTypeOf(result.context).toEqualTypeOf<{ a: string }>() expectTypeOf(result.sendContext).toEqualTypeOf<undefined>() return result }) expectTypeOf(middlewareAfterServer).not.toHaveProperty('server') expectTypeOf(middlewareAfterServer).not.toHaveProperty('input') expectTypeOf(middlewareAfterServer).not.toHaveProperty('middleware') }) test('createMiddleware merges server context', () => { const middleware1 = createMiddleware({ type: 'function' }).server( async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() expectTypeOf(options.data).toEqualTypeOf<undefined>() expectTypeOf(options.method).toEqualTypeOf<'GET' | 'POST'>() const result = await options.next({ context: { a: true } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { a: boolean } sendContext: undefined } context: { a: boolean } sendContext: undefined }>() return result }, ) const middleware2 = createMiddleware({ type: 'function' }).server( async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() expectTypeOf(options.data).toEqualTypeOf<undefined>() expectTypeOf(options.method).toEqualTypeOf<'GET' | 'POST'>() const result = await options.next({ context: { b: 'test' } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { b: string } sendContext: undefined } context: { b: string } sendContext: undefined }>() return result }, ) const middleware3 = createMiddleware({ type: 'function' }) .middleware([middleware1, middleware2]) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ a: boolean; b: string }>() const result = await options.next({ context: { c: 0 } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { c: number } sendContext: undefined } context: { a: boolean; b: string; c: number } sendContext: undefined }>() return result }) createMiddleware({ type: 'function' }) .middleware([middleware3]) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ a: boolean b: string c: number }>() const result = await options.next({ context: { d: 5 } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { d: number } sendContext: undefined } context: { a: boolean; b: string; c: number; d: number } sendContext: undefined }>() return result }) }) test('createMiddleware merges client context and sends to the server', () => { const middleware1 = createMiddleware({ type: 'function' }).client( async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() const result = await options.next({ context: { a: true } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { a: boolean } sendContext: undefined headers: HeadersInit fetch?: CustomFetch }>() return result }, ) const middleware2 = createMiddleware({ type: 'function' }).client( async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() const result = await options.next({ context: { b: 'test' } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { b: string } sendContext: undefined headers: HeadersInit fetch?: CustomFetch }>() return result }, ) const middleware3 = createMiddleware({ type: 'function' }) .middleware([middleware1, middleware2]) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ a: boolean; b: string }>() const result = await options.next({ context: { c: 0 } }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { a: boolean; b: string; c: number } sendContext: undefined headers: HeadersInit fetch?: CustomFetch }>() return result }) const middleware4 = createMiddleware({ type: 'function' }) .middleware([middleware3]) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ a: boolean b: string c: number }>() const result = await options.next({ sendContext: { ...options.context, d: 5 }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { a: boolean; b: string; c: number } sendContext: { a: boolean; b: string; c: number; d: 5 } headers: HeadersInit fetch?: CustomFetch }>() return result }) createMiddleware({ type: 'function' }) .middleware([middleware4]) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ a: boolean b: string c: number d: 5 }>() const result = await options.next({ context: { e: 'e', }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { e: string } sendContext: undefined } context: { a: boolean; b: string; c: number; d: 5; e: string } sendContext: undefined }>() return result }) }) test('createMiddleware merges input', () => { const middleware1 = createMiddleware({ type: 'function' }) .inputValidator(() => { return { a: 'a', } as const }) .server(({ data, next }) => { expectTypeOf(data).toEqualTypeOf<{ readonly a: 'a' }>() return next() }) const middleware2 = createMiddleware({ type: 'function' }) .middleware([middleware1]) .inputValidator(() => { return { b: 'b', } as const }) .server(({ data, next }) => { expectTypeOf(data).toEqualTypeOf<{ readonly a: 'a'; readonly b: 'b' }> return next() }) createMiddleware({ type: 'function' }) .middleware([middleware2]) .inputValidator(() => ({ c: 'c' }) as const) .server(({ next, data }) => { expectTypeOf(data).toEqualTypeOf<{ readonly a: 'a' readonly b: 'b' readonly c: 'c' }> return next() }) }) test('createMiddleware merges server context and client context, sends server context to the client and merges ', () => { const middleware1 = createMiddleware({ type: 'function' }) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() const result = await options.next({ context: { fromClient1: 'fromClient1' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { fromClient1: string } sendContext: undefined headers: HeadersInit fetch?: CustomFetch }>() return result }) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() const result = await options.next({ context: { fromServer1: 'fromServer1' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { fromServer1: string } sendContext: undefined } context: { fromServer1: string } sendContext: undefined }>() return result }) const middleware2 = createMiddleware({ type: 'function' }) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() const result = await options.next({ context: { fromClient2: 'fromClient2' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { fromClient2: string } sendContext: undefined headers: HeadersInit fetch?: CustomFetch }>() return result }) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<undefined>() const result = await options.next({ context: { fromServer2: 'fromServer2' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { fromServer2: string } sendContext: undefined } context: { fromServer2: string } sendContext: undefined }>() return result }) const middleware3 = createMiddleware({ type: 'function' }) .middleware([middleware1, middleware2]) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ fromClient1: string fromClient2: string }>() const result = await options.next({ context: { fromClient3: 'fromClient3' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { fromClient1: string fromClient2: string fromClient3: string } sendContext: undefined headers: HeadersInit fetch?: CustomFetch }>() return result }) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ fromServer1: string fromServer2: string }>() const result = await options.next({ context: { fromServer3: 'fromServer3' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { fromServer3: string } sendContext: undefined } context: { fromServer1: string fromServer2: string fromServer3: string } sendContext: undefined }>() return result }) const middleware4 = createMiddleware({ type: 'function' }) .middleware([middleware3]) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ fromClient1: string fromClient2: string fromClient3: string }>() const result = await options.next({ context: { fromClient4: 'fromClient4' }, sendContext: { toServer1: 'toServer1' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { fromClient1: string fromClient2: string fromClient3: string fromClient4: string } sendContext: { toServer1: 'toServer1' } headers: HeadersInit fetch?: CustomFetch }>() return result }) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ fromServer1: string fromServer2: string fromServer3: string toServer1: 'toServer1' }>() const result = await options.next({ context: { fromServer4: 'fromServer4' }, sendContext: { toClient1: 'toClient1' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { fromServer4: string } sendContext: { toClient1: 'toClient1' } } context: { fromServer1: string fromServer2: string fromServer3: string fromServer4: string toServer1: 'toServer1' } sendContext: { toClient1: 'toClient1' } }>() return result }) createMiddleware({ type: 'function' }) .middleware([middleware4]) .client(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ fromClient1: string fromClient2: string fromClient3: string fromClient4: string }>() const result = await options.next({ context: { fromClient5: 'fromClient5' }, sendContext: { toServer2: 'toServer2' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true context: { fromClient1: string fromClient2: string fromClient3: string fromClient4: string fromClient5: string toClient1: 'toClient1' } sendContext: { toServer1: 'toServer1'; toServer2: 'toServer2' } headers: HeadersInit fetch?: CustomFetch }>() return result }) .server(async (options) => { expectTypeOf(options.context).toEqualTypeOf<{ fromServer1: string fromServer2: string fromServer3: string fromServer4: string toServer1: 'toServer1' toServer2: 'toServer2' }>() const result = await options.next({ context: { fromServer5: 'fromServer5' }, sendContext: { toClient2: 'toClient2' }, }) expectTypeOf(result).toEqualTypeOf<{ 'use functions must return the result of next()': true '~types': { context: { fromServer5: string } sendContext: { toClient2: 'toClient2' } } context: { fromServer1: string fromServer2: string fromServer3: string fromServer4: string fromServer5: string toServer1: 'toServer1' toServer2: 'toServer2' } sendContext: { toClient1: 'toClient1'; toClient2: 'toClient2' } }>() return result }) }) test('createMiddleware sendContext cannot send a function', () => { createMiddleware({ type: 'function' }) .client(({ next }) => { expectTypeOf(next<{ func: () => 'func' }>) .parameter(0) .exclude<undefined>() .toHaveProperty('sendContext') .toEqualTypeOf<{ func: 'Function is not serializable' } | undefined>() return next() }) .server(({ next }) => { expectTypeOf(next<undefined, { func: () => 'func' }>) .parameter(0) .exclude<undefined>() .toHaveProperty('sendContext') .toEqualTypeOf<{ func: 'Function is not serializable' } | undefined>() return next() }) }) test('createMiddleware cannot validate function', () => { const validator = createMiddleware({ type: 'function' }).inputValidator< (input: { func: () => 'string' }) => { output: 'string' } > expectTypeOf(validator) .parameter(0) .toEqualTypeOf< ConstrainValidator< Register, 'GET', (input: { func: () => 'string' }) => { output: 'string' } > >() }) test('createMiddleware can validate Date', () => { const validator = createMiddleware({ type: 'function' }).inputValidator< (input: Date) => { output: 'string' } > expectTypeOf(validator) .parameter(0) .toEqualTypeOf< ConstrainValidator<Register, 'GET', (input: Date) => { output: 'string' }> >() }) test('createMiddleware can validate FormData', () => { const validator = createMiddleware({ type: 'function' }).inputValidator< (input: FormData) => { output: 'string' } > expectTypeOf(validator) .parameter(0) .toEqualTypeOf< ConstrainValidator< Register, 'GET', (input: FormData) => { output: 'string' } > >() }) test('createMiddleware merging from parent with undefined validator', () => { const middleware1 = createMiddleware({ type: 'function' }).inputValidator( (input: { test: string }) => input.test, ) createMiddleware({ type: 'function' }) .middleware([middleware1]) .server((ctx) => { expectTypeOf(ctx.data).toEqualTypeOf<string>() return ctx.next() }) }) test('createMiddleware validator infers unknown for default input type', () => { createMiddleware({ type: 'function' }) .inputValidator((input) => { expectTypeOf(input).toEqualTypeOf<unknown>() if (typeof input === 'number') return 'success' as const return 'failed' as const }) .server(({ data, next }) => { expectTypeOf(data).toEqualTypeOf<'success' | 'failed'>() return next() }) }) test('createMiddleware with type request, no middleware or context', () => { createMiddleware({ type: 'request' }).server(async (options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: undefined serverFnMeta?: ServerFnMeta }>() const result = await options.next() expectTypeOf(result).toEqualTypeOf<{ context: undefined pathname: string request: Request response: Response }>() return result }) }) test('createMiddleware with type request, no middleware with context', () => { createMiddleware({ type: 'request' }).server(async (options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: undefined serverFnMeta?: ServerFnMeta }>() const result = await options.next({ context: { a: 'a' } }) expectTypeOf(result).toEqualTypeOf<{ context: { a: string } pathname: string request: Request response: Response }>() return result }) }) test('createMiddleware with type request, middleware and context', () => { const middleware1 = createMiddleware({ type: 'request' }).server( async (options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: undefined serverFnMeta?: ServerFnMeta }>() const result = await options.next({ context: { a: 'a' } }) expectTypeOf(result).toEqualTypeOf<{ context: { a: string } pathname: string request: Request response: Response }>() return result }, ) createMiddleware({ type: 'request' }) .middleware([middleware1]) .server(async (options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: { a: string } serverFnMeta?: ServerFnMeta }>() const result = await options.next({ context: { b: 'b' } }) expectTypeOf(result).toEqualTypeOf<{ context: { a: string; b: string } pathname: string request: Request response: Response }>() return result }) }) test('createMiddleware with type request can return Response directly', () => { createMiddleware({ type: 'request' }).server(async (options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: undefined serverFnMeta?: ServerFnMeta }>() // Should be able to return a Response directly if (Math.random() > 0.5) { return new Response('Unauthorized', { status: 401 }) } // Or return the result from next() return options.next() }) }) test('createMiddleware with type request can return Promise<Response>', () => { createMiddleware({ type: 'request' }).server(async (options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: undefined serverFnMeta?: ServerFnMeta }>() // Should be able to return a Promise<Response> return Promise.resolve(new Response('OK', { status: 200 })) }) }) test('createMiddleware with type request can return sync Response', () => { createMiddleware({ type: 'request' }).server((options) => { expectTypeOf(options).toEqualTypeOf<{ request: Request next: RequestServerNextFn<{}, undefined> pathname: string context: undefined serverFnMeta?: ServerFnMeta }>() // Should be able to return a synchronous Response return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' }, }) }) })