Skip to content

feat(client): support ORPCError instanceof checks across different dependency graph#972

Merged
dinwwwh merged 3 commits into
mainfrom
feat/client/deal-with-nextjs-instanceof-issue
Sep 8, 2025
Merged

feat(client): support ORPCError instanceof checks across different dependency graph#972
dinwwwh merged 3 commits into
mainfrom
feat/client/deal-with-nextjs-instanceof-issue

Conversation

@dinwwwh
Copy link
Copy Markdown
Member

@dinwwwh dinwwwh commented Sep 7, 2025

Workaround for Next.js where different contexts use separate
dependency graphs, causing multiple ORPCError constructors existing and breaking
instanceof checks across contexts.

This is particularly problematic with "Optimized SSR", where orpc-client
executes in one context but is invoked from another. When an error is thrown
in the execution context, instanceof ORPCError checks fail in the
invocation context due to separate class constructors.

Closes: https://github.com/unnoq/orpc/issues/957

Summary by CodeRabbit

  • New Features
    • Exposed client package name and version constants and re-exported them for consumers.
    • Added a runtime constructor-detection utility for better type introspection.
  • Bug Fixes
    • Improved ORPCError instanceof behavior across multiple runtimes/bundles for reliable error checks.
  • Chores
    • Added client build configuration to support placeholder replacement during builds.
  • Tests
    • Added tests covering instanceof behavior and constructor detection.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Sep 7, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

Walkthrough

Adds build-time placeholders for the client package, exports those placeholder constants, implements a global registry and a Symbol.hasInstance override on ORPCError for cross-context instanceof checks, and introduces a shared getConstructor helper with tests covering null-proto and function cases.

Changes

