Skip to content

Commit bd59a4c

Browse files
committed
fix(router-core): avoid infinite type recursion in serializer
1 parent c2f732d commit bd59a4c

File tree

2 files changed

+50
-18
lines changed

2 files changed

+50
-18
lines changed

packages/router-core/src/ssr/serializer/transformer.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,21 @@ export interface CreateSerializationAdapterOptions<TInput, TOutput> {
3232

3333
export type ValidateSerializable<T, TSerializable> = T extends TSerializable
3434
? T
35-
: T extends (...args: Array<any>) => any
36-
? 'Function is not serializable'
37-
: T extends Promise<any>
38-
? ValidateSerializablePromise<T, TSerializable>
39-
: T extends ReadableStream<any>
40-
? ValidateReadableStream<T, TSerializable>
41-
: T extends Set<any>
42-
? ValidateSerializableSet<T, TSerializable>
43-
: T extends Map<any, any>
44-
? ValidateSerializableMap<T, TSerializable>
45-
: {
46-
[K in keyof T]: ValidateSerializable<T[K], TSerializable>
47-
}
35+
: T extends ReadonlyArray<any>
36+
? ValidateSerializableArray<T, TSerializable>
37+
: T extends (...args: Array<any>) => any
38+
? 'Function is not serializable'
39+
: T extends Promise<any>
40+
? ValidateSerializablePromise<T, TSerializable>
41+
: T extends ReadableStream<any>
42+
? ValidateReadableStream<T, TSerializable>
43+
: T extends Set<any>
44+
? ValidateSerializableSet<T, TSerializable>
45+
: T extends Map<any, any>
46+
? ValidateSerializableMap<T, TSerializable>
47+
: {
48+
[K in keyof T]: ValidateSerializable<T[K], TSerializable>
49+
}
4850

4951
export type ValidateSerializablePromise<T, TSerializable> =
5052
T extends Promise<infer TAwaited>
@@ -69,6 +71,19 @@ export type ValidateSerializableMap<T, TSerializable> =
6971
>
7072
: never
7173

74+
type ValidateSerializableArray<T extends ReadonlyArray<any>, TSerializable> =
75+
IsTuple<T> extends true
76+
? { [K in keyof T]: ValidateSerializable<T[K], TSerializable> }
77+
: T extends Array<infer U>
78+
? Array<ValidateSerializable<U, TSerializable>>
79+
: ReadonlyArray<ValidateSerializable<T[number], TSerializable>>
80+
81+
type IsTuple<T extends ReadonlyArray<any>> = T extends readonly []
82+
? true
83+
: T extends readonly [any, ...infer _Rest]
84+
? true
85+
: false
86+
7287
export type RegisteredReadableStream =
7388
unknown extends SerializerExtensions['ReadableStream']
7489
? never
@@ -175,11 +190,13 @@ export type ValidateSerializableInputResult<TRegister, T> =
175190
export type ValidateSerializableResult<T, TSerializable> =
176191
T extends TSerializable
177192
? T
178-
: unknown extends SerializerExtensions['ReadableStream']
179-
? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
180-
: T extends SerializerExtensions['ReadableStream']
181-
? ReadableStream
182-
: { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
193+
: T extends ReadonlyArray<any>
194+
? ValidateSerializableArray<T, TSerializable>
195+
: unknown extends SerializerExtensions['ReadableStream']
196+
? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
197+
: T extends SerializerExtensions['ReadableStream']
198+
? ReadableStream
199+
: { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
183200

184201
export type RegisteredSSROption<TRegister> =
185202
unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { describe, expectTypeOf, it } from 'vitest'
2+
3+
import type {
4+
Serializable,
5+
ValidateSerializableResult,
6+
} from '../src/ssr/serializer/transformer'
7+
8+
describe('ValidateSerializableResult recursion', () => {
9+
it('should preserve recursive payload without infinite expansion', () => {
10+
type Result = Array<Result> | { [key: string]: Result }
11+
expectTypeOf<
12+
ValidateSerializableResult<Result, Serializable>
13+
>().branded.toEqualTypeOf<Result>()
14+
})
15+
})

0 commit comments

Comments
 (0)