Repository navigation
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 3db1047 | Commit Preview URL Branch Preview URL |
Oct 31 2026, 04:11 AM |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/mcp
@orpc/msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the OpenAPI error-response codec hooks lose their null/undefined fallback, plus the corresponding type, test, and docs updates:
- Handler encoder always authoritative —
errorResponseBodyEncoder's return value is always used as the body (undefined→ empty body,null→ JSONnull);errorResponseContentTypeis now set only when the serialized body is notundefined. The default path still serializeserror.toJSON(). - Link decoder replaces decoding —
errorResponseBodyDecoderis now required to return anError(type narrowed fromAnyORPCError | null | undefined) and its result is thrown as-is; the default ORPC-json decoding runs only when no decoder is configured. - Widened public type —
StandardLinkCodecDecodedResponse's error arm widened fromAnyORPCErrortoError; the sole consumer just throws it, so the change is safe. - Docs & JSDoc — handler/link custom-error sections rewritten, migration guide updated, and
@seebacklinks added toisORPCErrorJson/createORPCErrorFromJson.
I traced the widened Error type through StandardLink (throws it) and confirmed the header omission matches the wire behavior — toFetchBody emits no content-type for an undefined body. The untouched generator errorResponseBodySchema fallback and its specification.mdx example are still accurate. Ran the three touched test files locally: 114 passed.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the incremental delta since the prior pullfrog review (commit 8e082c0), which extends the breaking change to the generator's errorResponseBodySchema:
- Generator schema now always decides —
errorResponseBodySchemamust return aJsonSchema(type narrowed fromJsonSchema | undefined | null) and receives a lazydefaultSchema()as a third argument. Returningfalse(a valid boolean schema, matches no value) omitscontentfrom the response object, documenting an empty body. - Lazy error-component registration — per-code error components are now registered only when
defaultSchema()is actually invoked, so statuses served by a custom schema no longer leave unused components behind.UndefinedErrorstays eagerly registered whenever errors exist (pre-existing). - Docs & migration —
specification.mdx's example takesdefaultSchemaand returnsdefaultSchema(); the migration guide's "Error response options changed" section now covers all three options (schema/encoder/decoder); tests coverdefaultSchema(), thefalseempty-body case, and the registration optimization.
I traced the closure through buildErrorResponse, confirmed JsonSchema includes boolean (ensureJsonSchemaObject(schema: JsonSchema): Exclude<JsonSchema, boolean>), and verified the refactor preserves the previous eager hoistDefs behavior and ??-short-circuit semantics (registration is skipped exactly when the default is not used). Ran the four touched openapi test files locally: 162 passed; pnpm --filter @orpc/openapi type:check clean.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the incremental delta since the prior pullfrog review (commit 8e082c0), a behavior-preserving cleanup on top of the breaking change:
- Handler codec default resolved in constructor —
errorResponseBodyEncodernow defaults toe => e.toJSON()in the constructor (field narrowed toExclude<..., undefined>), soencodeErrorcalls it unconditionally instead of the previous?.(...) ?? error.toJSON()ternary. Behavior is unchanged. - Generator reuses hoisted defs — the lazy
defaultSchema()now maps the already-hoisteddefinedErrorsrather than re-runningctx.registry.hoistDefson each data schema.hoistDefsreuses equivalent components, so the emitted document is identical; this drops a redundant second hoist that previously ran on the default path. - Docs & tests — the link decoder JSDoc now says "Throw a
MalformedResponseError", matching the updated example whichthrows it; prose tightened across the four docs files; the duplicate link-codec decoder tests were merged into one.
I traced both refactors as behavior-preserving (hoistDefs idempotence confirmed via openapi-generator-components.ts:51 and its tests), ran the four touched openapi test files (202 passed), and pnpm --filter @orpc/openapi type:check is clean.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the incremental delta since the prior pullfrog review (commit ce62fd6), which switches the generator's empty-error-body sentinel from false to null | undefined:
- Sentinel switched to
null | undefined—errorResponseBodySchema's return type is widened back fromJsonSchematoJsonSchema | null | undefined;buildErrorResponsenow omitscontentwhen the callback returnsnullorundefined(previouslyfalse), so a booleanfalseis once again a real schema that emitscontent. - Docs, JSDoc & tests updated —
specification.mdx, the migration guide, and the generator JSDoc all describenull/undefinedas the empty-body signal; the generator test covers both viait.each([null, undefined]), and the override test's default branch now returnsdefaultSchema().
I traced the new branch in buildErrorResponse (openapi-generator-operation.ts:550-558), confirmed the widened type still flows correctly into toOpenAPISchema, ran the two touched generator test files (57 passed), plus pnpm --filter @orpc/openapi type:check and eslint on the changed files — all clean.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one minor docs fix inline.
Reviewed changes — the incremental delta since the prior pullfrog review (commit 663ec27), which adds a lazy defaultError() third argument to the OpenAPI link's errorResponseBodyDecoder:
defaultError()third argument —errorResponseBodyDecodernow receives a lazydefaultError: () => Errorthat returns the ORPC error (with itsResponseErrorcause) for ORPC bodies, or aMalformedResponseErrorotherwise. The option defaults to(_body, _response, defaultError) => defaultError(), and its field is narrowed toExclude<..., undefined>, so the oldif (...)guard is gone.- Default decoding returns instead of throws —
decodeResponsebuildsdefaultErrorand returns{ kind: 'error', error: this.errorResponseBodyDecoder(deserialized, response, defaultError) }. Unknown payloads therefore surface as a returnedMalformedResponseErrorrather than a throw;StandardLinkstill rethrows it to the caller, so only thedecode_responsetracing span stops marking it as an error (the outer call span still records it, as the PR notes). - JSDoc & docs — the decoder JSDoc trades the
isORPCErrorJson/createORPCErrorFromJson/throw guidance for a@remarksbullet plus@default; the@seebacklinks are removed fromisORPCErrorJson/createORPCErrorFromJson(consistent, since those symbols are no longer imported in any docs code fence); the migration guide andlink.mdxexample now calldefaultError(). - Tests — a new case asserts
defaultError()reproduces the default ORPC decoding and returnsMalformedResponseErrorfor unknown bodies; the unknown-payload test now reads the returnedresult.error.
I traced defaultError through decodeResponse and confirmed the throw-to-return change is not observable to callers (StandardLink.call throws the returned error at packages/client/src/adapters/standard/link.ts:121-123). Ran the two codec test files (106 passed) and the JSDoc/docs backlink checker (0 errors).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Returning `null` or `undefined` from `errorResponseBodyEncoder`, `errorResponseBodyDecoder` or `errorResponseBodySchema` no longer falls back to the default behavior. Each option always decides, and the default stays reachable from its arguments. - Handler: the encoder's return value is always the body (`undefined` sends an empty one, without the configured `errorResponseContentType`); it defaults to `error.toJSON()`. - Link: the decoder must return an `Error` and receives a lazy `defaultError()` that returns the decoded ORPC error or a `MalformedResponseError`. `StandardLinkCodecDecodedResponse` now accepts any `Error`. - Generator: `errorResponseBodySchema` receives a lazy `defaultSchema()` (per-code error components are only registered when it is called), and `null`/`undefined` documents an empty body. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SJxePN5EdCgmjsAN5
7ad49f5 to
06ef511
Compare
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues require changes.
No open issues 🎉
What changed in this PR
This PR makes OpenAPI error-response customization explicit across oRPC’s handler, client link, and specification generator. Custom callbacks no longer trigger default behavior by returning null or undefined.
Changes:
- Uses handler encoder results directly, omitting the configured content type for empty bodies.
- Adds explicit default helpers for decoding and schema generation, supports arbitrary decoded errors, and lazily registers per-code error schemas.
- Updates tests, documentation examples, and migration guidance for the breaking behavior change.
| File | Description |
|---|---|
packages/openapi/src/openapi-generator.ts |
Updates schema callback types and documentation. |
packages/openapi/src/openapi-generator-operation.ts |
Adds lazy default schemas and content-free error responses. |
packages/openapi/src/openapi-generator-operation.test.ts |
Tests explicit defaults, lazy registration, and empty bodies. |
packages/openapi/src/adapters/standard/openapi-link-codec.ts |
Adds defaultError() and returns decoded errors directly. |
packages/openapi/src/adapters/standard/openapi-link-codec.test.ts |
Updates custom and malformed-response decoding tests. |
packages/openapi/src/adapters/standard/openapi-handler-codec.ts |
Removes implicit body fallback and handles empty-body headers. |
packages/openapi/src/adapters/standard/openapi-handler-codec.test.ts |
Tests null encoding and empty responses without content type. |
packages/client/src/adapters/standard/codec.ts |
Allows any Error in decoded error responses. |
apps/content/docs/openapi/specification.mdx |
Uses defaultSchema() in the customization example. |
apps/content/docs/openapi/link.mdx |
Uses defaultError() in the decoding example. |
apps/content/docs/openapi/handler.mdx |
Uses error.toJSON() to preserve the default body. |
apps/content/docs/migrations/from-v1.mdx |
Explains the removal of implicit defaults. |
Review effort: Balanced
`@standard-server` adapters drop the `content-type` of an `undefined` body (middleapi/standard-server#178), so the handler codec no longer special-cases it. Also import `ORPCError` in the link decoder docs example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SJxePN5EdCgmjsAN5
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (06ef511 squashed head + ef1eab1 follow-up), a small cleanup on top of the breaking change:
- Handler codec defers empty-body
content-typeto the adapter —encodeErrordrops thebody !== undefinedguard on thecontent-typeheader, so a configurederrorResponseContentTypeis now always set.@standard-serveradapters are expected to drop the header for anundefinedbody as of middleapi/standard-server#178; until that ships, an empty error body carries the configured content-type (called out in the PR body). - Docs fix —
link.mdx's decoder example now importsORPCErrorfrom@orpc/client, resolving the prior review's inline finding. - Test update — the empty-body encoder test drops
errorResponseContentTypefrom its config and is renamed to reflect that no fallback occurs.
I confirmed the deferral is a real (if low-stakes and documented) behavior change rather than a no-op: the currently installed @standard-server/fetch@0.13.0 does not clear content-type for an undefined body, so the header is sent until the ~0.13.0 range resolves to the #178 release. The prior inline finding is addressed, and no other concerns remain. Ran openapi-handler-codec.test.ts + openapi-link-codec.test.ts: 106 passed.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Returning
nullorundefinedfromerrorResponseBodyEncoder(handler),errorResponseBodyDecoder(link) orerrorResponseBodySchema(generator) no longer falls back to the default behavior. Each option now always decides, and the default stays reachable from its arguments:error.toJSON(),defaultError()anddefaultSchema().Changes
OpenAPIHandler—errorResponseBodyEncoderundefinednow sends an empty error response, andnullsends JSONnull.content-typeis left to the@standard-serveradapters, which drop it as of feat(fetch, node)!: drop the content-type of an empty body standard-server#178 (not released yet; until then the configurederrorResponseContentTypeis still sent with an empty body).OpenAPILink—errorResponseBodyDecoderError, which the link throws as-is.() => nullis now a type error.defaultError()as a third argument: the ORPC error decoded from the body (with itsResponseErrorcause), or aMalformedResponseErrorfor other bodies.MalformedResponseErrorfromdecodeResponseinstead of throwing it.StandardLinkstill throws it to the caller, so only thedecode_responsetracing span stops marking it as an error (the outer call span still records it).StandardLinkCodecDecodedResponse(@orpc/client/standard) now accepts anyErrorinstead of onlyORPCError.StandardLinkis the only consumer and just throws it.OpenAPIGenerator—errorResponseBodySchemanull/undefinednow documents an empty body: the response is emitted withoutcontent. This pairs with an encoder that returnsundefined.defaultSchema()as a third argument. The per-code error components are only registered when it is called, so statuses with a custom or empty body no longer leave unused components behind.Docs
error.toJSON(),defaultError()anddefaultSchema()respectively.Testing
pnpm vitest run(root suite)pnpm type:checkpnpm lintpnpm docs:validate🤖 Generated with Claude Code
https://claude.ai/code/session_01VVaw7SJxePN5EdCgmjsAN5