Skip to content

Default input typing for routes to use void not unknown#1136

Closed
treehill05 wants to merge 4 commits into
middleapi:mainfrom
treehill05:feature/default-zod-void-input
Closed

Default input typing for routes to use void not unknown#1136
treehill05 wants to merge 4 commits into
middleapi:mainfrom
treehill05:feature/default-zod-void-input

Conversation

@treehill05
Copy link
Copy Markdown
Contributor

@treehill05 treehill05 commented Oct 24, 2025

Implementation of #1135

  • Change default input schema in packages/server/src/builder.ts (line 339)
  • Change default input schema in packages/contract/src/builder.ts (line 190)
  • Update type tests to reflect new default behavior
  • Add comprehensive test coverage for the feature

Testing

✅ All existing tests pass
✅ Added new tests covering runtime and type-level behavior
✅ Verified in playground environments (Next.js)

Summary by CodeRabbit

  • New Features

    • Procedures now default to void input type when input is not explicitly specified, improving ergonomics for input-less operations.
    • Added ping endpoints to the playground demonstrating the new void input functionality.
  • Tests

    • Added comprehensive type-level and runtime tests verifying default void input behavior across contract and server builders.

Henry Kwon added 3 commits October 24, 2025 16:43
Signed-off-by: Henry Kwon <henry@lyra.so>
Signed-off-by: Henry Kwon <henry@lyra.so>
Signed-off-by: Henry Kwon <henry@lyra.so>
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Oct 24, 2025
@vercel
Copy link
Copy Markdown

vercel Bot commented Oct 24, 2025

@treehill05 is attempting to deploy a commit to the unnoq-team Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 24, 2025

Walkthrough

Builder singletons (oc and os) now have their input schema type narrowed from Schema<unknown, unknown> to Schema<void, void>, establishing void as the default input type. Comprehensive type-level and runtime tests verify void input inference and behavior. Playground routes demonstrate practical void input usage patterns.

Changes

Cohort / File(s) Summary
Builder type signature updates
packages/contract/src/builder.ts, packages/server/src/builder.ts
Updated public generic types for oc and os singletons to use Schema<void, void> for input instead of Schema<unknown, unknown>, narrowing default input type
Contract package void input tests
packages/contract/src/default-void-input.test-d.ts, packages/contract/src/default-void-input.test.ts
Added type-level and runtime tests verifying default void input inference, explicit void input equivalence, override behavior, and compatibility with metadata, routes, and error mappings
Server package void input tests
packages/server/src/default-void-input.test-d.ts, packages/server/src/default-void-input.test.ts
Added type-level and runtime tests validating void input inference, handler signatures, middleware chaining, and router semantics with and without input schemas
Playground ping endpoint infrastructure
playgrounds/next/src/schemas/ping.ts, playgrounds/next/src/routers/ping.ts
Created new ping.ts schema module (PingSchema, PingVoidSchema) and router module (ping and pingVoid endpoints) demonstrating void input and output patterns
Playground router exposure
playgrounds/next/src/routers/index.ts
Extended main router export to include new ping.ping and ping.pingVoid routes
Playground mutation component
playgrounds/next/src/app/orpc-mutation.tsx
Added useCallback hook and testMutate/testVoidMutate mutations to exercise ping endpoint behavior

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes apply a consistent pattern across multiple files (type narrowing and comprehensive test coverage), but require verification of type inference correctness across four test files and playground integration logic.

Possibly related issues

Possibly related PRs

  • feat(server): .$input #218: Modifies builder input schema behavior and void-input handling patterns (.$input API and related test infrastructure)
  • docs: nextjs ssr #488: Updates the same playgrounds/next/src/routers/index.ts file to expose router.ping and router.pingVoid

Suggested labels

size:M

Poem

🐰 Void becomes the default way,
Types now whisper what they say,
No input needed, clean and bright,
Tests confirm each inference right,
Ping-pong routers dance in play! 🏓

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 "Default input typing for routes to use void not unknown" directly and clearly describes the main objective of the changeset. The primary modifications involve updating the default input schema type from unknown to void in both packages/contract/src/builder.ts and packages/server/src/builder.ts, which is precisely what the title communicates. The title is specific, concise, and avoids vague language, making it immediately clear to anyone reviewing the git history what the core change entails.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @treehill05, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant refinement to the default input typing for routes within the framework, changing it from unknown to void when no input schema is explicitly defined. This modification aims to enhance type safety and developer experience by making the absence of input an explicit type, which better reflects the intent of procedures that do not consume any request data. The update spans core builder components in both the contract and server packages, and is thoroughly validated with new type and runtime tests, alongside practical demonstrations in a Next.js playground.