Cohort / File(s) Summary
Client build placeholders
packages/client/build.config.ts, packages/client/src/consts.ts, packages/client/src/index.ts
New unbuild config exporting a replace map for package name/version placeholders; new consts.ts exporting placeholder constants; index.ts re-exports the consts.
Client error instanceof support
packages/client/src/error.ts, packages/client/src/error.test.ts
ORPCError registers its constructor in a global registry (Symbol.for key scoped by package name@version) and overrides static [Symbol.hasInstance] to consult the registry for cross-context instanceof checks; tests added/extended to validate prototype/instanceof behavior including subclass and null-proto checks.
Shared object helpers
packages/shared/src/object.ts, packages/shared/src/object.test.ts
New exported getConstructor(value) that returns `Function

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as User Code
  participant Inst as Error Instance
  participant ORPC as ORPCError Class
  participant REG as Global Registry (Symbol.for "orpc:<name>@<version>")
  participant GET as getConstructor (shared)

  User->>ORPC: inst instanceof ORPCError?
  activate ORPC
  ORPC->>REG: ensure/read registry contains constructors
  ORPC->>GET: getConstructor(inst)
  GET-->>ORPC: ctorInst (Function|null|undefined)
  alt ctorInst && ctorInst in REG && ORPC in REG
    ORPC-->>User: true
  else
    ORPC->>ORPC: delegate to super[Symbol.hasInstance](instance)
    ORPC-->>User: boolean
  end
  deactivate ORPC

  note right of REG: Registry persists on globalThis across contexts to enable cross-context instanceof
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective (issue#) Addressed Explanation
Ensure isDefinedError / instanceof works when using shared globalThis.$client in SSR (957)
Use package-scoped registry key to avoid cross-package collisions (957)
Add tests validating instanceof across contexts and null-proto objects (957)

Assessment — Out-of-scope changes

Code Change Explanation
Add unbuild replace config (packages/client/build.config.ts) Build-time placeholder replacement is a packaging/build change not required to fix the cross-context instanceof behavior.
New exported placeholder constants and re-export (packages/client/src/consts.ts, packages/client/src/index.ts) Exporting placeholder constants and re-exporting them is a build/export convenience unrelated to the instanceof/registry objective.

Possibly related PRs

  • unnoq/orpc#340 — Modifies packages/client/src/error.ts; overlaps with Symbol.hasInstance/ORPCError changes.
  • unnoq/orpc#341 — Touches ORPCError construction/usage and may interact with the instanceof behavior changes.
  • unnoq/orpc#576 — Modifies packages/shared/src/object.ts and tests; related to NullProtoObj/getConstructor additions.

Poem

I nibble at symbols, snug in my den,
Register constructors so instances meet again.
Placeholders hop in, versioned and bright,
Cross-context checks pass in morning light.
A rabbit’s small tweak—errors reunite. 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/client/deal-with-nextjs-instanceof-issue

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.

@vercel
Copy link
Copy Markdown

vercel Bot commented Sep 7, 2025

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

Project Deployment Preview Comments Updated (UTC)
orpc Ready Ready Preview Comment Sep 7, 2025 7:48am

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.

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 provides a robust solution for ORPCError instanceof checks failing in environments with multiple JavaScript dependency graphs, such as Next.js's Optimized SSR. By globally registering ORPCError constructors and overriding the Symbol.hasInstance method, it ensures consistent and reliable error type identification across different execution contexts, improving the stability of error handling.

Highlights

  • Cross-context instanceof for ORPCError: Implemented a workaround to enable instanceof ORPCError checks to function correctly across different JavaScript contexts, specifically addressing issues in environments like Next.js where separate dependency graphs can lead to multiple ORPCError constructors.
  • Global Constructor Registry: Utilized Symbol.for to create a global registry for ORPCError constructors, allowing the ORPCError class to identify instances created by different constructors from other contexts.
  • Symbol.hasInstance Override: Overrode the Symbol.hasInstance method on the ORPCError class to leverage the global constructor registry, ensuring that instanceof checks correctly identify ORPCError instances regardless of their originating context.
  • New Utility Function: Introduced a getConstructor utility function in @orpc/shared to reliably retrieve the constructor of an object, which is crucial for the new instanceof workaround.
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

  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 introduces an intelligent workaround to address instanceof ORPCError check failures in environments with multiple dependency graphs, such as Next.js. The approach of using a global symbol to share ORPCError constructors and overriding Symbol.hasInstance is well-implemented and robust. The changes are well-tested, including a new helper function getConstructor. Overall, this is a high-quality contribution. I have a couple of minor suggestions to improve the inline comments for better maintainability of the new logic.

Comment thread packages/client/src/error.ts Outdated
Comment thread packages/client/src/error.ts Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 7, 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 Sep 7, 2025

More templates

@orpc/arktype

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

@orpc/client

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

@orpc/contract

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

@orpc/experimental-durable-event-iterator

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

@orpc/hey-api

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

@orpc/interop

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

@orpc/json-schema

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

@orpc/nest

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

@orpc/openapi

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

@orpc/openapi-client

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

@orpc/otel

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

@orpc/react

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

@orpc/react-query

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

@orpc/experimental-react-swr

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

@orpc/server

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

@orpc/shared

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

@orpc/solid-query

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

@orpc/standard-server

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

@orpc/standard-server-aws-lambda

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

@orpc/standard-server-fetch

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

@orpc/standard-server-node

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

@orpc/standard-server-peer

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

@orpc/svelte-query

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

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/vue-colada

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

@orpc/vue-query

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

@orpc/zod

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

commit: b9dea09

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: 2

🧹 Nitpick comments (5)
packages/client/build.config.ts (1)

4-9: Add CI/local check to verify placeholder replacement
Include a step that installs dependencies, runs the client build (unbuild), and fails if any __ORPC_CLIENT_PACKAGE_NAME_PLACEHOLDER__ or __ORPC_CLIENT_PACKAGE_VERSION_PLACEHOLDER__ remain in packages/client/dist. Example:

pnpm install
pnpm -w --filter @orpc/client run build
rg -nP '__ORPC_CLIENT_PACKAGE_(NAME|VERSION)_PLACEHOLDER__' packages/client/dist && {
  echo "Found unreplaced placeholders"; exit 1; 
} || {
  echo "OK: no placeholders found";
}
packages/client/src/consts.ts (1)

1-2: Nit: mark as const type to help inlining

Not required, but asserting literal types can aid DCE/minification.

-export const ORPC_CLIENT_PACKAGE_NAME = '__ORPC_CLIENT_PACKAGE_NAME_PLACEHOLDER__'
-export const ORPC_CLIENT_PACKAGE_VERSION = '__ORPC_CLIENT_PACKAGE_VERSION_PLACEHOLDER__'
+export const ORPC_CLIENT_PACKAGE_NAME = '__ORPC_CLIENT_PACKAGE_NAME_PLACEHOLDER__' as const
+export const ORPC_CLIENT_PACKAGE_VERSION = '__ORPC_CLIENT_PACKAGE_VERSION_PLACEHOLDER__' as const
packages/shared/src/object.ts (1)

30-40: Type the return as Function to reflect constructors

getConstructor returns prototype constructors (including classes), which align better with Function than AnyFunction (call signature). Optional, but removes type friction down the line.

-import type { AnyFunction } from './function'
...
-export function getConstructor(value: unknown): AnyFunction | null | undefined {
+export function getConstructor(value: unknown): Function | null | undefined {
packages/client/src/error.test.ts (1)

30-63: Add a cross-graph simulation test to prove the workaround

Current tests validate normal instanceof only. Simulate a second dependency graph by registering a fake constructor via the shared symbol and assert ORPCError recognition.

@@
   it('instanceof should behave as normal', () => {
@@
   })
+
+  it('instanceof should recognize errors from another dependency graph', () => {
+    class FakeORPCError extends Error {}
+    // Compute the same global symbol used by the library
+    const { ORPC_CLIENT_PACKAGE_NAME, ORPC_CLIENT_PACKAGE_VERSION } = require('./consts')
+    const sym = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}@${ORPC_CLIENT_PACKAGE_VERSION}/error/ORPCErrorConstructors__`)
+    const store: unknown[] = (globalThis as any)[sym]
+    try {
+      store.push(FakeORPCError)
+      const foreign = new FakeORPCError('x')
+      expect(foreign instanceof ORPCError).toBe(true)
+    } finally {
+      const i = store.indexOf(FakeORPCError)
+      if (i >= 0) store.splice(i, 1)
+    }
+  })
packages/client/src/error.ts (1)

