Skip to content

Conversation

@canac
Copy link

@canac canac commented Oct 22, 2025

The inputValidator in createServerFn doesn't currently support async validate. However, the execValidator result is awaited, even though it doesn't return a promise. Thus, I don't see a technical reason why inputValidator couldn't support async validation. I'm proposing this change because I'm hoping to be able to use an async zod refiner in my application.

Summary by CodeRabbit

  • New Features

    • Added support for async validation in server functions, allowing validators to perform asynchronous operations.
    • Introduced a new async validation demo page.
  • Bug Fixes

    • Fixed a typo in navigation text.
  • Tests

    • Added comprehensive test coverage for async validation scenarios with valid and invalid inputs.
    • Expanded type tests for various validator configurations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

The PR adds support for async validators in the server functions framework by making the execValidator function async and updating its return type. Type definitions are updated to accommodate Promise-based validator outputs. A new /async-validation route is added to the e2e test suite with corresponding tests and navigation links.

Changes

Cohort / File(s) Change Summary
Core Async Validator Support
packages/start-client-core/src/createServerFn.ts
Made execValidator function async and returns Promise<unknown>. Now awaits synchronous-style validation results and returns result.value for '~standard' validators. Error handling maintained.
Type System Updates
packages/start-client-core/src/createMiddleware.ts
Updated IntersectAllValidatorOutputs type to wrap validator output with Awaited<...>, accommodating both sync and Promise-based validator outputs.
Type Test Coverage
packages/start-client-core/src/tests/createServerFn.test-d.ts
Renamed existing test and added comprehensive type tests for async validators, sync/async parse methods, and standard validators with various configurations.
New E2E Route & Navigation
e2e/react-start/server-functions/src/routes/async-validation.tsx, e2e/react-start/server-functions/src/routes/index.tsx
Added /async-validation route with async validation schema using zod, server function handler, and UI component with test buttons. Fixed typo ("dead code elimation" → "elimination") and added navigation link.
Route Registration
e2e/react-start/server-functions/src/routeTree.gen.ts
Generated route tree updated to include AsyncValidationRoute with full integration into route maps, type definitions, and TanStack Router module augmentation.
E2E Test Suite
e2e/react-start/server-functions/tests/server-functions.spec.ts
Added new test suite "server functions with async validation" with tests for valid and invalid inputs, including 500 error whitelisting.

Sequence Diagram

sequenceDiagram
    participant Client as UI Component
    participant ServerFn as asyncValidationServerFn
    participant ExecValidator as execValidator (async)
    participant Schema as asyncValidationSchema
    
    Client->>ServerFn: callServerFn('valid')
    ServerFn->>ExecValidator: execValidator(schema, input)
    activate ExecValidator
    ExecValidator->>Schema: validate('valid')
    Schema-->>ExecValidator: { success, value }
    ExecValidator->>ExecValidator: Check issues
    ExecValidator-->>ServerFn: Promise<'valid'>
    deactivate ExecValidator
    ServerFn-->>Client: result = 'valid'
    
    Note over Client,ExecValidator: Error Path
    Client->>ServerFn: callServerFn('invalid')
    ServerFn->>ExecValidator: execValidator(schema, input)
    activate ExecValidator
    ExecValidator->>Schema: validate('invalid')
    Schema-->>ExecValidator: { success: false, issues }
    ExecValidator->>ExecValidator: Throw error
    ExecValidator-->>ServerFn: Promise rejected
    deactivate ExecValidator
    ServerFn-->>Client: error.message captured
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Critical areas for attention:
    • Type narrowing in createMiddleware.ts with Awaited<...> wrapper—verify correct union/intersection semantics across sync and async validators
    • execValidator async pattern and error propagation—ensure all validator branches handle Promise correctly and errors are properly thrown vs. returned
    • Route integration completeness in generated routeTree.gen.ts—verify all route mappings and type augmentations are consistently updated

Possibly related PRs

  • feat: serverfn abstraction #5040: Refactors createServerFn implementation including validator execution and middleware/validator typing, directly aligned with this PR's core validator changes.
  • fix: various fixes #5215: Updates start-plugin-core to exclude createServerFn.js from runtime filtering, affecting the same artifact modified in this PR.

