Skip to content

Conversation

@juliusmarminge
Copy link
Contributor

@juliusmarminge juliusmarminge commented Oct 27, 2025

allows restricting the type on both "branches" in an isomorphic fn.

Notes

  • Use special naming convention $withType to signal that it's a special thing, does not have any affect at runtime
  • When called with $withType(), both .server() and .client() must be chained
    • This is not (currently) enforced by the compiler, just typescript.
    • An argument could be made that when the return type of the type parameter passed to $withType extends undefined, then it should be fine that one of server/client can be omitted? idk...

Summary by CodeRabbit

  • New Features

    • Added $withType() to isomorphic functions to declare explicit server/client signatures and expose typed server-only, client-only, and both-side variants.
  • Tests

    • Added type-checking tests validating typed isomorphic-function chains and expected type errors.
  • Documentation

    • Added docs demonstrating how to restrict and enforce server/client implementation signatures with examples.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

Adds type-level APIs to constrain and chain isomorphic functions: new interfaces (ClientImplRequired, ServerImplRequired, IsomorphicFnWithType), a $withType generic on the base returned by createIsomorphicFn(), and corresponding tests, snapshots, docs, and examples exercising the new typed chaining.

Changes

Cohort / File(s) Summary
Core type additions
packages/start-client-core/src/createIsomorphicFn.ts
Introduces ClientImplRequired, ServerImplRequired, IsomorphicFnWithType; extends IsomorphicFnBase with $withType; returns a baseFns object that exposes $withType, server, and client while preserving runtime behavior and chainability.
Type tests
packages/start-client-core/src/tests/createIsomorphicFn.test-d.ts
Adds type-only tests for .$withType validating signature constraints, correct return unions, and ts-expect-error cases for invalid types.
Snapshots (client/server)
packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx, packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx
Adds withTypeRestriction constant entries and minor newline/format adjustments to reflect the new typed API usage in examples.
Integration test file
packages/start-plugin-core/tests/createIsomorphicFn/test-files/createIsomorphicFnDestructured.tsx
Uses .$withType<() => string>() in the isomorphic function chain to demonstrate explicit type restriction for server/client implementations.
Documentation
docs/start/framework/react/guide/environment-functions.md
Adds "Restricting the type of the implementations" section showing .$withType<TFn>() usage, examples, and TypeScript enforcement notes.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant Core as createIsomorphicFn()
  participant Base as baseFns (has $withType, server, client)
  participant Typed as $withType<TFn>()
  participant ServerReg as .server(serverImpl)
  participant ClientReg as .client(clientImpl)

  Note over Core,Base: createIsomorphicFn returns baseFns exposing $withType and registration methods
  Dev->>Core: call createIsomorphicFn()
  Core-->>Dev: returns Base
  Dev->>Typed: Base.$withType<() => R>()
  Typed-->>Dev: returns typed chaining object (IsomorphicFnWithType)
  Dev->>ServerReg: .server((...args)=>R)
  Dev->>ClientReg: .client((...args)=>R)
  Note right of ClientReg: chain produces IsomorphicFn<TArgs, R> ready for use
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • Focus review on packages/start-client-core/src/createIsomorphicFn.ts generics and return types for backward compatibility.
  • Verify createIsomorphicFn.test-d.ts placements of ts-expect-error.
  • Confirm snapshots and docs accurately reflect the new API and contain no unintended runtime changes.

Suggested reviewers

  • GuilhermeAmado

Poem

🐰
I hopped through typings, neat and bright,
Chained $withType in morning light,
Server, client now agree,
I munched a carrot, typed and free,
✨🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 pull request title "feat: add $withType<TFn>() to restrict isomorphicFn" directly and accurately summarizes the main change in the changeset. The PR adds a new $withType method to the isomorphic function builder with accompanying type-level constructs (ClientImplRequired, ServerImplRequired, IsomorphicFnWithType) to enable typed enforcement of matching server and client implementations. The title is concise, specific, and uses clear terminology that would help a teammate understand the primary feature being introduced without ambiguity.
✨ 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 b3b9da9 and 0144e3c.

