Skip to content

fix(client): preserve subclass prototype chain in cloneORPCError - #1801

Merged
dinwwwh merged 4 commits into
mainfrom
fix/clone-orpc-error-preserve-prototype
Aug 7, 2026
Merged

fix(client): preserve subclass prototype chain in cloneORPCError#1801
dinwwwh merged 4 commits into
mainfrom
fix/clone-orpc-error-preserve-prototype

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Aug 7, 2026

Copy link
Copy Markdown
Member

cloneORPCError rebuilt errors with new ORPCError(...), which stripped custom subclass prototypes, so error instanceof CustomError failed after error reconciliation. The clone is now constructed as a real ORPCError whose prototype is swapped to the original's, so subclass instances stay instanceof their class while the clone remains a native Error.

Supersedes #1799, thanks @Wadiou for the report and the initial approach.

Fixes

  • Cloned errors are instanceof their subclass, and custom properties are copied with their exact descriptors.
  • Unlike the Object.create approach in fix(client): preserve subclass prototype chain in cloneORPCError #1799, the clone keeps native Error semantics: message / stack / cause stay non-enumerable, so { ...error } and key iteration do not expose stack traces, and structuredClone / postMessage still round-trip.
  • Signature widened to <T extends AnyORPCError>(error: T): T, backward compatible.

Testing

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
orpc Ready Ready Preview Aug 7, 2026 9:13am

@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1801

@orpc/arktype

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

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@1801

@orpc/client

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

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@1801

@orpc/contract

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

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@1801

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@1801

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@1801

@orpc/json-schema

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

@orpc/nest

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

@orpc/next

npm i https://pkg.pr.new/@orpc/next@1801

@orpc/openapi

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

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@1801

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@1801

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@1801

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@1801

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@1801

@orpc/server

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

@orpc/shared

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

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@1801

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: 206db63

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 25 untouched benchmarks


Comparing fix/clone-orpc-error-preserve-prototype (206db63) with main (722f8df)

Open in CodSpeed

@pullfrog pullfrog Bot left a comment

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.

ℹ️ Minor suggestions only — the fix is correct and well-tested; one foot-gun worth noting inline.

Reviewed changes

  • Prototype-chain preservation in cloneORPCError — the clone is now built as a real ORPCError and its prototype is swapped to the original's (Object.setPrototypeOf + Object.defineProperties copying own descriptors), so subclass instances stay instanceof their class while keeping native Error non-enumerable message/stack/cause semantics.
  • Signature widened from ORPCError<T, TData> to <T extends AnyORPCError>(error: T): T, with the old explicit defined/inferable copy superseded by the descriptor copy (they are own instance properties, so it's covered).
  • Two new tests: subclass prototype-chain preservation (would fail under the previous implementation) and native-error shape semantics for the base ORPCError. Both tests are meaningful — the subclass test asserts instanceof, which the old clone could not satisfy, and the shape test would catch any of message/stack/cause leaking into a spread.

I verified the implementation in V8: public subclass fields are carried over via the descriptor copy, and cloning never throws on private fields (they aren't returned by Object.getOwnPropertyDescriptors). The change is backward compatible and the instanceof behavior composes correctly with ORPCError's cross-graph Symbol.hasInstance workaround.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread packages/client/src/error-utils.ts
…tests

Co-authored-by: Wadoud <wadiouyt@gmail.com>

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found. The prior foot-gun note is now addressed — the subclass private-field limitation is pinned by an explicit test rather than a JSDoc comment.

Reviewed changes

Since the prior review (54b11bd), the cloneORPCError logic is unchanged; the two new commits trim comments and tighten coverage:

  • Added does not carry over subclass private fields — asserts that reading a #private-backed accessor on the clone throws TypeError, making the constructor-skip limitation executable and regression-proof (the test fails if the clone were built as a plain ORPCError).
  • Removed the cloneORPCError JSDoc and the shape-test comment that are now redundant with the pinned tests.

I re-ran packages/client/src/error-utils.test.ts (25 tests) — all pass. cloneORPCError remains a single shared implementation imported by contract, server, and json-schema, so the fix covers all reconciliation paths.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

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.

ℹ️ This round swaps the private-field regression test for a JSDoc note — a reasonable doc-for-coverage trade; one observation inline.

Reviewed changes

Since the prior review (4e35e9c), the cloneORPCError implementation is unchanged. The only delta is on documentation/coverage:

  • Removed the does not carry over subclass private fields test added in 4e35e9c.
  • Added a JSDoc block on cloneORPCError documenting that subclass constructors aren't re-run, so #private fields are absent and subclass members reading them throw on the clone.

The JSDoc matches the verified runtime behavior (Object.defineProperties from getOwnPropertyDescriptors never surfaces private fields, so the clone carries no private slots while inheriting the subclass getter, which then throws on read). This reverses the earlier approved state (limitation pinned by a test) back to documentation-only, per the author's rationale that a limitation — not a contract — shouldn't be pinned by a test.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +61 to +63
* Limitation: subclass constructors are not re-run, so private fields
* (`#field`) are not carried over and subclass members that read them
* will throw on the clone.

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.

Recording a deliberate trade in this round: the real regression signal for this limitation is now gone. If a future change ever re-runs subclass constructors (or the clone strategy changes), this JSDoc would drift silently stale instead of a test failing — the earlier does not carry over subclass private fields test from 4e35e9c was the only executable guard for it. The rationale (a known limitation, not a contract to pin) is reasonable, so this is fine as-is — just confirming we're consciously accepting documentation-only coverage here.

@dinwwwh
dinwwwh merged commit 37d8836 into main Aug 7, 2026
12 checks passed
@dinwwwh
dinwwwh deleted the fix/clone-orpc-error-preserve-prototype branch August 8, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant