Skip to content

Conversation

@hokkyss
Copy link
Contributor

@hokkyss hokkyss commented Jan 25, 2026

Summary by CodeRabbit

  • New Features

    • UI now shows Async Foo and Async Car sections (expected vs actual async values); SSR pages include async car honk state.
    • Added a file-based route with a button to invoke a server function that returns sync and async results, shown in-app.
    • Async-aware serialization adapters and helpers added so async data is included alongside existing sync data.
  • Tests

    • Added e2e tests covering async serialization flows and the new server-function route.

✏️ Tip: You can customize this high-level summary in your review settings.

@hokkyss hokkyss changed the title Async adapters feat: support asynchronous serialization adapter Jan 25, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

Adds async-capable data primitives and adapters (AsyncFoo/AsyncCar + adapters), extends core serializer with toSerializableAsync support, registers new adapters, adds a server-function route returning class instances, updates SSR pages, and expands E2E tests to validate async serialization and rendering.

Changes

Cohort / File(s) Summary
Async data & adapters
e2e/*-start/serialization-adapters/src/data.tsx (react, solid, vue)
Introduce AsyncFoo class, AsyncCar interface and factories, asyncFooAdapter and asyncCarAdapter (with toSerializableAsync), add makeAsyncFoo/makeAsyncCar, and extend makeData + RenderData to include async sections.
Core serializer
packages/router-core/src/ssr/serializer/transformer.ts
Add toSerializableAsync?: to adapter/type APIs, introduce MaybePromise handling, and prefer toSerializableAsync at runtime with fallback to sync toSerializable.
Route registration & files
e2e/*-start/serialization-adapters/src/routeTree.gen.ts, e2e/*-start/serialization-adapters/src/routes/server-function/class-instance.tsx
Add /server-function/class-instance route across react/solid/vue route trees and new route files that expose a serverFn returning foo and asyncFoo for client invocation.
Start registration
e2e/*-start/serialization-adapters/src/start.tsx
Import and register asyncFooAdapter and asyncCarAdapter in the serializationAdapters array.
SSR data-only pages
e2e/*-start/serialization-adapters/src/routes/ssr/data-only.tsx
Extract and render expected vs actual async honk state (new testids and separators) alongside existing honk UI.
E2E tests
e2e/*-start/serialization-adapters/tests/app.spec.ts
Add assertions for async foo/car values, SSR async-honk check, and a new "class instance" server-function E2E test exercising async serialization and client rendering.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Browser
    participant Router as Router/Preloader
    participant ServerFn as Server Function
    participant Adapter as Serialization Adapter
    participant Seroval as Seroval Serializer

    Client->>Router: Request /server-function/class-instance
    Router->>ServerFn: Invoke server handler
    ServerFn->>ServerFn: Create Foo (sync) and AsyncFoo/AsyncCar (instances)
    ServerFn-->>Router: Return { foo, asyncFoo, asyncCar }

    Router->>Adapter: Serialize values
    alt adapter implements toSerializableAsync
        Adapter->>Adapter: Await async value (e.g. valueAsync)
        Adapter-->>Seroval: Return async-serialized payload
    else
        Adapter->>Adapter: Use toSerializable (sync)
        Adapter-->>Seroval: Return sync-serialized payload
    end

    Seroval->>Client: Send serialized JSON response
    Client->>Client: Render resp.foo.value and resp.asyncFoo.echo()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • chorobin
  • nlynzaad

Poem

🐇 I hopped through bytes and midnight queues,

Async foos, async cars — delightful news,
Adapters hum, the honks reply,
Server calls bounce, responses fly,
A rabbit applauds — serialized views!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: support asynchronous serialization adapter' accurately reflects the primary change: adding async serialization support throughout the codebase with new adapters and methods.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Jan 25, 2026

View your CI Pipeline Execution ↗ for commit 9216260

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 1m 20s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-27 13:42:15 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 25, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6503

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6503

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6503

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6503

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6503

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6503

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6503

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6503

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6503

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6503

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6503

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6503

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6503

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6503

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6503

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6503

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6503

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6503

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6503

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6503

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6503

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6503

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6503

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6503

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6503

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6503

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6503

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6503

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6503

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6503

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6503

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6503

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6503

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6503

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6503

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6503

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6503

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6503

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6503

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6503

commit: 9216260

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@e2e/react-start/serialization-adapters/src/routes/server-function/class-instance.tsx`:
- Around line 18-33: Replace the untyped state "const [resp, setResp] =
useState<any>(null)" with a strongly-typed state derived from the server
function return type; use Awaited<ReturnType<typeof serverFn>> as the generic
for useState so "resp" has the correct shape (including Foo and AsyncFoo
instances) and update any usages accordingly (e.g., where resp?.foo?.value and
resp?.asyncFoo?.echo() are accessed).

In `@e2e/react-start/serialization-adapters/src/routes/ssr/data-only.tsx`:
- Around line 21-22: Typo in the expected async honk state variable: rename
exepctedAsyncHonkState to expectedAsyncHonkState wherever declared/used (e.g.,
the declarations comparing loaderData.asyncCar.singleInstance.honk() at the
sites referencing exepctedAsyncHonkState and any subsequent assertions); update
both occurrences shown (around the first use and the block at lines 55-57) so
the variable name is spelled consistently as expectedAsyncHonkState and all
references (comparisons/expect calls) are adjusted accordingly.

In `@e2e/react-start/serialization-adapters/tests/app.spec.ts`:
- Around line 99-103: The test named 'class instance' is navigating to the wrong
route; update the page.goto call in the test('class instance', async ({ page })
=> { ... }) to load the class-instance page (replace
'/server-function/custom-error' with the correct class-instance route, e.g.
'/server-function/class-instance') so the test exercises the intended page
before calling awaitPageLoaded(page).
- Around line 20-33: The test is reading the sync expected node
(`${id}-car-expected`) for the async assertion; change the selector used to
build expectedAsyncData to the async expected test id
(`${id}-async-car-expected`) so expectedAsyncData corresponds to the async
section being validated (selectors: `${id}-async-car-expected`,
`${id}-async-car-actual`, `${id}-car-expected`, `${id}-async-foo`).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@e2e/solid-start/serialization-adapters/src/data.tsx`:
- Around line 234-250: The test id for the expected Async Car block is
misspelled; update the data-testid on the expected div so it matches the actual
and other files: change the string template from `${id}-casync-ar-expected` to
`${id}-async-car-expected` in the component rendering the expected JSON for
localData.asyncCar.singleInstance (the div near the JSX that references
localData.asyncCar and data.asyncCar).

In `@e2e/vue-start/serialization-adapters/src/data.tsx`:
- Around line 121-123: Operator precedence in makeFoo causes the ternary to only
apply to the left operand of +; change makeFoo to wrap the ternary in
parentheses so the suffix is appended to both branches (i.e. replace new
Foo(typeof window === 'undefined' ? 'server' : 'client' + suffix) with new
Foo((typeof window === 'undefined' ? 'server' : 'client') + suffix)); update the
expression in function makeFoo to match the pattern used by makeAsyncFoo so
server and client both get the suffix consistently.
🧹 Nitpick comments (1)
e2e/vue-start/serialization-adapters/src/data.tsx (1)

102-113: Consider adding toSerializableAsync to asyncCarAdapter for consistency.

The asyncFooAdapter (line 72-78) includes toSerializableAsync, but asyncCarAdapter only has synchronous serialization. If AsyncCar is intended to demonstrate async serialization patterns alongside AsyncFoo, consider adding the async serialization method.

If this is intentional (i.e., AsyncCar demonstrates that not all "async" adapters need async serialization), this is fine as-is.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@e2e/solid-start/serialization-adapters/src/data.tsx`:
- Around line 49-70: The AsyncFoo class is not exported; update the declaration
of AsyncFoo to be exported so other modules can access it (e.g., change class
AsyncFoo to export class AsyncFoo) while keeping its constructor, internalValue,
getters (valueAsync, value) and method echo unchanged; ensure the symbol
AsyncFoo is available for external instanceof checks and imports.
- Around line 121-123: The makeFoo function currently applies suffix only on the
client due to operator precedence; update the return to parenthesize the ternary
so the suffix is appended in both environments (i.e., wrap typeof window ===
'undefined' ? 'server' : 'client' before adding + suffix) to match makeAsyncFoo
behavior, and apply the same change in the corresponding makeFoo implementations
in the Vue and React serialization-adapters files.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
e2e/react-start/serialization-adapters/src/data.tsx (1)

115-173: Fix makeFoo suffix precedence to match makeAsyncFoo.

Without parentheses, the suffix is only appended on the client branch due to operator precedence. The ternary must be wrapped before concatenation to ensure consistent behavior between server and client.

🧭 Correct concatenation order
 export function makeFoo(suffix: string = '') {
-  return new Foo(typeof window === 'undefined' ? 'server' : 'client' + suffix)
+  return new Foo(
+    (typeof window === 'undefined' ? 'server' : 'client') + suffix,
+  )
 }
🤖 Fix all issues with AI agents
In `@e2e/react-start/serialization-adapters/src/data.tsx`:
- Around line 49-79: The test function passed to createSerializationAdapter must
be a TypeScript type predicate so the adapter infers inputs strictly; update the
asyncFooAdapter's test implementation to have the signature (value): value is
AsyncFoo (i.e., return boolean but declared as a predicate) and adjust the
predicate logic to use instanceof AsyncFoo; ensure related functions
toSerializable, toSerializableAsync, and fromSerializable still reference
AsyncFoo to preserve typing.
- Around line 80-113: The test predicate on asyncCarAdapter uses the 'in'
operator unsafely and will throw for null/undefined; update asyncCarAdapter.test
to first guard that the input is non-nullish (e.g., value != null or typeof
value === 'object') before checking '__type' in (value as AsyncCar) and
comparing to 'asynccar', so the test safely returns false for null/undefined
while still recognizing AsyncCar instances created by makeAsyncCar and used with
createSerializationAdapter.
♻️ Duplicate comments (1)
e2e/vue-start/serialization-adapters/src/data.tsx (1)

121-123: Operator precedence still drops the server suffix.

Line 122 has the same precedence issue previously flagged; leaving as-is if that test behavior is intentional.

🧹 Nitpick comments (2)
e2e/solid-start/serialization-adapters/src/data.tsx (1)

102-106: Guard test against non-object values and avoid any.

'__type' in value throws on null/primitives. A defensive guard keeps adapters robust if the registry probes arbitrary values, and improves type safety.

♻️ Suggested hardening
 export const asyncCarAdapter = createSerializationAdapter({
   key: 'asyncCar',
-  test: (value: any): value is AsyncCar =>
-    '__type' in (value as AsyncCar) && value.__type === 'asynccar',
+  test: (value: unknown): value is AsyncCar =>
+    !!value &&
+    typeof value === 'object' &&
+    '__type' in value &&
+    (value as { __type?: string }).__type === 'asynccar',
   toSerializable: (car) => ({
     make: car.make,
     model: car.model,
     year: car.year,
   }),

As per coding guidelines, prefer stricter typing over any.

e2e/vue-start/serialization-adapters/src/data.tsx (1)

102-113: Harden test to avoid in on non-objects and drop any.

Line 104 can throw if the serializer probes primitives or null. Use unknown and a safe object guard before in. As per coding guidelines, avoid any in TS/TSX.

♻️ Suggested adjustment
 export const asyncCarAdapter = createSerializationAdapter({
   key: 'asyncCar',
-  test: (value: any): value is AsyncCar =>
-    '__type' in (value as AsyncCar) && value.__type === 'asynccar',
+  test: (value: unknown): value is AsyncCar =>
+    !!value &&
+    typeof value === 'object' &&
+    '__type' in value &&
+    (value as AsyncCar).__type === 'asynccar',
   toSerializable: (car) => ({
     make: car.make,
     model: car.model,
     year: car.year,
   }),
   fromSerializable: (value: { make: string; model: string; year: number }) =>
     makeAsyncCar(value),
 })

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@e2e/solid-start/serialization-adapters/src/data.tsx`:
- Around line 72-78: The test callback for asyncFooAdapter currently has an
implicit any; change its signature to a typed type guard such as (value:
unknown): value is AsyncFoo so TypeScript knows the checked type—update the test
in the createSerializationAdapter call (referencing asyncFooAdapter and the test
callback) to use (value: unknown): value is AsyncFoo => value instanceof
AsyncFoo to remove the implicit any and satisfy strict typing.

Comment on lines +72 to +78
export const asyncFooAdapter = createSerializationAdapter({
key: 'asyncFoo',
test: (value) => value instanceof AsyncFoo,
toSerializable: (foo) => foo.value,
toSerializableAsync: (foo) => foo.valueAsync,
fromSerializable: (value) => new AsyncFoo(value),
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -a 'tsconfig.*\.json' -E node_modules -E dist -E build -E .git | head -20

Repository: TanStack/router

Length of output: 1518


🏁 Script executed:

cat e2e/solid-start/serialization-adapters/src/data.tsx | head -100

Repository: TanStack/router

Length of output: 2234


🏁 Script executed:

cat /home/jailuser/git/e2e/solid-start/serialization-adapters/tsconfig.json

Repository: TanStack/router

Length of output: 602


Type the test callback to avoid implicit any.

The test callback at line 74 has an implicit any parameter, which violates the "strict": true TypeScript configuration in effect. Apply a type guard pattern:

 export const asyncFooAdapter = createSerializationAdapter({
   key: 'asyncFoo',
-  test: (value) => value instanceof AsyncFoo,
+  test: (value: unknown): value is AsyncFoo => value instanceof AsyncFoo,
   toSerializable: (foo) => foo.value,
   toSerializableAsync: (foo) => foo.valueAsync,
   fromSerializable: (value) => new AsyncFoo(value),
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const asyncFooAdapter = createSerializationAdapter({
key: 'asyncFoo',
test: (value) => value instanceof AsyncFoo,
toSerializable: (foo) => foo.value,
toSerializableAsync: (foo) => foo.valueAsync,
fromSerializable: (value) => new AsyncFoo(value),
})
export const asyncFooAdapter = createSerializationAdapter({
key: 'asyncFoo',
test: (value: unknown): value is AsyncFoo => value instanceof AsyncFoo,
toSerializable: (foo) => foo.value,
toSerializableAsync: (foo) => foo.valueAsync,
fromSerializable: (value) => new AsyncFoo(value),
})
🤖 Prompt for AI Agents
In `@e2e/solid-start/serialization-adapters/src/data.tsx` around lines 72 - 78,
The test callback for asyncFooAdapter currently has an implicit any; change its
signature to a typed type guard such as (value: unknown): value is AsyncFoo so
TypeScript knows the checked type—update the test in the
createSerializationAdapter call (referencing asyncFooAdapter and the test
callback) to use (value: unknown): value is AsyncFoo => value instanceof
AsyncFoo to remove the implicit any and satisfy strict typing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant