Skip to content

Conversation

@dzhu
Copy link
Contributor

@dzhu dzhu commented Aug 11, 2025

🎯 Changes

Since it is the new and standard thing, we should use Standard Schema to extract input and output types whenever the schema implements it.

Fixes #6887

✅ Checklist

  • I have followed the steps listed in the Contributing guide.
  • If necessary, I have added documentation related to the changes made.
  • I have added or updated the tests related to the changes made. (note: The test uses toEqualTypeOf in a resolver definition like the other Zod branded type test, which doesn't seem to actually cause pnpm test to signal failure if the equality is not satisfied, though it does show up as an error in a TypeScript-aware editor. I'm not sure if a failure there is actually being caught in any automatically visible way.)

Summary by CodeRabbit

  • Refactor
    • Improved type inference for validator integrations by prioritizing the Standard Schema path and clarifying the inference order (no runtime/API behavior changes).
  • Tests
    • Added test coverage verifying branded-type inputs with a new procedure to confirm input/output inference and round‑trip behavior.
  • Documentation
    • Clarified that inference may use the Standard Schema and updated capitalization/links for that reference.

Since it is the new and standard thing, we should use Standard Schema to
extract input and output types whenever the schema implements it.

Fixes trpc#6887
@dzhu dzhu requested review from a team as code owners August 11, 2025 04:08
@vercel
Copy link

vercel bot commented Aug 11, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
www Ready Ready Preview Comment Aug 18, 2025 11:08pm

@vercel
Copy link

vercel bot commented Aug 11, 2025

@dzhu is attempting to deploy a commit to the trpc Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 11, 2025

Walkthrough

Reorders type-level parser inference to prefer Standard Schema-like parsers first, then ParserWithInputOutput, then ParserWithoutInput; adds a zod-branded top test procedure and updates validator docs to reference "Standard Schema".

Changes

Cohort / File(s) Summary
Parser inference
packages/server/src/unstable-core-do-not-import/parser.ts
Reordered inferParser conditional branches to: ParserStandardSchemaEsqueParserWithInputOutputParserWithoutInput, updating inferred in/out mappings accordingly. Exported parser type declaration updated to include the StandardSchema branch first.
Tests — zod branded input
packages/tests/server/validators.test.ts
Added top public procedure using branded AccountId input; asserts opts.input matches branded output type and returns it.
Docs — validators
www/docs/server/validators.md
Textual updates: note that input/output inference can use the Standard Schema interface; capitalization/labels changed to "Standard Schema".

Sequence Diagram(s)