Suggested reviewers

  • schiller-manuel

Poem

🐰 A validator awaits with a twitch of the ear,
Async paths now clear, no more fear,
"Valid" succeeds, "invalid" does fall,
Promise-wrapped schemas handle them all!
With routes newly traced, the test suite agrees,
Hop forward with validators, if you please!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: enable async serverFn validation" directly and accurately describes the primary objective of the changeset. The core changes modify execValidator to support asynchronous validators by making it async and updating type definitions in createMiddleware.ts to handle Promise-based validator outputs. The supporting changes (e2e tests, example routes, type tests) all reinforce this central goal of enabling async validation in server functions. The title is concise, specific, and clearly communicates the main feature being introduced without vague terminology.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22cc026 and feb392e.

📒 Files selected for processing (1)
  • packages/start-client-core/src/createMiddleware.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/start-client-core/src/createMiddleware.ts
packages/{*-start,start-*}/**

📄 CodeRabbit inference engine (AGENTS.md)

Name and place Start framework packages under packages/-start/ or packages/start-/

Files:

  • packages/start-client-core/src/createMiddleware.ts
🧬 Code graph analysis (1)
packages/start-client-core/src/createMiddleware.ts (1)
packages/router-core/src/validators.ts (1)
  • ResolveValidatorOutput (113-121)
🔇 Additional comments (1)
packages/start-client-core/src/createMiddleware.ts (1)

223-231: <!-- [scratchpad_start]
[task review_file_1/1 review_verification_3/3 COMPLETE]
Verified async validator handling and test coverage.

[observations]

  • Async validator test exists at lines 65-91 (test 'createServerFn with async validator function')
  • Test pattern: .inputValidator((input: string) => Promise.resolve(input))
  • Handler expects: data: string (unwrapped, not Promise)
  • Test also covers async validators with parse method (line 121)
  • Test also covers async standard validators (line 196)
  • All tests expect unwrapped types in handler parameters

[analysis]
The change on line 230 is CORRECT and COMPLETE:

  • ResolveValidatorOutput for async validator returns Promise
  • Awaited<Promise> resolves to T
  • Handler receives unwrapped type, matching test expectations
  • Type hierarchy is correct: raw output (line 142) vs final output (line 143/230)
  • Change is backward compatible: Awaited = T for non-Promise types
  • Existing tests validate this behavior

[conclusion]
Original review comment concerns are RESOLVED:

  • The change properly enables async validators
  • The inline async function pattern is already tested
  • Types are correctly unwrapped for handlers
  • No additional changes needed
  • Code is ready to merge

[tag]

</scratchpad_end] -->

Type change correctly enables async validator support and is verified by existing tests.

The Awaited<...> wrapper on line 230 properly unwraps async validator outputs while maintaining backward compatibility with sync validators. The test file confirms this works as intended: async validators returning Promise<T> are unwrapped to T for the handler data parameter.

Existing tests validate the behavior:

  • Async validator function: (input: string) => Promise.resolve(input) → handler receives data: string
  • Async validator with parse method ✓
  • Async standard validator ✓

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@schiller-manuel
Copy link
Contributor

interesting!
we should also make sure any kind of async function works, in terms of types and runtime

we definitely need e2e tests in e2e/react-start/server-functions

@canac
Copy link
Author

canac commented Oct 24, 2025

@schiller-manuel I tried to get the type-level test to work with a Standard validator like this:

const validator = {
  ['~standard']: {
     validate: (input: { input: string }) => ({ value: { a: input } }),
  },
}

But the type inference wasn't working. That's why I added zod. If you don't want to add zod to that package, I may need help with the "createServerFn with standard validator" test. With a working baseline, I'm sure I can get a working "createServerFn with async standard validator".

EDIT: I got it working without zod using the standard schema validator interface. 🎉

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: 0

🧹 Nitpick comments (2)
packages/start-client-core/package.json (1)

89-89: Consider upgrading to Zod 4 for enhanced async validation support.

The PR uses Zod 3.24.2, but Zod 4 is available and includes significant improvements for async validation scenarios, including better error handling and more consistent async behavior. Since this PR specifically enables async validation, upgrading to Zod 4 would provide better alignment with the feature's goals.

Apply this diff:

-    "zod": "^3.24.2"
+    "zod": "^4.0.0"
e2e/react-start/server-functions/src/routes/async-validation.tsx (1)

10-12: Add a custom error message to the async refine validation.

The current .refine() implementation returns a boolean, which will result in a generic Zod error message. For better user experience and more meaningful test assertions, provide a custom error message.

Apply this diff:

 const asyncValidationSchema = z
   .object({ value: z.string() })
-  .refine(async (data) => Promise.resolve(data.value !== 'invalid'))
+  .refine(
+    async (data) => Promise.resolve(data.value !== 'invalid'),
+    { message: 'Invalid input' }
+  )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87fae60 and 7c122d2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • e2e/react-start/server-functions/src/routeTree.gen.ts (11 hunks)
  • e2e/react-start/server-functions/src/routes/async-validation.tsx (1 hunks)
  • e2e/react-start/server-functions/src/routes/index.tsx (1 hunks)
  • e2e/react-start/server-functions/tests/server-functions.spec.ts (1 hunks)
  • packages/start-client-core/package.json (1 hunks)
  • packages/start-client-core/src/tests/createServerFn.test-d.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Use workspace:* protocol for internal dependencies in package.json files

Files:

  • packages/start-client-core/package.json
packages/{*-start,start-*}/**

📄 CodeRabbit inference engine (AGENTS.md)

Name and place Start framework packages under packages/-start/ or packages/start-/

Files:

  • packages/start-client-core/package.json
  • packages/start-client-core/src/tests/createServerFn.test-d.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • e2e/react-start/server-functions/src/routes/async-validation.tsx
  • e2e/react-start/server-functions/src/routeTree.gen.ts
  • e2e/react-start/server-functions/tests/server-functions.spec.ts
  • e2e/react-start/server-functions/src/routes/index.tsx
  • packages/start-client-core/src/tests/createServerFn.test-d.ts
**/src/routes/**

📄 CodeRabbit inference engine (AGENTS.md)

Place file-based routes under src/routes/ directories

Files:

  • e2e/react-start/server-functions/src/routes/async-validation.tsx
  • e2e/react-start/server-functions/src/routes/index.tsx
e2e/**

📄 CodeRabbit inference engine (AGENTS.md)

Store end-to-end tests under the e2e/ directory

Files:

  • e2e/react-start/server-functions/src/routes/async-validation.tsx
  • e2e/react-start/server-functions/src/routeTree.gen.ts
  • e2e/react-start/server-functions/tests/server-functions.spec.ts
  • e2e/react-start/server-functions/src/routes/index.tsx
🧬 Code graph analysis (2)
e2e/react-start/server-functions/src/routes/async-validation.tsx (1)
e2e/react-start/server-functions/src/routes/index.tsx (1)
  • Route (3-5)
packages/start-client-core/src/tests/createServerFn.test-d.ts (1)
packages/start-client-core/src/createServerFn.ts (1)
  • createServerFn (49-170)
🔇 Additional comments (9)
e2e/react-start/server-functions/src/routes/index.tsx (2)

66-66: Good catch on the typo fix.


72-76: LGTM! Navigation link properly added.

The new async validation test route link follows the existing pattern and is properly integrated into the navigation list.

packages/start-client-core/src/tests/createServerFn.test-d.ts (3)

2-2: LGTM! Zod import added for validator tests.


66-97: Good type-level test coverage for standard Zod validator.

The test properly verifies that Zod schemas with transforms are correctly typed through the validator chain, ensuring the transformed output type flows through to the handler's data parameter.


99-134: Excellent! Async validator type support verified.

This test confirms that async Zod validators (using .refine(async ...)) maintain correct type inference throughout the chain, which directly addresses the maintainer's concern about TypeScript type support for async functions.

e2e/react-start/server-functions/tests/server-functions.spec.ts (1)

316-362: Excellent e2e test coverage for async validation!

The test suite properly covers both success and failure paths for async validation, correctly whitelists expected validation errors, and follows Playwright best practices. This directly addresses the maintainer's request for end-to-end tests under e2e/react-start/server-functions.

e2e/react-start/server-functions/src/routes/async-validation.tsx (2)

14-16: LGTM! Server function properly configured with async validator.

The server function correctly applies the input validator and extracts the validated data.


18-66: Solid component implementation with proper error handling.

The component correctly manages async state, handles errors, and provides test IDs for e2e testing. The error handling properly extracts the message from Error instances.

e2e/react-start/server-functions/src/routeTree.gen.ts (1)

23-23: Auto-generated route tree properly updated.

All changes are mechanical additions for the new /async-validation route, following the consistent pattern used for other routes. The file header correctly indicates this file is auto-generated and should not be manually edited.

Also applies to: 92-96, 164-164, 190-190, 217-217, 245-245, 271-271, 297-297, 324-324, 427-433, 524-524

@nx-cloud
Copy link

nx-cloud bot commented Oct 24, 2025

View your CI Pipeline Execution ↗ for commit feb392e

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

☁️ Nx Cloud last updated this comment at 2025-10-27 19:01:36 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 24, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5583

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5583

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: feb392e

@schiller-manuel
Copy link
Contributor

can we also just to inputValidator(async(foo:string) => foo) ?

@schiller-manuel
Copy link
Contributor

the types for this are not yet correct:

  const someFunction   = createServerFn()
  .inputValidator(async(data: string) => data)
  .handler(({ data }) => data)
Screenshot 2025-10-26 at 19 55 54

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

🧹 Nitpick comments (1)
packages/start-client-core/src/tests/createServerFn.test-d.ts (1)

151-168: Consider extracting standard validator interfaces to reduce duplication.

The SyncValidator and AsyncValidator interfaces are defined inline and share similar structure. While this keeps tests self-contained, you could reduce duplication by extracting these to helper types at the file level.

Example refactor:

// At file level
type StandardValidatorShape<TSync extends boolean = false> = {
  readonly '~standard': {
    types?: {
      input: string
      output: string
    }
    validate: (input: unknown) => TSync extends true
      ? { value: string }
      : Promise<{ value: string }>
  }
}

// In tests
const syncValidator: StandardValidatorShape<true> = { ... }
const asyncValidator: StandardValidatorShape<false> = { ... }

However, keeping them inline is also acceptable for test clarity and independence.

Also applies to: 197-215

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38af395 and 22cc026.

📒 Files selected for processing (2)
  • packages/router-core/src/validators.ts (1 hunks)
  • packages/start-client-core/src/tests/createServerFn.test-d.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/router-core/src/validators.ts
  • packages/start-client-core/src/tests/createServerFn.test-d.ts
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep framework-agnostic core router logic in packages/router-core/

Files:

  • packages/router-core/src/validators.ts
packages/{*-start,start-*}/**

📄 CodeRabbit inference engine (AGENTS.md)

Name and place Start framework packages under packages/-start/ or packages/start-/

Files:

  • packages/start-client-core/src/tests/createServerFn.test-d.ts
🧬 Code graph analysis (1)
packages/start-client-core/src/tests/createServerFn.test-d.ts (1)
packages/start-client-core/src/createServerFn.ts (1)
  • createServerFn (49-170)
🔇 Additional comments (1)
packages/start-client-core/src/tests/createServerFn.test-d.ts (1)

65-241: E2E tests for async validators are already present.

The type-level tests comprehensively cover different async validator shapes (async functions, parse methods, and standard validators). E2E tests also exist at e2e/react-start/server-functions/tests/server-functions.spec.ts (lines 316+) with test cases for both valid and invalid input scenarios at the /async-validation route. Async validator functionality has complete test coverage.

@schiller-manuel
Copy link
Contributor

we will need to wait with this until #5517 is merged

@canac
Copy link
Author

canac commented Oct 27, 2025

we will need to wait with this until #5517 is merged

@schiller-manuel Got it. I'll take a look to see if any changes are needed after that PR merges. Thanks for the code review so far and for pointing out improvements!

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.

2 participants