feat(contract): Response Validation Plugin#953
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a ResponseValidationPlugin and its tests, exposes plugins from the contract package, moves validateORPCError into the contract package and re-exports it from server, removes the server-local validator, and updates site navigation and docs for response validation and OpenAPI type expansion. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant L as StandardLink
participant P as ResponseValidationPlugin
participant N as Next Link/Transport
participant S as Server
C->>L: request(path, input)
note right of L: plugins/interceptors run in order
L->>P: interceptor(request)
P->>P: resolve contract procedure by path
P->>N: next(request)
N->>S: send(input)
S-->>N: output or ORPCError
alt output present
P->>P: validate outputSchema (if present)
alt valid
P-->>L: return validated value
else invalid
P-->>L: throw ValidationError (includes issues + raw data)
end
else ORPCError returned
P->>P: validateORPCError(errorMap, error)
P-->>L: throw mapped ORPCError
end
L-->>C: result or error
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Summary of Changes
Hello @unnoq, 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 significantly enhances the client-side capabilities for ensuring data integrity by introducing a robust response validation mechanism. It allows developers to enforce contract schemas on incoming server responses, leading to more reliable and type-safe client applications. A key benefit is the improved handling of complex data types with OpenAPI links, streamlining data transformation and reducing boilerplate.
Highlights
- New Response Validation Plugin: Introduced a new client-side plugin,
ResponseValidationPlugin, which validates server responses against the defined contract schema. This ensures data integrity and type safety on the client side. - Enhanced OpenAPI Type Support: Added functionality and documentation to allow
OpenAPILinkto support additional data types (likeDateandBigInt) beyond JSON's native capabilities by leveraging schema coercion and the new Response Validation Plugin, potentially removing the need for aJsonifiedClientwrapper in certain scenarios. - Error Validation Logic Refactoring: The
validateORPCErrorutility function has been refactored and moved from the@orpc/serverpackage to the@orpc/contractpackage. This centralizes error validation logic within the contract definition layer, making it accessible for client-side plugins like the new Response Validation Plugin. - Comprehensive Documentation: New documentation pages have been added for the Response Validation Plugin and for expanding type support in OpenAPI Link, providing clear guidance on setup, usage, and limitations.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| 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 issue 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
-
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. ↩
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable ResponseValidationPlugin that enables client-side validation of server responses against the contract. This is a great addition for ensuring type safety, especially when using OpenAPILink with data types not native to JSON. The accompanying documentation is clear and the refactoring of validateORPCError into the @orpc/contract package is a logical improvement. I have one suggestion to enhance the readability of the new error validation logic.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
More templates
@orpc/arktype
@orpc/client
@orpc/contract
@orpc/experimental-durable-event-iterator
@orpc/hey-api
@orpc/interop
@orpc/json-schema
@orpc/nest
@orpc/openapi
@orpc/openapi-client
@orpc/otel
@orpc/react
@orpc/react-query
@orpc/experimental-react-swr
@orpc/server
@orpc/shared
@orpc/solid-query
@orpc/standard-server
@orpc/standard-server-aws-lambda
@orpc/standard-server-fetch
@orpc/standard-server-node
@orpc/standard-server-peer
@orpc/svelte-query
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/vue-colada
@orpc/vue-query
@orpc/zod
commit: |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/content/docs/openapi/client/openapi-link.md (1)
90-90: Fix typo and MIME example.Replace “whe file” with “when the file” and correct the MIME type.
-`OpenAPILink` requires access to the `Content-Disposition` to distinguish file responses from other responses whe file has a common MIME type like `application/json`, `plain/text`, etc. To enable this, include `Content-Disposition` in your CORS policy's `Access-Control-Expose-Headers`: +`OpenAPILink` requires access to the `Content-Disposition` to distinguish file responses from other responses when the file has a common MIME type like `application/json`, `text/plain`, etc. To enable this, include `Content-Disposition` in your CORS policy's `Access-Control-Expose-Headers`:packages/server/src/error.ts (1)
24-47: Avoid accidental “thenable” behavior and tighten defined-check; optional caching for stability.
- Proxy currently returns a callable for any string key, including "then", which can make the proxy appear thenable and break awaits/Promise.resolve. Guard it.
- Use hasOwn to avoid prototype hits (e.g., toString) falsely marking an error as defined.
- Optionally cache per-code constructors to keep referential stability and reduce allocations.
const proxy = new Proxy(errors, { get(target, code) { if (typeof code !== 'string') { return Reflect.get(target, code) } - const item: ORPCErrorConstructorMapItem<string, unknown> = (...rest) => { + // prevent thenable assimilation (e.g., Promise.resolve(proxy)) + if (code === 'then') return undefined as any + + // optional: cache to keep stable references and reduce allocations + const __cache = (__cacheMap as Map<string, any>) ?? new Map<string, any>() + ;(__cacheMap as any) = __cache + const cached = __cache.get(code) + if (cached) return cached + + const has = Object.hasOwn + ? Object.hasOwn(errors as object, code) + : Object.prototype.hasOwnProperty.call(errors, code) + + const item: ORPCErrorConstructorMapItem<string, unknown> = (...rest) => { const options = resolveMaybeOptionalOptions(rest) - const config = errors[code] + const config = has ? (errors as any)[code] : undefined - return new ORPCError(code, { - defined: Boolean(config), + return new ORPCError(code as any, { + defined: has, status: config?.status, message: options.message ?? config?.message, data: options.data, cause: options.cause, }) } - return item + __cache.set(code, item) + return item }, }) return proxy as any } + +// module-local cache holder (per map instance via closure); keep it typed but outside Proxy callback +let __cacheMap: Map<string, any> | undefined
🧹 Nitpick comments (16)
packages/server/src/procedure-client.test.ts (2)
10-13: Mock should return a Promise by default to match the real signature.Prevents accidental sync behavior if a test path uses the default mock.
-vi.mock('@orpc/contract', async origin => ({ - ...await origin(), - validateORPCError: vi.fn((map, error) => error), -})) +vi.mock('@orpc/contract', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + validateORPCError: vi.fn(async (_map, error) => error), + } +})
67-69: Avoid async describe callback.describe blocks shouldn’t be async; it’s unnecessary here and can confuse test runners.
-describe.each(procedureCases)('createProcedureClient - case %s', async (_, procedure) => { +describe.each(procedureCases)('createProcedureClient - case %s', (_, procedure) => {apps/content/docs/plugins/response-validation.md (1)
38-49: Clarify “transforms” vs “coercion” to avoid confusion with the OpenAPI guideState explicitly that coercion (e.g., z.coerce.*) is supported, while type-changing transforms are not.
## Limitations -Schemas that transform data into different types than the expected schema types are not supported. +Schemas that transform data into different types than the expected schema types are not supported. + +Note: Coercion that preserves the target type in your schema (for example, `z.coerce.date()`, `z.coerce.bigint()`) is supported and recommended.packages/contract/src/error.test.ts (2)
73-78: Fix test title: it describes the precondition incorrectlyInput uses
defined=false; the test asserts the validator returnsdefined=true.-it('return new error when defined=true and data schema is undefined with match error', async () => { +it('returns a new error with defined=true when defined=false and data schema is undefined (match)', async () => {
80-85: Optional: also cover defined=true + valid data pathAdd a case where
defined=trueand data validates to ensure we still return a new error with validated data.it('returns new error with defined=true and validated data when defined=true (match)', async () => { const e = new ORPCError('BAD_GATEWAY', { defined: true, data: { value: '123' } }) const v = await validateORPCError(errors, e) expect(v).not.toBe(e) expect({ ...v }).toEqual({ ...e, defined: true, data: { value: 123 } }) })apps/content/docs/openapi/advanced/expanding-type-support-for-openapi-link.md (2)
20-30: Provide imports for oc/implement in the snippet (or note prior context)Without prior context,
ocandimplementare undefined in this standalone example.-```ts +```ts +import { oc, implement } from '@orpc/contract' const contract = oc.output(z.object({ date: z.coerce.date(), // [!code highlight] bigint: z.coerce.bigint(), // [!code highlight] })) const procedure = implement(contract).handler(() => ({ date: new Date(), bigint: 123n, }))
50-52: Grammar: duplicate “first”Remove the extra “first”.
- To support more types than those in [OpenAPI Handler](/docs/openapi/openapi-handler#supported-data-types), you must first extend the [OpenAPI JSON Serializer](/docs/openapi/advanced/openapi-json-serializer) first. + To support more types than those in [OpenAPI Handler](/docs/openapi/openapi-handler#supported-data-types), you must extend the [OpenAPI JSON Serializer](/docs/openapi/advanced/openapi-json-serializer) first.packages/server/src/error.test.ts (1)
2-2: Decouple test from contract test fixturesImport a local schema instead of reaching into another package’s test utilities.
-import { outputSchema } from '../../contract/tests/shared' +import z from 'zod'describe('createORPCErrorConstructorMap', () => { - const errors = { + const outputSchema = z.object({ output: z.number() }) + const errors = {packages/contract/src/error.ts (4)
58-59: Tighten the function typing (input and return codes).Use ORPCErrorCode to avoid
anyerosion and reflect actual code domain.-export async function validateORPCError(map: ErrorMap, error: ORPCError<any, any>): Promise<ORPCError<string, unknown>> { +export async function validateORPCError( + map: ErrorMap, + error: ORPCError<ORPCErrorCode, unknown>, +): Promise<ORPCError<ORPCErrorCode, unknown>> {
62-66: Compute expected status once for clarity and future reuse.Minor readability win; also makes it obvious what’s being compared.
- if (!config || fallbackORPCErrorStatus(error.code, config.status) !== error.status) { + const expectedStatus = config ? fallbackORPCErrorStatus(code, config.status) : undefined + if (!config || expectedStatus !== status) {
68-72: Optionally prefer mapped message when upgrading to defined.When we flip
definedtotrue(no data schema), preferconfig.messageif the incoming error lacks one.- if (!config.data) { + if (!config.data) { return defined - ? error - : new ORPCError(code, { defined: true, status, message, data, cause }) + ? error + : new ORPCError(code, { + defined: true, + status, + message: message ?? config.message, + data, + cause, + }) }
74-80: Guard against validators that may throw.Most validators return issues, but a defensive try/catch avoids turning validation failures into unexpected 500s.
- const validated = await config.data['~standard'].validate(error.data) + let validated + try { + validated = await config.data['~standard'].validate(error.data) + } + catch { + return defined + ? new ORPCError(code, { defined: false, status, message, data, cause }) + : error + }packages/contract/src/plugins/response-validation.ts (2)
31-33: Message is clear; consider adding path context to aid debugging.Including the full dot-path is great. Optionally add contract/router id if available.
35-54: Skip validation for streaming outputs (if any).If a procedure can return an AsyncIterator with an output schema, validating the iterator object would be incorrect. Consider bypassing when
Symbol.asyncIterator in output.- const output = await next() + const output = await next() const outputSchema = procedure['~orpc'].outputSchema if (!outputSchema) { return output } + if (output && typeof output === 'object' && Symbol.asyncIterator in (output as any)) { + return output + }packages/contract/src/plugins/response-validation.test.ts (2)
66-66: Remove unnecessaryasyncondescribe.
describeshould not be async; tests remain async at theitlevel.- describe('validate output', async () => { + describe('validate output', () => {
52-55: Stubclient.callexplicitly to reduce coupling.Tests rely solely on
codec.decode, but stubbingclient.callto resolve a sentinel avoids accidental breakage ifdecodelater depends on the response value.- const client = { - call: vi.fn(), - } + const client = { + call: vi.fn().mockResolvedValue({ ok: true }), + }
📜 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.
📒 Files selected for processing (16)
apps/content/.vitepress/config.ts(2 hunks)apps/content/docs/openapi/advanced/expanding-type-support-for-openapi-link.md(1 hunks)apps/content/docs/openapi/client/openapi-link.md(1 hunks)apps/content/docs/plugins/client-retry.md(1 hunks)apps/content/docs/plugins/response-validation.md(1 hunks)packages/contract/package.json(1 hunks)packages/contract/src/error.test.ts(2 hunks)packages/contract/src/error.ts(2 hunks)packages/contract/src/plugins/index.ts(1 hunks)packages/contract/src/plugins/response-validation.test.ts(1 hunks)packages/contract/src/plugins/response-validation.ts(1 hunks)packages/server/src/error.test.ts(1 hunks)packages/server/src/error.ts(1 hunks)packages/server/src/index.ts(1 hunks)packages/server/src/procedure-client.test.ts(1 hunks)packages/server/src/procedure-client.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
packages/contract/src/plugins/response-validation.test.ts (2)
packages/client/src/adapters/standard/link.ts (1)
StandardLink(24-109)packages/contract/src/plugins/response-validation.ts (1)
ResponseValidationPlugin(18-64)
packages/contract/src/plugins/response-validation.ts (4)
packages/client/src/adapters/standard/plugin.ts (1)
StandardLinkPlugin(4-7)packages/contract/src/router.ts (1)
AnyContractRouter(17-17)packages/client/src/adapters/standard/link.ts (1)
StandardLinkOptions(18-22)packages/contract/src/procedure.ts (1)
isContractProcedure(51-66)
packages/contract/src/error.ts (3)
packages/server/src/index.ts (3)
validateORPCError(26-26)ErrorMap(31-31)ORPCError(24-24)packages/contract/src/index.ts (1)
ORPCError(20-20)packages/client/src/error.ts (1)
fallbackORPCErrorStatus(88-90)
packages/contract/src/error.test.ts (1)
packages/contract/src/error.ts (2)
ErrorMap(38-40)validateORPCError(58-83)
⏰ 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
- GitHub Check: publish-commit
- GitHub Check: test
🔇 Additional comments (19)
apps/content/docs/plugins/client-retry.md (1)
44-46: Good clarification on link compatibility.This info block helps readers understand the plugin works across all oRPC links.
packages/server/src/index.ts (2)
26-26: Re-exporting validateORPCError from contract — LGTM.Centralizing the API in
@orpc/contractand surfacing it here is consistent and clearer.
24-26: No deep imports from@orpc/server/errordetected; movingvalidateORPCErrorto@orpc/contractis safe.apps/content/docs/openapi/client/openapi-link.md (1)
74-74: Clear guidance on JSON limitations and alternatives — good.The warning succinctly points to the type-preserving path via the advanced guide.
packages/server/src/procedure-client.ts (1)
9-9: Import rewiring to contract — LGTM.Using
validateORPCErrorfrom@orpc/contractand keeping onlycreateORPCErrorConstructorMapfrom./erroraligns with the new API surface.Also applies to: 13-13
packages/server/src/error.ts (1)
4-4: Import change looks good.No issues with importing ORPCError directly from @orpc/client.
packages/server/src/procedure-client.test.ts (2)
2-2: Good switch to contract-level import.Aligns tests with the new public surface where validateORPCError lives.
15-18: Mock shape for ./error is fine.Stubs only what’s needed and preserves other exports.
packages/contract/src/plugins/index.ts (1)
1-1: Export map includes./plugins; consumers can importResponseValidationPluginfrom@orpc/contract/plugins.apps/content/.vitepress/config.ts (2)
140-149: Docs sidebar: Response Validation entry looks goodPath and placement under Plugins are consistent with the new page.
279-282: OpenAPI Advanced nav: new page wired correctlyThe link matches the new doc path and sits logically after “Customizing Error Response.”
apps/content/docs/plugins/response-validation.md (1)
16-32: Confirmed export of ResponseValidationPlugin via @orpc/contract/plugins
Thepackages/contract/src/plugins/index.tsbarrel re-exportsResponseValidationPlugin, andpackage.jsonexposes"./plugins": "./src/plugins/index.ts", so the public import path is valid.packages/server/src/error.test.ts (1)
1-12: LGTM: constructor map behavior and expectations are clearCovers default message, overrides, unknown codes, and symbol access.
packages/contract/src/error.ts (2)
1-5: Good split between type-only and value imports.Reduces runtime footprint and avoids potential circular deps.
82-83: LGTM: returns validated, typed data withdefined: true.This completes the “upgrade” path cleanly.
packages/contract/src/plugins/response-validation.ts (2)
23-29: Interceptor placement achieves intended execution order.Pushing the interceptor ensures it runs closest to the core, so user interceptors observe validated output. Comment already hints this; no action needed.
56-58: Error remapping viavalidateORPCErroris correct.Consistent with contract error maps and keeps error surfaces predictable.
packages/contract/src/plugins/response-validation.test.ts (2)
58-64: Order assertion is effective.Asserting interceptor sees transformed output proves the plugin sits inside user interceptors. Nice.
85-105: Ignore matcher availability concern. Vitest v3.2.4 ships withexpect.toSatisfybuilt-in—no additional setup or custom matcher extension is required.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/contract/src/plugins/response-validation.ts (2)
18-27: Make plugin initialization idempotent to prevent duplicate interceptorsIf
initis called more than once (re-linking, hot reloads), you’ll stack multiple validators.Apply this diff:
export class ResponseValidationPlugin<T extends ClientContext> implements StandardLinkPlugin<T> { constructor( private readonly contract: AnyContractRouter, ) {} order = 1_500_000 // make sure run before DurableEventIteratorLinkPlugin + private initialized = false init(options: StandardLinkOptions<T>): void { - options.interceptors ??= [] + if (this.initialized) return + this.initialized = true + options.interceptors ??= []
56-61: Avoidthrow await ...for clarityMap first, then throw. Easier to debug and consistent with common style.
Apply this diff:
- if (e instanceof ORPCError) { - throw await validateORPCError(procedure['~orpc'].errorMap, e) - } + if (e instanceof ORPCError) { + const mapped = await validateORPCError(procedure['~orpc'].errorMap, e) + throw mapped + }
📜 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.
📒 Files selected for processing (1)
packages/contract/src/plugins/response-validation.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/contract/src/plugins/response-validation.ts (4)
packages/client/src/adapters/standard/plugin.ts (1)
StandardLinkPlugin(4-7)packages/contract/src/router.ts (1)
AnyContractRouter(17-17)packages/client/src/adapters/standard/link.ts (1)
StandardLinkOptions(18-22)packages/contract/src/procedure.ts (1)
isContractProcedure(51-66)
⏰ 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: publish-commit
- GitHub Check: test
- GitHub Check: lint
🔇 Additional comments (3)
packages/contract/src/plugins/response-validation.ts (3)
9-17: Solid plugin: clear scope and accurate docsJSDoc and behavior align; mapping
ORPCErrorvia contract errorMap is a good touch.
23-23: Plugin ordering validated: ResponseValidationLinkPlugin (order = 1 500 000) correctly precedes DurableEventIteratorLinkPlugin (order = 2 100 000) when plugins are sorted ascending byorder.
25-33: Usinginterceptorshere is correct. The first‐stage interceptors wrap the entire request pipeline—including thedecode_responsestep—so they always receive the final, decoded server output (both unary and streaming). No change needed.
|
I'm curious to know as to why this only works for coerced schemas? |
@sebastienbarre This isn't part of oRPC. If you're using Zod, please ask in the Zod community - oRPC doesn’t handle this. |
|
@unnoq Thanks for the clarification. I'm using Zod yes. Big fan of oRPC so far, but it is unfortunate that dates are a bit difficult to handle when working in OpenAPI mode (using the official documentation). Considering this import { oc } from '@orpc/contract';
import * as z from 'zod';
export const contract = {
query: oc.input(z.date()).output(z.date()),
};Then the input and output handler signature vary in all 3 scenarios in my
const linkRpc = new RPCLink({ url });
const clientRpc: ContractRouterClient<typeof contract> = createORPCClient(linkRpc);
console.log(typeof (await clientRpc.query(new Date())));
This works: my handler implementation does receive a Date as input, I can send back a Date as output, and client side I'm getting a Date.
const linkOpenApi = new OpenAPILink(contract, { url });
const clientOpenApi: JsonifiedClient<ContractRouterClient<typeof contract>> =
createORPCClient(linkOpenApi);
console.log(typeof (await clientOpenApi.query(new Date())));
This is problematic because it requires manual conversion of
const linkOpenApiRvp = new OpenAPILink(contract, {
url,
plugins: [new ResponseValidationPlugin(contract)],
});
const clientOpenApiRvp: ContractRouterClient<typeof contract> = createORPCClient(linkOpenApiRvp);
console.log(typeof (await clientOpenApiRvp.query(new Date())));
While the signature looks good when using Finally, this leads me to using For reference, import { createServer } from 'node:http';
import { experimental_SmartCoercionPlugin as SmartCoercionPlugin } from '@orpc/json-schema';
import { OpenAPIHandler } from '@orpc/openapi/node';
import { implement } from '@orpc/server';
import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4';
import { contract } from './try-contract.ts';
const os = implement(contract);
const appRouter = {
query: os.query.handler(async ({ input }) => input),
};
const schemaConverters = [new ZodToJsonSchemaConverter()];
const handler = new OpenAPIHandler(appRouter, {
plugins: [new SmartCoercionPlugin({ schemaConverters })],
});
const server = createServer(async (req, res) => await handler.handle(req, res));
server.listen(3000, '127.0.0.1'); |
|
@sebastienbarre try this: const betterDateSchema = z.coerce.date<Date>()Again all this is zod knowledge not related to oRPC |
|
@sebastienbarre please strictly follow this guide: https://orpc.unnoq.com/docs/openapi/advanced/expanding-type-support-for-openapi-link to remove Please create github issue/discussion for further discussion. |
|
OK thanks. Alright just in case somebody stumbled upon this, I'm going to sum up what I think is going on when using oRPC in OpenAPI mode, and leave it at that.
|


Closes: https://github.com/unnoq/orpc/issues/947
Summary by CodeRabbit
New Features
Documentation
Chores / Tests