From 794f158029ef32abd7219b0da9b642394ed88ad7 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 03:07:44 +0100 Subject: [PATCH 01/14] fix(router-core): avoid infinite type recursion in serializer --- .../src/ssr/serializer/transformer.ts | 53 ++++++++++++------- .../tests/serializer-recursion.test-d.ts | 15 ++++++ 2 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 packages/router-core/tests/serializer-recursion.test-d.ts diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 3974a25bf8..206fae1cec 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -32,19 +32,21 @@ export interface CreateSerializationAdapterOptions { export type ValidateSerializable = T extends TSerializable ? T - : T extends (...args: Array) => any - ? 'Function is not serializable' - : T extends Promise - ? ValidateSerializablePromise - : T extends ReadableStream - ? ValidateReadableStream - : T extends Set - ? ValidateSerializableSet - : T extends Map - ? ValidateSerializableMap - : { - [K in keyof T]: ValidateSerializable - } + : T extends ReadonlyArray + ? ValidateSerializableArray + : T extends (...args: Array) => any + ? 'Function is not serializable' + : T extends Promise + ? ValidateSerializablePromise + : T extends ReadableStream + ? ValidateReadableStream + : T extends Set + ? ValidateSerializableSet + : T extends Map + ? ValidateSerializableMap + : { + [K in keyof T]: ValidateSerializable + } export type ValidateSerializablePromise = T extends Promise @@ -69,6 +71,19 @@ export type ValidateSerializableMap = > : never +type ValidateSerializableArray, TSerializable> = + IsTuple extends true + ? { [K in keyof T]: ValidateSerializable } + : T extends Array + ? Array> + : ReadonlyArray> + +type IsTuple> = T extends readonly [] + ? true + : T extends readonly [any, ...infer _Rest] + ? true + : false + export type RegisteredReadableStream = unknown extends SerializerExtensions['ReadableStream'] ? never @@ -175,11 +190,13 @@ export type ValidateSerializableInputResult = export type ValidateSerializableResult = T extends TSerializable ? T - : unknown extends SerializerExtensions['ReadableStream'] - ? { [K in keyof T]: ValidateSerializableResult } - : T extends SerializerExtensions['ReadableStream'] - ? ReadableStream - : { [K in keyof T]: ValidateSerializableResult } + : T extends ReadonlyArray + ? ValidateSerializableArray + : unknown extends SerializerExtensions['ReadableStream'] + ? { [K in keyof T]: ValidateSerializableResult } + : T extends SerializerExtensions['ReadableStream'] + ? ReadableStream + : { [K in keyof T]: ValidateSerializableResult } export type RegisteredSSROption = unknown extends RegisteredConfigType diff --git a/packages/router-core/tests/serializer-recursion.test-d.ts b/packages/router-core/tests/serializer-recursion.test-d.ts new file mode 100644 index 0000000000..dbeb4b03bb --- /dev/null +++ b/packages/router-core/tests/serializer-recursion.test-d.ts @@ -0,0 +1,15 @@ +import { describe, expectTypeOf, it } from 'vitest' + +import type { + Serializable, + ValidateSerializableResult, +} from '../src/ssr/serializer/transformer' + +describe('ValidateSerializableResult recursion', () => { + it('should preserve recursive payload without infinite expansion', () => { + type Result = Array | { [key: string]: Result } + expectTypeOf< + ValidateSerializableResult + >().branded.toEqualTypeOf() + }) +}) From 4fb6bb051af900a42681af1a00c88905d9d98b84 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 03:31:36 +0100 Subject: [PATCH 02/14] fix: restore result array recursion --- .../router-core/src/ssr/serializer/transformer.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 206fae1cec..68c62b61ee 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -191,13 +191,22 @@ export type ValidateSerializableResult = T extends TSerializable ? T : T extends ReadonlyArray - ? ValidateSerializableArray + ? ValidateSerializableResultArray : unknown extends SerializerExtensions['ReadableStream'] ? { [K in keyof T]: ValidateSerializableResult } : T extends SerializerExtensions['ReadableStream'] ? ReadableStream : { [K in keyof T]: ValidateSerializableResult } +type ValidateSerializableResultArray< + T extends ReadonlyArray, + TSerializable, +> = IsTuple extends true + ? { [K in keyof T]: ValidateSerializableResult } + : T extends Array + ? Array> + : ReadonlyArray> + export type RegisteredSSROption = unknown extends RegisteredConfigType ? SSROption From 6d88b6f87c866b696f73072b354ca9e450a86bd0 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 11:08:36 +0100 Subject: [PATCH 03/14] fix: distribute serializer validation over unions --- .../src/ssr/serializer/transformer.ts | 55 ++++++++++--------- .../tests/serializer-recursion.test-d.ts | 19 +++++++ 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 68c62b61ee..822ac42880 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -30,23 +30,24 @@ export interface CreateSerializationAdapterOptions { fromSerializable: (value: TOutput) => TInput } -export type ValidateSerializable = T extends TSerializable - ? T - : T extends ReadonlyArray - ? ValidateSerializableArray - : T extends (...args: Array) => any - ? 'Function is not serializable' - : T extends Promise - ? ValidateSerializablePromise - : T extends ReadableStream - ? ValidateReadableStream - : T extends Set - ? ValidateSerializableSet - : T extends Map - ? ValidateSerializableMap - : { - [K in keyof T]: ValidateSerializable - } +export type ValidateSerializable = + T extends unknown + ? T extends TSerializable + ? T + : T extends ReadonlyArray + ? ValidateSerializableArray + : T extends (...args: Array) => any + ? 'Function is not serializable' + : T extends Promise + ? ValidateSerializablePromise + : T extends ReadableStream + ? ValidateReadableStream + : T extends Set + ? ValidateSerializableSet + : T extends Map + ? ValidateSerializableMap + : { [K in keyof T]: ValidateSerializable } + : never export type ValidateSerializablePromise = T extends Promise @@ -188,15 +189,17 @@ export type ValidateSerializableInputResult = ValidateSerializableResult> export type ValidateSerializableResult = - T extends TSerializable - ? T - : T extends ReadonlyArray - ? ValidateSerializableResultArray - : unknown extends SerializerExtensions['ReadableStream'] - ? { [K in keyof T]: ValidateSerializableResult } - : T extends SerializerExtensions['ReadableStream'] - ? ReadableStream - : { [K in keyof T]: ValidateSerializableResult } + T extends unknown + ? T extends TSerializable + ? T + : T extends ReadonlyArray + ? ValidateSerializableResultArray + : unknown extends SerializerExtensions['ReadableStream'] + ? { [K in keyof T]: ValidateSerializableResult } + : T extends SerializerExtensions['ReadableStream'] + ? ReadableStream + : { [K in keyof T]: ValidateSerializableResult } + : never type ValidateSerializableResultArray< T extends ReadonlyArray, diff --git a/packages/router-core/tests/serializer-recursion.test-d.ts b/packages/router-core/tests/serializer-recursion.test-d.ts index dbeb4b03bb..1e6e936a46 100644 --- a/packages/router-core/tests/serializer-recursion.test-d.ts +++ b/packages/router-core/tests/serializer-recursion.test-d.ts @@ -12,4 +12,23 @@ describe('ValidateSerializableResult recursion', () => { ValidateSerializableResult >().branded.toEqualTypeOf() }) + + it('should preserve recursive tuples without infinite expansion', () => { + type ResultTuple = readonly [ + ReadonlyArray, + { [key: string]: ResultTuple } + ] + expectTypeOf< + ValidateSerializableResult + >().branded.toEqualTypeOf() + }) + + it('should preserve readonly recursive arrays without infinite expansion', () => { + type ResultReadonlyArray = ReadonlyArray< + ResultReadonlyArray | { [key: string]: ResultReadonlyArray } + > + expectTypeOf< + ValidateSerializableResult + >().branded.toEqualTypeOf() + }) }) From 72de9f34940a7590a20a470095767a5621477bc3 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 12:30:13 +0100 Subject: [PATCH 04/14] more test cases and tiny type format tweaks --- .../src/ssr/serializer/transformer.ts | 69 +++++++++---------- .../tests/serializer-recursion.test-d.ts | 26 ++++++- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 822ac42880..aff1f873ea 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -30,24 +30,23 @@ export interface CreateSerializationAdapterOptions { fromSerializable: (value: TOutput) => TInput } -export type ValidateSerializable = - T extends unknown - ? T extends TSerializable - ? T - : T extends ReadonlyArray - ? ValidateSerializableArray - : T extends (...args: Array) => any - ? 'Function is not serializable' - : T extends Promise - ? ValidateSerializablePromise - : T extends ReadableStream - ? ValidateReadableStream - : T extends Set - ? ValidateSerializableSet - : T extends Map - ? ValidateSerializableMap - : { [K in keyof T]: ValidateSerializable } - : never +export type ValidateSerializable = T extends unknown + ? T extends TSerializable + ? T + : T extends ReadonlyArray + ? ValidateSerializableArray + : T extends (...args: Array) => any + ? 'Function is not serializable' + : T extends Promise + ? ValidateSerializablePromise + : T extends ReadableStream + ? ValidateReadableStream + : T extends Set + ? ValidateSerializableSet + : T extends Map + ? ValidateSerializableMap + : { [K in keyof T]: ValidateSerializable } + : never export type ValidateSerializablePromise = T extends Promise @@ -188,27 +187,27 @@ export type RegisteredSerializationAdapters = RegisteredConfigType< export type ValidateSerializableInputResult = ValidateSerializableResult> -export type ValidateSerializableResult = - T extends unknown - ? T extends TSerializable - ? T - : T extends ReadonlyArray - ? ValidateSerializableResultArray - : unknown extends SerializerExtensions['ReadableStream'] - ? { [K in keyof T]: ValidateSerializableResult } - : T extends SerializerExtensions['ReadableStream'] - ? ReadableStream - : { [K in keyof T]: ValidateSerializableResult } - : never +export type ValidateSerializableResult = T extends unknown + ? T extends TSerializable + ? T + : T extends ReadonlyArray + ? ValidateSerializableResultArray + : unknown extends SerializerExtensions['ReadableStream'] + ? { [K in keyof T]: ValidateSerializableResult } + : T extends SerializerExtensions['ReadableStream'] + ? ReadableStream + : { [K in keyof T]: ValidateSerializableResult } + : never type ValidateSerializableResultArray< T extends ReadonlyArray, TSerializable, -> = IsTuple extends true - ? { [K in keyof T]: ValidateSerializableResult } - : T extends Array - ? Array> - : ReadonlyArray> +> = + IsTuple extends true + ? { [K in keyof T]: ValidateSerializableResult } + : T extends Array + ? Array> + : ReadonlyArray> export type RegisteredSSROption = unknown extends RegisteredConfigType diff --git a/packages/router-core/tests/serializer-recursion.test-d.ts b/packages/router-core/tests/serializer-recursion.test-d.ts index 1e6e936a46..063feff469 100644 --- a/packages/router-core/tests/serializer-recursion.test-d.ts +++ b/packages/router-core/tests/serializer-recursion.test-d.ts @@ -2,10 +2,32 @@ import { describe, expectTypeOf, it } from 'vitest' import type { Serializable, + ValidateSerializable, ValidateSerializableResult, } from '../src/ssr/serializer/transformer' -describe('ValidateSerializableResult recursion', () => { +describe('ValidateSerializable array handling', () => { + it('preserves nested array payloads for input validation', () => { + type Input = Array<{ value: string; nested: Array<{ id: number }> }> + expectTypeOf< + ValidateSerializable + >().branded.toEqualTypeOf() + }) + + it('preserves tuple structure for input validation', () => { + type InputTuple = readonly [{ name: string }, { count: number }] + expectTypeOf< + ValidateSerializable + >().branded.toEqualTypeOf() + }) + + it('preserves readonly array structure for input validation', () => { + type InputReadonlyArray = ReadonlyArray<{ value: string }> + expectTypeOf< + ValidateSerializable + >().branded.toEqualTypeOf() + }) + it('should preserve recursive payload without infinite expansion', () => { type Result = Array | { [key: string]: Result } expectTypeOf< @@ -16,7 +38,7 @@ describe('ValidateSerializableResult recursion', () => { it('should preserve recursive tuples without infinite expansion', () => { type ResultTuple = readonly [ ReadonlyArray, - { [key: string]: ResultTuple } + { [key: string]: ResultTuple }, ] expectTypeOf< ValidateSerializableResult From 4b65c4a8adc85587be408041d41bc21b07d2acca Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 12:38:58 +0100 Subject: [PATCH 05/14] ok i mean it's deduplicated but needs a leaf sentinel so whatevs --- .../src/ssr/serializer/transformer.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index aff1f873ea..c8a4b1d1c3 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -71,12 +71,29 @@ export type ValidateSerializableMap = > : never -type ValidateSerializableArray, TSerializable> = +type ApplyArrayValidation< + TValue, + TSerializable, + TKind extends 'input' | 'result', +> = TKind extends 'input' + ? ValidateSerializable + : ValidateSerializableResult + +type ValidateSerializableArrayCore< + T extends ReadonlyArray, + TSerializable, + TKind extends 'input' | 'result', +> = IsTuple extends true - ? { [K in keyof T]: ValidateSerializable } + ? { [K in keyof T]: ApplyArrayValidation } : T extends Array - ? Array> - : ReadonlyArray> + ? Array> + : ReadonlyArray> + +type ValidateSerializableArray< + T extends ReadonlyArray, + TSerializable, +> = ValidateSerializableArrayCore type IsTuple> = T extends readonly [] ? true @@ -202,12 +219,7 @@ export type ValidateSerializableResult = T extends unknown type ValidateSerializableResultArray< T extends ReadonlyArray, TSerializable, -> = - IsTuple extends true - ? { [K in keyof T]: ValidateSerializableResult } - : T extends Array - ? Array> - : ReadonlyArray> +> = ValidateSerializableArrayCore export type RegisteredSSROption = unknown extends RegisteredConfigType From fe19e00bcff8eb8f8af55db83e0c20e7eca3a501 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 12:51:18 +0100 Subject: [PATCH 06/14] use unknown instead of any --- .../router-core/src/ssr/serializer/transformer.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index c8a4b1d1c3..4e960df548 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -33,7 +33,7 @@ export interface CreateSerializationAdapterOptions { export type ValidateSerializable = T extends unknown ? T extends TSerializable ? T - : T extends ReadonlyArray + : T extends ReadonlyArray ? ValidateSerializableArray : T extends (...args: Array) => any ? 'Function is not serializable' @@ -80,7 +80,7 @@ type ApplyArrayValidation< : ValidateSerializableResult type ValidateSerializableArrayCore< - T extends ReadonlyArray, + T extends ReadonlyArray, TSerializable, TKind extends 'input' | 'result', > = @@ -91,13 +91,13 @@ type ValidateSerializableArrayCore< : ReadonlyArray> type ValidateSerializableArray< - T extends ReadonlyArray, + T extends ReadonlyArray, TSerializable, > = ValidateSerializableArrayCore -type IsTuple> = T extends readonly [] +type IsTuple> = T extends readonly [] ? true - : T extends readonly [any, ...infer _Rest] + : T extends readonly [unknown, ...Array] ? true : false @@ -207,7 +207,7 @@ export type ValidateSerializableInputResult = export type ValidateSerializableResult = T extends unknown ? T extends TSerializable ? T - : T extends ReadonlyArray + : T extends ReadonlyArray ? ValidateSerializableResultArray : unknown extends SerializerExtensions['ReadableStream'] ? { [K in keyof T]: ValidateSerializableResult } @@ -217,7 +217,7 @@ export type ValidateSerializableResult = T extends unknown : never type ValidateSerializableResultArray< - T extends ReadonlyArray, + T extends ReadonlyArray, TSerializable, > = ValidateSerializableArrayCore From 2c14a2c982273c77473b8c7ad3db647155c727a3 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 13:03:52 +0100 Subject: [PATCH 07/14] remove now unused IsTuple type --- .../src/ssr/serializer/transformer.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 4e960df548..6df279c7f3 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -31,10 +31,10 @@ export interface CreateSerializationAdapterOptions { } export type ValidateSerializable = T extends unknown - ? T extends TSerializable - ? T - : T extends ReadonlyArray - ? ValidateSerializableArray + ? T extends ReadonlyArray + ? ValidateSerializableArray + : T extends TSerializable + ? T : T extends (...args: Array) => any ? 'Function is not serializable' : T extends Promise @@ -83,24 +83,19 @@ type ValidateSerializableArrayCore< T extends ReadonlyArray, TSerializable, TKind extends 'input' | 'result', -> = - IsTuple extends true - ? { [K in keyof T]: ApplyArrayValidation } - : T extends Array +> = T extends any + ? number extends T['length'] + ? T extends Array ? Array> : ReadonlyArray> + : { [K in keyof T]: ApplyArrayValidation } + : never type ValidateSerializableArray< T extends ReadonlyArray, TSerializable, > = ValidateSerializableArrayCore -type IsTuple> = T extends readonly [] - ? true - : T extends readonly [unknown, ...Array] - ? true - : false - export type RegisteredReadableStream = unknown extends SerializerExtensions['ReadableStream'] ? never @@ -205,10 +200,10 @@ export type ValidateSerializableInputResult = ValidateSerializableResult> export type ValidateSerializableResult = T extends unknown - ? T extends TSerializable - ? T - : T extends ReadonlyArray - ? ValidateSerializableResultArray + ? T extends ReadonlyArray + ? ValidateSerializableResultArray + : T extends TSerializable + ? T : unknown extends SerializerExtensions['ReadableStream'] ? { [K in keyof T]: ValidateSerializableResult } : T extends SerializerExtensions['ReadableStream'] From 4fd2e248650f3445972439d4c08ff09d263a4c72 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 13:40:45 +0100 Subject: [PATCH 08/14] retain variadic tuple fidelity in the types --- .../src/ssr/serializer/transformer.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 6df279c7f3..65dd7009a5 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -79,16 +79,24 @@ type ApplyArrayValidation< ? ValidateSerializable : ValidateSerializableResult +type IsTuple> = T extends readonly [] + ? true + : T extends readonly [unknown, ...infer TRest] + ? TRest extends ReadonlyArray + ? true + : false + : false + type ValidateSerializableArrayCore< T extends ReadonlyArray, TSerializable, TKind extends 'input' | 'result', > = T extends any - ? number extends T['length'] - ? T extends Array + ? IsTuple extends true + ? { [K in keyof T]: ApplyArrayValidation } + : T extends Array ? Array> : ReadonlyArray> - : { [K in keyof T]: ApplyArrayValidation } : never type ValidateSerializableArray< @@ -204,11 +212,15 @@ export type ValidateSerializableResult = T extends unknown ? ValidateSerializableResultArray : T extends TSerializable ? T - : unknown extends SerializerExtensions['ReadableStream'] - ? { [K in keyof T]: ValidateSerializableResult } - : T extends SerializerExtensions['ReadableStream'] - ? ReadableStream - : { [K in keyof T]: ValidateSerializableResult } + : T extends (...args: Array) => any + ? 'Function is not serializable' + : unknown extends SerializerExtensions['ReadableStream'] + ? { [K in keyof T]: ValidateSerializableResult } + : T extends SerializerExtensions['ReadableStream'] + ? ReadableStream + : { + [K in keyof T]: ValidateSerializableResult + } : never type ValidateSerializableResultArray< From c92e23c5cd064568feab11c198c3b6a5c6ca439a Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 14:03:42 +0100 Subject: [PATCH 09/14] test cov for Promise and ReadableStream recursive containers --- .../tests/serializer-recursion.test-d.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/router-core/tests/serializer-recursion.test-d.ts b/packages/router-core/tests/serializer-recursion.test-d.ts index 063feff469..4df82d5b73 100644 --- a/packages/router-core/tests/serializer-recursion.test-d.ts +++ b/packages/router-core/tests/serializer-recursion.test-d.ts @@ -28,6 +28,30 @@ describe('ValidateSerializable array handling', () => { >().branded.toEqualTypeOf() }) + it('preserves recursive payloads wrapped in Promise for input validation', () => { + type Recursive = { value: number; next?: Recursive } + type InputPromise = Promise + expectTypeOf< + ValidateSerializable + >().branded.toEqualTypeOf() + }) + + it('preserves recursive payloads wrapped in Promise for input validation', () => { + type Recursive = { value: number; children?: Array } + type InputPromiseArray = Promise> + expectTypeOf< + ValidateSerializable + >().branded.toEqualTypeOf() + }) + + it('preserves recursive payloads wrapped in ReadableStream for input validation', () => { + type Recursive = { value: number; next?: Recursive } + type InputStream = ReadableStream + expectTypeOf< + ValidateSerializable + >().branded.toEqualTypeOf() + }) + it('should preserve recursive payload without infinite expansion', () => { type Result = Array | { [key: string]: Result } expectTypeOf< From 3c5f2cf2d816e749ec4a4affeba05b7e0566a1de Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 16:15:04 +0100 Subject: [PATCH 10/14] use more efficient tuple test --- packages/router-core/src/ssr/serializer/transformer.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 65dd7009a5..15541bee55 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -79,13 +79,9 @@ type ApplyArrayValidation< ? ValidateSerializable : ValidateSerializableResult -type IsTuple> = T extends readonly [] - ? true - : T extends readonly [unknown, ...infer TRest] - ? TRest extends ReadonlyArray - ? true - : false - : false +type IsTuple> = number extends T['length'] + ? false + : true type ValidateSerializableArrayCore< T extends ReadonlyArray, From 0bbb80087d9b7e4721bbe662865b16d267ff825e Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 16:15:29 +0100 Subject: [PATCH 11/14] get rid of separate IsTuple helper just do the check inline --- packages/router-core/src/ssr/serializer/transformer.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 15541bee55..4574809388 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -79,20 +79,16 @@ type ApplyArrayValidation< ? ValidateSerializable : ValidateSerializableResult -type IsTuple> = number extends T['length'] - ? false - : true - type ValidateSerializableArrayCore< T extends ReadonlyArray, TSerializable, TKind extends 'input' | 'result', > = T extends any - ? IsTuple extends true - ? { [K in keyof T]: ApplyArrayValidation } - : T extends Array + ? number extends T['length'] + ? T extends Array ? Array> : ReadonlyArray> + : { [K in keyof T]: ApplyArrayValidation } : never type ValidateSerializableArray< From b94486cabcf55233e056c1750bfdbd1b35bd865d Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 20:53:37 +0100 Subject: [PATCH 12/14] we gotta treat tuples differently i guess --- .../src/ssr/serializer/transformer.ts | 82 +++++++++++-------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 4574809388..bba2b3f667 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -30,8 +30,8 @@ export interface CreateSerializationAdapterOptions { fromSerializable: (value: TOutput) => TInput } -export type ValidateSerializable = T extends unknown - ? T extends ReadonlyArray +export type ValidateSerializable = + T extends ReadonlyArray ? ValidateSerializableArray : T extends TSerializable ? T @@ -45,8 +45,9 @@ export type ValidateSerializable = T extends unknown ? ValidateSerializableSet : T extends Map ? ValidateSerializableMap - : { [K in keyof T]: ValidateSerializable } - : never + : { + [K in keyof T]: ValidateSerializable + } export type ValidateSerializablePromise = T extends Promise @@ -71,30 +72,27 @@ export type ValidateSerializableMap = > : never -type ApplyArrayValidation< - TValue, - TSerializable, - TKind extends 'input' | 'result', -> = TKind extends 'input' - ? ValidateSerializable - : ValidateSerializableResult - -type ValidateSerializableArrayCore< +type ValidateSerializableArray< T extends ReadonlyArray, TSerializable, - TKind extends 'input' | 'result', -> = T extends any - ? number extends T['length'] - ? T extends Array - ? Array> - : ReadonlyArray> - : { [K in keyof T]: ApplyArrayValidation } - : never +> = number extends T['length'] + ? T extends Array + ? Array> + : ReadonlyArray> + : ValidateSerializableTuple -type ValidateSerializableArray< +type ValidateSerializableTuple< T extends ReadonlyArray, TSerializable, -> = ValidateSerializableArrayCore +> = T extends readonly [infer THead, ...infer TTail] + ? readonly [ + ValidateSerializable, + ...ValidateSerializableTuple< + TTail extends ReadonlyArray ? TTail : [], + TSerializable + >, + ] + : T export type RegisteredReadableStream = unknown extends SerializerExtensions['ReadableStream'] @@ -199,26 +197,38 @@ export type RegisteredSerializationAdapters = RegisteredConfigType< export type ValidateSerializableInputResult = ValidateSerializableResult> -export type ValidateSerializableResult = T extends unknown - ? T extends ReadonlyArray +export type ValidateSerializableResult = + T extends ReadonlyArray ? ValidateSerializableResultArray : T extends TSerializable ? T - : T extends (...args: Array) => any - ? 'Function is not serializable' - : unknown extends SerializerExtensions['ReadableStream'] - ? { [K in keyof T]: ValidateSerializableResult } - : T extends SerializerExtensions['ReadableStream'] - ? ReadableStream - : { - [K in keyof T]: ValidateSerializableResult - } - : never + : unknown extends SerializerExtensions['ReadableStream'] + ? { [K in keyof T]: ValidateSerializableResult } + : T extends SerializerExtensions['ReadableStream'] + ? ReadableStream + : { [K in keyof T]: ValidateSerializableResult } type ValidateSerializableResultArray< T extends ReadonlyArray, TSerializable, -> = ValidateSerializableArrayCore +> = number extends T['length'] + ? T extends Array + ? Array> + : ReadonlyArray> + : ValidateSerializableResultTuple + +type ValidateSerializableResultTuple< + T extends ReadonlyArray, + TSerializable, +> = T extends readonly [infer THead, ...infer TTail] + ? readonly [ + ValidateSerializableResult, + ...ValidateSerializableResultTuple< + TTail extends ReadonlyArray ? TTail : [], + TSerializable + >, + ] + : T export type RegisteredSSROption = unknown extends RegisteredConfigType From c0d048aab3c47e66cf04e7fa1b5808bfa0878eae Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 22:22:06 +0100 Subject: [PATCH 13/14] i sure hope an abstraction doesn't break things again --- .../src/ssr/serializer/transformer.ts | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index bba2b3f667..0a55aed499 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -32,7 +32,7 @@ export interface CreateSerializationAdapterOptions { export type ValidateSerializable = T extends ReadonlyArray - ? ValidateSerializableArray + ? ResolveArrayShape : T extends TSerializable ? T : T extends (...args: Array) => any @@ -45,9 +45,7 @@ export type ValidateSerializable = ? ValidateSerializableSet : T extends Map ? ValidateSerializableMap - : { - [K in keyof T]: ValidateSerializable - } + : { [K in keyof T]: ValidateSerializable } export type ValidateSerializablePromise = T extends Promise @@ -72,28 +70,6 @@ export type ValidateSerializableMap = > : never -type ValidateSerializableArray< - T extends ReadonlyArray, - TSerializable, -> = number extends T['length'] - ? T extends Array - ? Array> - : ReadonlyArray> - : ValidateSerializableTuple - -type ValidateSerializableTuple< - T extends ReadonlyArray, - TSerializable, -> = T extends readonly [infer THead, ...infer TTail] - ? readonly [ - ValidateSerializable, - ...ValidateSerializableTuple< - TTail extends ReadonlyArray ? TTail : [], - TSerializable - >, - ] - : T - export type RegisteredReadableStream = unknown extends SerializerExtensions['ReadableStream'] ? never @@ -199,7 +175,7 @@ export type ValidateSerializableInputResult = export type ValidateSerializableResult = T extends ReadonlyArray - ? ValidateSerializableResultArray + ? ResolveArrayShape : T extends TSerializable ? T : unknown extends SerializerExtensions['ReadableStream'] @@ -208,28 +184,6 @@ export type ValidateSerializableResult = ? ReadableStream : { [K in keyof T]: ValidateSerializableResult } -type ValidateSerializableResultArray< - T extends ReadonlyArray, - TSerializable, -> = number extends T['length'] - ? T extends Array - ? Array> - : ReadonlyArray> - : ValidateSerializableResultTuple - -type ValidateSerializableResultTuple< - T extends ReadonlyArray, - TSerializable, -> = T extends readonly [infer THead, ...infer TTail] - ? readonly [ - ValidateSerializableResult, - ...ValidateSerializableResultTuple< - TTail extends ReadonlyArray ? TTail : [], - TSerializable - >, - ] - : T - export type RegisteredSSROption = unknown extends RegisteredConfigType ? SSROption @@ -262,3 +216,36 @@ export type ValidateSerializableLifecycleResultSSR< : RegisteredSSROption extends false ? any : ValidateSerializableInput> + +type ResolveArrayShape< + T extends ReadonlyArray, + TSerializable, + TMode extends 'input' | 'result', +> = number extends T['length'] + ? T extends Array + ? Array> + : ReadonlyArray> + : ResolveTupleShape + +type ResolveTupleShape< + T extends ReadonlyArray, + TSerializable, + TMode extends 'input' | 'result', +> = T extends readonly [infer THead, ...infer TTail] + ? readonly [ + ResolveArrayElement, + ...ResolveTupleShape< + TTail extends ReadonlyArray ? TTail : [], + TSerializable, + TMode + >, + ] + : T + +type ResolveArrayElement< + TValue, + TSerializable, + TMode extends 'input' | 'result', +> = TMode extends 'input' + ? ValidateSerializable + : ValidateSerializableResult From 7b18dc8ce998526518329690ceb48d63a0df6099 Mon Sep 17 00:00:00 2001 From: Brooke Holmes Date: Wed, 24 Sep 2025 22:52:54 +0100 Subject: [PATCH 14/14] wip --- .../src/ssr/serializer/transformer.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/router-core/src/ssr/serializer/transformer.ts b/packages/router-core/src/ssr/serializer/transformer.ts index 0a55aed499..564800811f 100644 --- a/packages/router-core/src/ssr/serializer/transformer.ts +++ b/packages/router-core/src/ssr/serializer/transformer.ts @@ -223,8 +223,8 @@ type ResolveArrayShape< TMode extends 'input' | 'result', > = number extends T['length'] ? T extends Array - ? Array> - : ReadonlyArray> + ? Array> + : ReadonlyArray> : ResolveTupleShape type ResolveTupleShape< @@ -233,19 +233,15 @@ type ResolveTupleShape< TMode extends 'input' | 'result', > = T extends readonly [infer THead, ...infer TTail] ? readonly [ - ResolveArrayElement, - ...ResolveTupleShape< - TTail extends ReadonlyArray ? TTail : [], - TSerializable, - TMode - >, + ArrayModeResult, + ...ResolveTupleShape, TSerializable, TMode>, ] : T -type ResolveArrayElement< +type ArrayModeResult< + TMode extends 'input' | 'result', TValue, TSerializable, - TMode extends 'input' | 'result', > = TMode extends 'input' ? ValidateSerializable : ValidateSerializableResult