sequenceDiagram
  participant Call as inferParser<TParser>
  participant Types as Parser union

  Call->>Types: check TParser type
  alt ParserStandardSchemaEsque<TIn,TOut>
    Types-->>Call: { in: TIn, out: TOut }
  else ParserWithInputOutput<TIn,TOut>
    Types-->>Call: { in: TIn, out: TOut }
  else ParserWithoutInput<T>
    Types-->>Call: { in: T, out: T }
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Use Standard Schema when available for input/output inference (#6887)
Migrate away from deprecated Zod _input/_output in type inference paths (#6887) The PR prioritizes Standard Schema in inferParser, but it does not show removal/replacement of deprecated Zod _input/_output usage elsewhere.
Validate Zod v4 branded types are inferred correctly in procedures (#6887)

Possibly related PRs

Poem

I nibble at types beneath the tree,
Standard Schema whispers, "come see me."
Branded AccountId snug in my paw,
In and out inferred — neat little awe.
Hooray for hops and types! 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4b0db76 and 0ccdf74.

📒 Files selected for processing (1)
  • packages/server/src/unstable-core-do-not-import/parser.ts (1 hunks)
⏰ 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). (17)
  • GitHub Check: E2E-tests (next-minimal-starter)
  • GitHub Check: E2E-tests (next-prisma-websockets-starter)
  • GitHub Check: E2E-tests (express-server)
  • GitHub Check: E2E-tests (next-formdata)
  • GitHub Check: E2E-tests (express-minimal)
  • GitHub Check: E2E-tests (cloudflare-workers)
  • GitHub Check: E2E-tests (.test/ssg)
  • GitHub Check: E2E-tests (.test/diagnostics-big-router)
  • GitHub Check: e2e-legacy-node (next-prisma-starter, 18.x)
  • GitHub Check: E2E-tests (Bun) (bun, ubuntu-latest)
  • GitHub Check: E2E-tests (.experimental/next-app-dir)
  • GitHub Check: Test a monorepo using built declaration files
  • GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 18.x)
  • GitHub Check: Release using pkg.pr.new
  • GitHub Check: E2E-tests (Deno) (deno-deploy)
  • GitHub Check: test
  • GitHub Check: Lint and auto-fix
🔇 Additional comments (3)
packages/server/src/unstable-core-do-not-import/parser.ts (3)

65-69: Precedence now correctly prefers Standard Schema for type inference — LGTM

This ordering ensures schemas implementing Standard Schema drive inference first. Given that ParserWithInputOutput includes ParserStandardSchemaEsque, the extends Parser bound no longer blocks this branch — reachability is good.


70-74: Correctly infers distinct input/output types for “WithInputOutput” parsers

Switching to $TIn/$TOut fixes cases where input and output differ (e.g., transform schemas), avoiding the old “same-type” conflation. This aligns with the PR goal to avoid deprecated internals.


75-80: Sensible fallback for single-type validators retained

Keeping { in: $InOut; out: $InOut } for parse/assert/create/validateSync-style validators preserves backward compatibility while allowing the new precedence to take effect.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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 (5)
www/docs/server/validators.md (3)

8-8: Tighten phrasing and hyphenation

Small clarity/style tweak and hyphenation:

  • “first class support” → “first-class support”
  • Consider splitting the long sentence for readability.
- tRPC procedures may define validation logic for their input and/or output, and validators are also used to infer the types of inputs and outputs (using the [Standard Schema](https://standardschema.dev) interface if available, or custom interfaces for supported validators if not). We have first class support for many popular validators, and you can [integrate validators](#contributing-your-own-validator-library) which we don't directly support.
+ tRPC procedures may define validation logic for their input and/or output. Validators are also used to infer the types of inputs and outputs (using the [Standard Schema](https://standardschema.dev) interface if available, or custom interfaces for supported validators if not). We have first-class support for many popular validators, and you can [integrate validators](#contributing-your-own-validator-library) which we don't directly support.

172-172: Specify the version: “Standard Schema v1”

Since the code targets v1, make it explicit to reduce ambiguity.

- including any library conforming to [Standard Schema](https://standardschema.dev).
+ including any library conforming to [Standard Schema v1](https://standardschema.dev).

498-498: Tone/style: avoid “Feel free to” and clarify

Consider a more direct tone and minor rewording:

- Conforming to [Standard Schema](https://standardschema.dev) is recommended, but in some cases we may accept a PR to add a new supported interface. Feel free to open an issue for discussion.
+ Conforming to [Standard Schema](https://standardschema.dev) is recommended. In some cases, we may accept a PR to add a new supported interface. You can open an issue to discuss your use case.
packages/tests/server/validators.test.ts (1)

938-942: Nice addition: direct branded scalar route covered

This validates Standard Schema-based inference for scalar inputs. Consider also asserting router-level types for top to mirror the num checks:

   type RouterInput = inferRouterInputs<typeof router>;
   type RouterOutput = inferRouterOutputs<typeof router>;
 
   type AccountIdInput = RouterInput['num']['accountId'];
   //     ^?
   type AccountIdOutput = RouterOutput['num']['accountId'];
   //    ^?
 
   expectTypeOf<AccountIdInput>().toEqualTypeOf<Types['input']>();
   expectTypeOf<AccountIdOutput>().toEqualTypeOf<Types['output']>();
+
+  // Additional coverage for the scalar route:
+  type TopInput = RouterInput['top'];
+  //    ^?
+  type TopOutput = RouterOutput['top'];
+  //   ^?
+  expectTypeOf<TopInput>().toEqualTypeOf<Types['input']>();
+  expectTypeOf<TopOutput>().toEqualTypeOf<Types['output']>();

This strengthens the guarantee that the public API reflects the Standard Schema types for both object-wrapped and direct scalar forms.

packages/server/src/unstable-core-do-not-import/parser.ts (1)

65-81: Prefer Standard Schema in inferParser: correct precedence

Good change. This ensures dual-interface parsers (e.g., Zod v4) infer via Standard Schema first, avoiding stale or incorrect internals.

Consider adding a brief doc comment to lock in the intent and reduce future regressions:

 export type inferParser<TParser extends Parser> =
+  /**
+   * Precedence:
+   * 1) Standard Schema v1 (canonical input/output)
+   * 2) Parsers exposing explicit input/output internals
+   * 3) Parsers with single in/out type (custom fn, myzod, scale, superstruct, yup)
+   */
   TParser extends ParserStandardSchemaEsque<infer $TIn, infer $TOut>
     ? { in: $TIn; out: $TOut }
     : TParser extends ParserWithInputOutput<infer $TIn, infer $TOut>
       ? { in: $TIn; out: $TOut }
       : TParser extends ParserWithoutInput<infer $InOut>
         ? { in: $InOut; out: $InOut }
         : never;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c93a1f and bbad5c7.

📒 Files selected for processing (3)
  • packages/server/src/unstable-core-do-not-import/parser.ts (1 hunks)
  • packages/tests/server/validators.test.ts (1 hunks)
  • www/docs/server/validators.md (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/tests/server/validators.test.ts (1)
packages/server/src/vendor/standard-schema-v1/spec.ts (1)
  • Types (62-67)
🪛 LanguageTool
www/docs/server/validators.md

[style] ~498-~498: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ... a PR to add a new supported interface. Feel free to open an issue for discussion. You can c...

(FEEL_FREE_TO_STYLE_ME)

⏰ 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). (3)
  • GitHub Check: Lint and auto-fix
  • GitHub Check: test
  • GitHub Check: Analyze (typescript)


export type inferParser<TParser extends Parser> =
TParser extends ParserWithInputOutput<infer $TIn, infer $TOut>
TParser extends ParserStandardSchemaEsque<infer $TIn, infer $TOut>
Copy link
Contributor

@Nick-Lucas Nick-Lucas Aug 11, 2025

Choose a reason for hiding this comment

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

Thanks for the contribution! This is a great idea and the docs changes look good

In this case couldn't we change the precedence of ParserWithInputOutput to keep the conditional type simple?

export type ParserWithInputOutput<TInput, TParsedInput> =
  | ParserStandardSchemaEsque<TInput, TParsedInput>
  | ParserZodEsque<TInput, TParsedInput>
  | ParserValibotEsque<TInput, TParsedInput>
  | ParserArkTypeEsque<TInput, TParsedInput>;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that, but it didn't work. My understanding, as a relative newcomer to TypeScript, is that there isn't actually precedence there; rather, the final type is the union of the outcomes for each individual branch.

For example, if you try to compile the following code, you should see an error that indicates that Get<{ a: number; b: string }> evaluates to string | number.

interface A<T> {
  a: T;
}
type B<T> = {
  b: T;
};

type U<T> = A<T> | B<T>;
type Get<X> = X extends U<infer $T> ? $T : never;

const x: Get<{ a: number; b: string }> = {};

I think the same thing applies in the Zod case, but it's hidden because the branded type is a subtype of the base type, so the union just equals the base type. (If you brand string with "brand", the branded type is string & z.BRAND<"brand">.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I believe you're correct, thanks for clarifying!

Nick-Lucas
Nick-Lucas previously approved these changes Aug 12, 2025
KATT
KATT previously approved these changes Aug 18, 2025
Copy link
Member

@KATT KATT left a comment

Choose a reason for hiding this comment

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

Updated it a bit, got rid of the ParserStandardSchemaEsque-type and just placed StandardSchemaV1 instead ofi t first

Edit: nvm, undid that.

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bbad5c7 and 155347f.

📒 Files selected for processing (1)
  • packages/server/src/unstable-core-do-not-import/parser.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/server/src/unstable-core-do-not-import/parser.ts (1)
packages/server/src/vendor/standard-schema-v1/spec.ts (1)
  • StandardSchemaV1 (10-13)
🔇 Additional comments (1)
packages/server/src/unstable-core-do-not-import/parser.ts (1)

51-55: No external usage of ParserWithInputOutput detected—removal of Standard Schema is safe

I’ve searched across the repo and found that ParserWithInputOutput is only defined and used in packages/server/src/unstable-core-do-not-import/parser.ts (it isn’t imported or referenced elsewhere). Removing StandardSchemaV1 from that union won’t impact downstream code or public surfaces.

This reverts commit 155347f.
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

♻️ Duplicate comments (1)
packages/server/src/unstable-core-do-not-import/parser.ts (1)

67-70: Including StandardSchemaV1 in Parser unblocks the first branch of inferParser

By adding StandardSchemaV1 to the Parser union, the Standard Schema branch in inferParser becomes reachable and preferred. This also addresses the prior “unreachable branch” concern raised earlier.

🧹 Nitpick comments (3)
packages/server/src/unstable-core-do-not-import/parser.ts (3)

72-88: Optional: remove the generic constraint to decouple inference from the Parser union

inferParser now works because Parser includes StandardSchemaV1. To make this robust against future changes to Parser (and to allow inference on any Standard Schema-implementing value even if it isn’t typed as Parser), consider dropping the extends Parser constraint. This keeps the precedence logic self-contained and avoids reintroducing the “unreachable branch” class of issues if Parser is refactored later.

Proposed minimal change:

-export type inferParser<TParser extends Parser> =
+export type inferParser<TParser> =
   TParser extends StandardSchemaV1<infer $TIn, infer $TOut>
     ? {
         in: $TIn;
         out: $TOut;
       }
     : TParser extends ParserWithInputOutput<infer $TIn, infer $TOut>
       ? {
           in: $TIn;
           out: $TOut;
         }
       : TParser extends ParserWithoutInput<infer $InOut>
         ? {
             in: $InOut;
             out: $InOut;
           }
         : never;

49-51: Nit: capitalize and hyphenate “Standard Schema” in the deprecation note

For consistency with the docs and naming elsewhere, prefer “non-Standard Schema”.

 /**
- * @deprecated we can drop support for non standard schema parsers in the next major version
+ * @deprecated We can drop support for non-Standard Schema parsers in the next major version
  */

59-61: Nit: same capitalization/hyphenation fix here

 /**
- * @deprecated we can drop support for non standard schema parsers in the next major version
+ * @deprecated We can drop support for non-Standard Schema parsers in the next major version
  */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 155347f and 4b0db76.

📒 Files selected for processing (1)
  • packages/server/src/unstable-core-do-not-import/parser.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/server/src/unstable-core-do-not-import/parser.ts (1)
packages/server/src/vendor/standard-schema-v1/spec.ts (1)
  • StandardSchemaV1 (10-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). (4)
  • GitHub Check: build
  • GitHub Check: test
  • GitHub Check: Lint and auto-fix
  • GitHub Check: Analyze (typescript)
🔇 Additional comments (1)
packages/server/src/unstable-core-do-not-import/parser.ts (1)

62-66: Removing StandardSchema from ParserWithInputOutput is correct to enforce precedence

Good call excluding ParserStandardSchemaEsque from ParserWithInputOutput. This ensures Standard Schema inference happens via the dedicated first branch in inferParser, not conflated with other “with input/output” parsers.

@KATT KATT disabled auto-merge August 18, 2025 23:02
@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 18, 2025

Open in StackBlitz

@trpc/client

npm i https://pkg.pr.new/trpc/trpc/@trpc/client@6888

@trpc/next

npm i https://pkg.pr.new/trpc/trpc/@trpc/next@6888

@trpc/react-query

npm i https://pkg.pr.new/trpc/trpc/@trpc/react-query@6888

@trpc/server

npm i https://pkg.pr.new/trpc/trpc/@trpc/server@6888

@trpc/tanstack-react-query

npm i https://pkg.pr.new/trpc/trpc/@trpc/tanstack-react-query@6888

@trpc/upgrade

npm i https://pkg.pr.new/trpc/trpc/@trpc/upgrade@6888

commit: 0ccdf74

@KATT KATT merged commit b90d509 into trpc:main Aug 18, 2025
42 of 46 checks passed
@github-actions
Copy link
Contributor

This pull request has been locked because we are very unlikely to see comments on closed issues. If you think, this PR is still necessary, create a new one with the same branch. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 20, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: migrate to Zod v4 ways of retrieving input/output types

3 participants