157-167: Tighten instanceof guard and short-circuit for non-objects

Minor: getConstructor returns null for primitives; early-return avoids touching the set. Also switch to WeakSet.has when applying the refactor above.

-  static override[Symbol.hasInstance](instance: unknown): boolean {
-    if (
-      GlobalORPCErrorConstructors.includes(this) // not applicable to extended classes
-      && GlobalORPCErrorConstructors.includes(getConstructor(instance))
-    ) {
+  static override [Symbol.hasInstance](instance: unknown): boolean {
+    const ctor = getConstructor(instance)
+    if (
+      GlobalORPCErrorConstructors.has(this as unknown as Function) // not applicable to extended classes
+      && ctor
+      && GlobalORPCErrorConstructors.has(ctor)
+    ) {
       return true
     }
 
     // fallback to default instanceof check if this is extended from ORPCError
     return super[Symbol.hasInstance](instance)
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a72b189 and 0c8f88e.

📒 Files selected for processing (7)
  • packages/client/build.config.ts (1 hunks)
  • packages/client/src/consts.ts (1 hunks)
  • packages/client/src/error.test.ts (2 hunks)
  • packages/client/src/error.ts (3 hunks)
  • packages/client/src/index.ts (1 hunks)
  • packages/shared/src/object.test.ts (2 hunks)
  • packages/shared/src/object.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
packages/shared/src/object.ts (1)
packages/shared/src/function.ts (1)
  • AnyFunction (1-1)
packages/shared/src/object.test.ts (1)
packages/shared/src/object.ts (2)
  • getConstructor (33-40)
  • NullProtoObj (99-104)
packages/client/src/error.test.ts (2)
packages/client/src/error.ts (1)
  • ORPCError (112-168)
packages/shared/src/object.ts (1)
  • NullProtoObj (99-104)
packages/client/src/error.ts (2)
packages/client/src/consts.ts (2)
  • ORPC_CLIENT_PACKAGE_NAME (1-1)
  • ORPC_CLIENT_PACKAGE_VERSION (2-2)
packages/shared/src/object.ts (1)
  • getConstructor (33-40)
⏰ 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). (2)
  • GitHub Check: publish-commit
  • GitHub Check: lint
🔇 Additional comments (5)
packages/client/src/index.ts (1)

3-3: Publicly re-exporting consts is reasonable

Makes the replacement key computable in tests/consumers. No issues spotted.

packages/shared/src/object.test.ts (1)

36-45: Good coverage of edge-cases for getConstructor

Null/undefined/primitives, Error, null-proto, and function cases covered. Looks solid.

packages/shared/src/object.ts (1)

45-53: isObject heuristic is intentionally narrow

Calling out: Map/Set/Date deliberately excluded. No change requested.

packages/client/src/error.test.ts (1)

27-28: Nice sanity check

Constructor name assertion is useful alongside instanceof assertions.

packages/client/src/error.ts (1)

177-179: Semantics of isDefinedError remain correct with custom hasInstance

Good to see predicate relies on instanceof; your Symbol.hasInstance override keeps behavior consistent across graphs.

Comment thread packages/client/src/error.ts
Comment thread packages/client/src/error.ts Outdated
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: 0

♻️ Duplicate comments (2)
packages/client/src/error.ts (2)

145-167: Strengthen cross-context instanceof to cover subclasses; tighten comments; swap to WeakSet.has.

Right now only base ORPCError instances from another graph pass; subclass instances won’t (constructor differs and isn’t registered). Walking the prototype chain of the instance’s constructor fixes that and keeps the behavior scoped to the base class. Also adopt WeakSet.has when you switch the registry.

Apply:

-  static override[Symbol.hasInstance](instance: unknown): boolean {
-    if (
-      GlobalORPCErrorConstructors.includes(this) // not applicable to extended classes
-      && GlobalORPCErrorConstructors.includes(getConstructor(instance))
-    ) {
-      return true
-    }
-
-    // fallback to default instanceof check
-    return super[Symbol.hasInstance](instance)
-  }
+  static override[Symbol.hasInstance](instance: unknown): boolean {
+    // Apply custom logic only when RHS is a registered base ORPCError constructor.
+    if (GlobalORPCErrorConstructors.has(this as any)) {
+      // Support subclasses across contexts by walking the constructor chain.
+      let ctor = getConstructor(instance)
+      while (ctor) {
+        if (GlobalORPCErrorConstructors.has(ctor)) return true
+        const proto = (ctor as any)?.prototype && Object.getPrototypeOf((ctor as any).prototype)
+        ctor = proto?.constructor
+      }
+    }
+    // Fallback to the default prototype-chain-based instanceof check.
+    return super[Symbol.hasInstance](instance)
+  }

This also addresses prior feedback about the inline comments being imprecise.


169-173: Make registration idempotent.

Avoid duplicates across HMR by using WeakSet.add (or guard push if you keep Array).

Apply one of:

  • With WeakSet:
-GlobalORPCErrorConstructors.push(ORPCError)
+GlobalORPCErrorConstructors.add(ORPCError)
  • If you keep Array:
-if (true) GlobalORPCErrorConstructors.push(ORPCError)
+if (!GlobalORPCErrorConstructors.includes(ORPCError)) {
+  GlobalORPCErrorConstructors.push(ORPCError)
+}
🧹 Nitpick comments (1)
packages/client/src/error.ts (1)

102-111: Use WeakSet for the global registry; avoid duplicates, O(1) lookups, and dev-time leaks.

Array.includes is O(n), allows duplicates across HMR, and keeps strong refs. WeakSet dedups and lets GC reclaim unused constructors.

Apply:

 const GlobalORPCErrorConstructorsSymbol = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}@${ORPC_CLIENT_PACKAGE_VERSION}/error/ORPCErrorConstructors__`)
-void ((globalThis as any)[GlobalORPCErrorConstructorsSymbol] ??= [])
-const GlobalORPCErrorConstructors: unknown[] = (globalThis as any)[GlobalORPCErrorConstructorsSymbol]
+(globalThis as any)[GlobalORPCErrorConstructorsSymbol] ||= new WeakSet<Function>()
+const GlobalORPCErrorConstructors: WeakSet<Function> = (globalThis as any)[GlobalORPCErrorConstructorsSymbol]

Nit: dropping the leading void simplifies intent without changing semantics:

-void ((globalThis as any)[GlobalORPCErrorConstructorsSymbol] ??= [])
+(globalThis as any)[GlobalORPCErrorConstructorsSymbol] ||= new WeakSet<Function>()

Note: This approach assumes both module graphs share the same globalThis (true for Next.js Optimized SSR, not for Edge/Workers). Please confirm your target environments share globals; otherwise this workaround won’t bridge isolates.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8f88e and 1072280.

📒 Files selected for processing (1)
  • packages/client/src/error.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/client/src/error.ts (2)
packages/client/src/consts.ts (2)
  • ORPC_CLIENT_PACKAGE_NAME (1-1)
  • ORPC_CLIENT_PACKAGE_VERSION (2-2)
packages/shared/src/object.ts (1)
  • getConstructor (33-40)
🔇 Additional comments (1)
packages/client/src/error.ts (1)

2-3: Imports and build-time const usage look good.

getConstructor is correctly consumed; package name/version placeholders align with the Symbol.for keying below.

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

♻️ Duplicate comments (1)
packages/client/src/error.ts (1)

158-158: Clarify the intent of the subclass gating comment

Make the comment explicit about base-class-only behavior to aid future readers.

Apply:

-    // not applicable to extended classes
+    // Apply custom logic only for the base ORPCError constructor; subclasses fall back to default semantics.
🧹 Nitpick comments (2)
packages/client/src/error.ts (2)

108-111: Type the global registry as WeakSet and parametrize the initializer

These entries are constructor functions. Tightening the type improves readability and tooling, and avoids accidental non-callable entries.

Apply:

-const GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}@${ORPC_CLIENT_PACKAGE_VERSION}/error/ORPC_ERROR_CONSTRUCTORS__`)
-void ((globalThis as any)[GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL] ??= new WeakSet())
-const globalORPCErrorConstructors: WeakSet<object> = (globalThis as any)[GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL]
+const GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}@${ORPC_CLIENT_PACKAGE_VERSION}/error/ORPC_ERROR_CONSTRUCTORS__`)
+void ((globalThis as any)[GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL] ??= new WeakSet<Function>())
+const globalORPCErrorConstructors: WeakSet<Function> = (globalThis as any)[GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL]

108-108: Optional: make the global key version-agnostic to survive patch bumps

Using the exact version in the key can split the registry after minor/patch updates in mixed graphs, reintroducing instanceof issues. If you want cross-graph compatibility across same-major versions (or any version), drop the version (or encode only major).

Minimal option (versionless):

-const GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}@${ORPC_CLIENT_PACKAGE_VERSION}/error/ORPC_ERROR_CONSTRUCTORS__`)
+const GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}__/error/ORPC_ERROR_CONSTRUCTORS__`)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1072280 and b9dea09.

📒 Files selected for processing (2)
  • packages/client/src/error.ts (3 hunks)
  • packages/shared/src/object.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/shared/src/object.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/client/src/error.ts (2)
packages/client/src/consts.ts (2)
  • ORPC_CLIENT_PACKAGE_NAME (1-1)
  • ORPC_CLIENT_PACKAGE_VERSION (2-2)
packages/shared/src/object.ts (1)
  • getConstructor (32-39)
🪛 Biome (2.1.2)
packages/client/src/error.ts

[error] 160-160: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ 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/client/src/error.ts (3)

2-3: LGTM: Imports align with new cross-context logic

The added imports are correct and used appropriately in this file.


157-168: Symbol.hasInstance fallback and gating logic look solid

Custom logic only runs when checking against the base class; otherwise it defers to default prototype-chain semantics. This preserves subclass behavior.


170-174: Idempotent global registration is correct

Using WeakSet.add ensures no duplicates across HMR and avoids leaks. Good call.

Comment thread packages/client/src/error.ts
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.

isDefinedError doesn't work when using globalThis.$client for SSR optimization in nextjs

1 participant