📒 Files selected for processing (1)
  • packages/start-client-core/src/createIsomorphicFn.ts (2 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/createIsomorphicFn.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/createIsomorphicFn.ts
🔇 Additional comments (5)
packages/start-client-core/src/createIsomorphicFn.ts (5)

24-28: LGTM! Enforces client implementation requirement correctly.

The ClientImplRequired interface correctly enforces that when .server() is called first, .client() must follow. The return type IsomorphicFn<TArgs, TReturnType, TReturnType> appropriately reflects that both branches return the same type once both implementations are provided.


30-34: LGTM! Symmetric enforcement for server implementation.

The ServerImplRequired interface mirrors ClientImplRequired and correctly enforces that when .client() is called first, .server() must follow. The design maintains symmetry regardless of call order.


36-43: LGTM! Intentionally non-callable to enforce complete implementation.

The IsomorphicFnWithType interface deliberately does NOT extend IsomorphicFn, making it non-callable until both .server() and .client() implementations are provided. This design aligns with the PR objective that both branches must be chained when using $withType(), enforcing complete type-safe implementation at compile time.


46-48: LGTM! Type extraction pattern is sound.

The $withType method correctly extracts Parameters and ReturnType from the generic function type, enabling type-safe restrictions like .$withType<() => string>(). The naming follows the $-prefix convention indicating no runtime effect.


60-68: LGTM! Dummy implementation supports pre-transform chaining correctly.

The implementation correctly addresses past review feedback:

  • baseFns object extracted and reused for both root methods and $withType return
  • $withType: () => baseFns returns a chainable object supporting .server() and .client() chains
  • The as any cast is necessary and appropriate for a dummy implementation that will be replaced by the transformer

The structure allows pre-transform code to execute without runtime errors while maintaining the correct API surface.


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.

@nx-cloud
Copy link

nx-cloud bot commented Oct 27, 2025

View your CI Pipeline Execution ↗ for commit 0144e3c

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

☁️ Nx Cloud last updated this comment at 2025-10-27 04:44:51 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 27, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: 0144e3c

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5ac27b and 5cc2c13.

📒 Files selected for processing (5)
  • packages/start-client-core/src/createIsomorphicFn.ts (2 hunks)
  • packages/start-client-core/src/tests/createIsomorphicFn.test-d.ts (1 hunks)
  • packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx (1 hunks)
  • packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx (1 hunks)
  • packages/start-plugin-core/tests/createIsomorphicFn/test-files/createIsomorphicFnDestructured.tsx (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/createIsomorphicFn.ts
  • packages/start-client-core/src/tests/createIsomorphicFn.test-d.ts
  • packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx
  • packages/start-plugin-core/tests/createIsomorphicFn/test-files/createIsomorphicFnDestructured.tsx
  • packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx
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/createIsomorphicFn.ts
  • packages/start-client-core/src/tests/createIsomorphicFn.test-d.ts
  • packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx
  • packages/start-plugin-core/tests/createIsomorphicFn/test-files/createIsomorphicFnDestructured.tsx
  • packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx
🧬 Code graph analysis (2)
packages/start-client-core/src/tests/createIsomorphicFn.test-d.ts (1)
packages/start-client-core/src/createIsomorphicFn.ts (1)
  • createIsomorphicFn (65-71)
packages/start-plugin-core/tests/createIsomorphicFn/test-files/createIsomorphicFnDestructured.tsx (1)
packages/start-client-core/src/createIsomorphicFn.ts (1)
  • createIsomorphicFn (65-71)
🔇 Additional comments (7)
packages/start-client-core/src/createIsomorphicFn.ts (3)

24-31: Typed server-only surface looks good

Names, variance, and return types align with existing non-typed variant. No issues.


39-47: IsomorphicFnWithType surface is coherent

API returns typed server/client builders and preserves union until both sides exist. Good.


50-53: $withType signature is spot on

Generic constraint via AnyFn and mapping to Parameters/ReturnType is correct.

packages/start-plugin-core/tests/createIsomorphicFn/test-files/createIsomorphicFnDestructured.tsx (1)

37-40: Good addition to exercise typed fluent chain

This usage cleanly validates the transformer path with $withType before server/client.

packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx (1)

16-17: Client snapshot matches expectation

withTypeRestriction reduces to the client branch. Looks correct.

packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx (1)

16-17: Server snapshot matches expectation

withTypeRestriction reduces to the server branch. Looks correct.

packages/start-client-core/src/tests/createIsomorphicFn.test-d.ts (1)

74-114: Review comment contains incorrect type assertion

The recommended test for onlyClient has an incorrect expectation. Based on the type definitions in packages/start-client-core/src/createIsomorphicFn.ts:

  • ServerOnlyFnWithType extends IsomorphicFn<TArgs, TReturnType> with TClient = undefined, so return type is correctly TReturnType | undefined.
  • ClientOnlyFnWithType extends IsomorphicFn<TArgs, TReturnType, TReturnType>, so return type is TReturnType | TReturnType which simplifies to TReturnType (not a union with undefined).

The suggested assertion expectTypeOf(onlyClient).returns.toEqualTypeOf<string | undefined>() contradicts the actual type definition, which already guarantees string. The property checks are sound, but the return type expectation for the client-only case needs correction:

expectTypeOf(onlyClient).returns.toEqualTypeOf<string>()  // not string | undefined

Likely an incorrect or invalid review comment.

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

♻️ Duplicate comments (2)
packages/start-client-core/src/createIsomorphicFn.ts (2)

32-37: Critical: Type unsoundness persists in ClientOnlyFnWithType

This interface still extends IsomorphicFn<TArgs, TReturnType, TReturnType>, which incorrectly suggests the server branch returns TReturnType when only the client is implemented. At runtime, server-side calls return undefined for client-only functions. For consistency with ClientOnlyFn (line 18) and runtime behavior, this must extend IsomorphicFn<TArgs, undefined, TReturnType>.

Apply this diff:

-export interface ClientOnlyFnWithType<TArgs extends Array<any>, TReturnType>
-  extends IsomorphicFn<TArgs, TReturnType, TReturnType> {
+export interface ClientOnlyFnWithType<TArgs extends Array<any>, TReturnType>
+  extends IsomorphicFn<TArgs, undefined, TReturnType> {
   server: (
     serverImpl: (...args: TArgs) => TReturnType,
   ) => IsomorphicFn<TArgs, TReturnType, TReturnType>
 }

66-70: Critical: $withType breaks method chains

The $withType: () => {} implementation still returns an empty function, causing runtime TypeError when chains like .$withType<...>().server(...) execute before the transformer runs. This was previously identified with confirmed usage sites in test files. The method must return a chainable object matching the same shape as the root.

Apply this diff:

 export function createIsomorphicFn(): IsomorphicFnBase {
   return {
-    $withType: () => {},
+    $withType: () => ({
+      server: () => ({ client: () => () => {} }),
+      client: () => ({ server: () => () => {} }),
+    }),
     server: () => ({ client: () => () => {} }),
     client: () => ({ server: () => () => {} }),
   } as any
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc2c13 and 72ad1dc.

📒 Files selected for processing (1)
  • packages/start-client-core/src/createIsomorphicFn.ts (2 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/createIsomorphicFn.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/createIsomorphicFn.ts
🧬 Code graph analysis (1)
packages/start-client-core/src/createIsomorphicFn.ts (1)
packages/start-client-core/src/index.tsx (2)
  • IsomorphicFn (10-10)
  • IsomorphicFnBase (13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (1)
packages/start-client-core/src/createIsomorphicFn.ts (1)

24-30: LGTM: ServerOnlyFnWithType correctly typed

The interface correctly extends IsomorphicFn<TArgs, TReturnType, undefined> (via the default), meaning server returns TReturnType and client returns undefined. The .client() method appropriately returns IsomorphicFn<TArgs, TReturnType, TReturnType> after adding a client implementation.

@juliusmarminge juliusmarminge force-pushed the julius/type-restrict-createIsomorphicFn branch from 72ad1dc to 4d75ba6 Compare October 27, 2025 04:17
@github-actions github-actions bot added the documentation Everything documentation related label Oct 27, 2025
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 (1)
docs/start/framework/react/guide/environment-functions.md (1)

86-101: Minor grammar refinement for clarity.

Line 88 has a subject-verb agreement issue. The phrase "enforce the type of both the server and client implementations matches" should be "enforce that both implementations match" or restructured for grammatical consistency.

Suggested revision:

- You can use `.$withType<TFn>()` to enforce the type of both the server and client implementations matches the same function signature.
+ You can use `.$withType<TFn>()` to enforce that both the server and client implementations match the specified function signature.

The code example is clear and accurately demonstrates the feature.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea98dbb and b3b9da9.

📒 Files selected for processing (1)
  • docs/start/framework/react/guide/environment-functions.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Use internal docs links relative to the docs/ folder (e.g., ./guide/data-loading)

Files:

  • docs/start/framework/react/guide/environment-functions.md
docs/{router,start}/**

📄 CodeRabbit inference engine (AGENTS.md)

Place router docs under docs/router/ and start framework docs under docs/start/

Files:

  • docs/start/framework/react/guide/environment-functions.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (1)
docs/start/framework/react/guide/environment-functions.md (1)

103-107: Note accuracy confirmed.

The note correctly states that TypeScript will enforce both server and client implementations when using $withType(). This aligns with the PR objectives and provides helpful guidance to users about the type-level requirements.

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