Highlights

  • Default Input Type Change: The default input schema for routes in both the contract and server builders has been updated from unknown to void. This change applies when no explicit input() definition is provided for a procedure.
  • Enhanced Type Safety and Clarity: By defaulting to void, the framework now explicitly indicates that procedures without an input() method expect no input, improving type inference and making the API clearer for developers. This resolves issue Default input typing for routes to use void not unknown #1135.
  • Comprehensive Test Coverage: New type-level and runtime tests have been added to both the packages/contract and packages/server modules. These tests validate the new void default behavior, ensure correct type inference, and confirm backward compatibility for procedures with explicitly defined input schemas.
  • Playground Verification: The changes have been verified in playground environments, including a Next.js example. This demonstrates the practical impact, such as useMutation now correctly handling procedures that implicitly expect void input without requiring undefined to be explicitly passed.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a solid improvement, changing the default input type for routes from unknown to void. This enhances type safety and provides a better developer experience by making the absence of an input explicit. The change is well-supported by a comprehensive suite of new runtime and type-level tests, which is excellent. I've identified a couple of minor areas for improvement in the test code and a playground component to enhance clarity and remove redundancy.

Comment on lines +5 to +33
it('should accept no input when input() is not specified', async () => {
const procedure = os
.output(z.string())
.handler(() => 'result')

// Should work without providing input
const result = await procedure['~orpc'].handler({
context: {},
input: undefined,
rawInput: undefined,
})

expect(result).toBe('result')
})

it('should accept undefined as input when input() is not specified', async () => {
const procedure = os
.output(z.string())
.handler(() => 'result')

// Should work with undefined input
const result = await procedure['~orpc'].handler({
context: {},
input: undefined,
rawInput: undefined,
})

expect(result).toBe('result')
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

These two test cases, 'should accept no input when input() is not specified' and 'should accept undefined as input when input() is not specified', are functionally identical. Both tests use input: undefined in the handler call, effectively testing the same scenario. To improve conciseness and remove redundancy, they can be merged into a single test.

Suggested change
it('should accept no input when input() is not specified', async () => {
const procedure = os
.output(z.string())
.handler(() => 'result')
// Should work without providing input
const result = await procedure['~orpc'].handler({
context: {},
input: undefined,
rawInput: undefined,
})
expect(result).toBe('result')
})
it('should accept undefined as input when input() is not specified', async () => {
const procedure = os
.output(z.string())
.handler(() => 'result')
// Should work with undefined input
const result = await procedure['~orpc'].handler({
context: {},
input: undefined,
rawInput: undefined,
})
expect(result).toBe('result')
})
it('should accept undefined as input when input() is not specified', async () => {
const procedure = os
.output(z.string())
.handler(() => 'result')
// Should work with undefined input
const result = await procedure['~orpc'].handler({
context: {},
input: undefined,
rawInput: undefined,
})
expect(result).toBe('result')
})

Comment on lines +24 to +31
const { mutate: testMutate } = useMutation(orpc.ping.run.mutationOptions())
const { mutate: testVoidMutate } = useMutation(orpc.ping.runVoid.mutationOptions())

useCallback(() => {
testMutate() // this will throw an error if z.void() is not default of input schema
testMutate(undefined) // this will not throw an error
testVoidMutate() // this will not throw an error
}, [testMutate, testVoidMutate])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This useCallback and the associated useMutation hooks appear to be for compile-time type checking and have no effect at runtime. This type of check is valuable but belongs in dedicated type-test files (.test-d.ts) rather than in a component file, where it can be confusing and is effectively dead code. The new .test-d.ts files in this PR already provide excellent coverage for these type-level behaviors.

Removing this block would also allow for the removal of the useCallback import on line 5.

Copy link
Copy Markdown

@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)
playgrounds/next/src/app/orpc-mutation.tsx (1)

5-5: Unused import.

The useCallback import is only used for an unused callback (lines 27-31). Consider removing this import if the callback is not needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd7a90b and 9ef0669.

📒 Files selected for processing (10)
  • packages/contract/src/builder.ts (1 hunks)
  • packages/contract/src/default-void-input.test-d.ts (1 hunks)
  • packages/contract/src/default-void-input.test.ts (1 hunks)
  • packages/server/src/builder.ts (1 hunks)
  • packages/server/src/default-void-input.test-d.ts (1 hunks)
  • packages/server/src/default-void-input.test.ts (1 hunks)
  • playgrounds/next/src/app/orpc-mutation.tsx (2 hunks)
  • playgrounds/next/src/routers/index.ts (2 hunks)
  • playgrounds/next/src/routers/ping.ts (1 hunks)
  • playgrounds/next/src/schemas/ping.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
packages/server/src/default-void-input.test-d.ts (1)
packages/server/src/builder.ts (2)
  • os (336-352)
  • middleware (146-150)
packages/server/src/builder.ts (1)
packages/contract/src/schema.ts (1)
  • Schema (5-5)
playgrounds/next/src/routers/index.ts (1)
playgrounds/next/src/routers/ping.ts (2)
  • ping (4-4)
  • pingVoid (5-5)
packages/contract/src/default-void-input.test-d.ts (1)
packages/contract/src/builder.ts (1)
  • oc (189-198)
packages/contract/src/default-void-input.test.ts (2)
packages/contract/src/builder.ts (1)
  • oc (189-198)
packages/contract/src/procedure.ts (1)
  • isContractProcedure (51-66)
packages/contract/src/builder.ts (1)
packages/contract/src/schema.ts (1)
  • Schema (5-5)
packages/server/src/default-void-input.test.ts (1)
packages/server/src/builder.ts (2)
  • os (336-352)
  • middleware (146-150)
playgrounds/next/src/app/orpc-mutation.tsx (1)
playgrounds/next/src/lib/orpc.ts (1)
  • orpc (32-32)
playgrounds/next/src/routers/ping.ts (1)
playgrounds/next/src/schemas/ping.ts (2)
  • PingSchema (3-3)
  • PingVoidSchema (4-4)
🔇 Additional comments (9)
playgrounds/next/src/schemas/ping.ts (1)

1-4: Clean schema definitions.

The schemas are well-defined with descriptive messages. The explicit use of z.void() in PingVoidSchema provides clarity even though void is now the default input type.

playgrounds/next/src/routers/index.ts (1)

2-2: LGTM!

The new ping router follows the established pattern and cleanly integrates the ping endpoints into the router structure.

Also applies to: 21-25

playgrounds/next/src/routers/ping.ts (1)

1-5: Clean implementation demonstrating void input defaults.

The two endpoints effectively demonstrate the PR's core functionality:

  • ping relies on the new void input default (no explicit input schema)
  • pingVoid explicitly declares void input for comparison

Both handlers are simple and appropriate for playground/demo purposes.

packages/server/src/default-void-input.test-d.ts (1)

1-86: Excellent type-level test coverage for void input behavior.

The test suite comprehensively validates the default void input type at the type level, covering:

  • Inference when input() is omitted
  • Equivalence between implicit and explicit z.void()
  • Override capability with explicit schemas
  • Compatibility with middleware and router patterns
packages/server/src/builder.ts (1)

336-352: LGTM - Type signature correctly updated to default void input.

The change from Schema<unknown, unknown> to Schema<void, void> for the input schema type parameter establishes void as the default, which better represents "no input" semantics. The change is type-only, leaving runtime behavior intact, and is thoroughly validated by the comprehensive test suite.

packages/contract/src/builder.ts (1)

189-198: LGTM - Contract builder type signature updated consistently.

This change mirrors the server builder update, establishing Schema<void, void> as the default input type for the contract builder. The consistency across packages ensures uniform behavior and is validated by comprehensive contract-specific tests.

packages/contract/src/default-void-input.test-d.ts (1)

1-85: Comprehensive type-level validation for contract void input.

The test suite thoroughly validates void input behavior at the contract level, covering all key scenarios including metadata, routing, and error mapping. The use of optional inputSchema? in type extraction is correct for contract procedures.

packages/contract/src/default-void-input.test.ts (1)

1-76: Thorough runtime validation of contract void input behavior.

The test suite validates that contracts function correctly at runtime with void input defaults, covering procedure validity, schema properties, and integration with metadata, routing, and error mapping. Complements the type-level tests effectively.

packages/server/src/default-void-input.test.ts (1)

1-119: Excellent runtime validation of server void input behavior.

The test suite thoroughly validates that procedures execute correctly with void input defaults, covering:

  • Handler invocation without input parameters
  • Compatibility with undefined input values
  • Explicit void vs. implicit void equivalence
  • Middleware chaining preservation
  • Mixed router scenarios (no-input and with-input procedures)

Comment thread playgrounds/next/src/app/orpc-mutation.tsx Outdated
Signed-off-by: Henry Kwon <henry@lyra.so>
@treehill05
Copy link
Copy Markdown
Contributor Author

Reverted playground commit.

@dinwwwh
Copy link
Copy Markdown
Member

dinwwwh commented Oct 25, 2025

Thanks @treehill05 for the effort! I believe this would be a breaking change, so we can't accept a PR like this at the moment. Plus, your approach also doesn't seem fully safe, since it still allows the client to send any input and the server won't validate or strip that input.

@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Oct 25, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1136

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1136

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1136

@orpc/experimental-durable-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-iterator@1136

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1136

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1136

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1136

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1136

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1136

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1136

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1136

@orpc/experimental-publisher

npm i https://pkg.pr.new/@orpc/experimental-publisher@1136

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1136

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1136

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1136

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1136

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1136

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1136

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1136

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1136

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1136

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1136

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1136

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1136

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1136

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1136

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1136

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1136

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1136

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1136

commit: 20bbde0

@treehill05
Copy link
Copy Markdown
Contributor Author

Thanks for your feedback, I agree on your point regarding security. Closing this PR for a safer approach.

@treehill05 treehill05 closed this Oct 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants