Skip to content

feat(client): adapter interceptor - #899

Merged
dinwwwh merged 3 commits into
mainfrom
feat/client/adapter-interceptor
Aug 21, 2025
Merged

feat(client): adapter interceptor#899
dinwwwh merged 3 commits into
mainfrom
feat/client/adapter-interceptor

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Aug 20, 2025

Copy link
Copy Markdown
Member

LinkFetchClient, RPCLink, OpenAPILink, ... now support adapterInterceptors option for intercept fetch request/response

Summary by CodeRabbit

  • New Features

    • Plugin support for the fetch client with ordered runtime initialization.
    • Adapter interceptors to customize request/response handling.
    • Client options expanded to accept plugins and interceptors.
  • Refactor

    • Client constructor now accepts a single consolidated options object.
    • Handler option shapes adjusted to reorganize and restrict where plugins are provided.
  • Tests

    • Added unit and type tests for plugin composition and interceptor execution.

LinkFetchClient, RPCLink, OpenAPILink, ... now support `adapterInterceptors` option for intercept fetch request/response
@vercel

vercel Bot commented Aug 20, 2025

Copy link
Copy Markdown

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

Project Deployment Preview Comments Updated (UTC)
orpc Ready Ready Preview Comment Aug 21, 2025 2:15am

@coderabbitai

coderabbitai Bot commented Aug 20, 2025

Copy link
Copy Markdown

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 adapter interceptors and plugin support to the fetch link client API and types; constructor now accepts LinkFetchClientOptions (fetch, adapterInterceptors, plugins). Introduces LinkFetchPlugin and CompositeLinkFetchPlugin, updates call flow to build Requests via toFetchRequest, run interceptors with redirect: 'manual', and return StandardLazyResponse. Tests and type-tests added/updated.

Changes

Cohort / File(s) Summary
Fetch Client Core
packages/client/src/adapters/fetch/link-fetch-client.ts
Replaced single-{fetch} ctor param with exported LinkFetchClientOptions (includes fetch, adapterInterceptors?, plugins?); added LinkFetchInterceptorOptions; ctor initializes CompositeLinkFetchPlugin and calls initRuntimeAdapter; call accepts standardRequest, converts via toFetchRequest(linkOptions), applies adapter interceptors with init: { redirect: 'manual' }, invokes fetch, and returns StandardLazyResponse.
Plugin Types & Composite
packages/client/src/adapters/fetch/plugin.ts
Added exported LinkFetchPlugin<T> (extends StandardLinkPlugin) with optional initRuntimeAdapter(options: LinkFetchClientOptions<T>): void; implemented CompositeLinkFetchPlugin that forwards initRuntimeAdapter to contained plugins in order.
Fetch Client Tests
packages/client/src/adapters/fetch/link-fetch-client.test.ts
Updated tests to construct linkOptions with fetch and adapterInterceptors, assert toFetchRequest called with linkOptions, verify both interceptors invoked with context (request, options, init:{redirect:'manual'}, input, path), and added plugin init assertion.
Plugin Unit Tests
packages/client/src/adapters/fetch/plugin.test.ts
New tests for CompositeLinkFetchPlugin verifying initRuntimeAdapter is propagated to underlying plugins and invoked in ascending order (-1, 0, 1).
Type-level Tests
packages/client/src/adapters/fetch/plugin.test-d.ts
New type-only tests asserting mutual assignability between LinkFetchPlugin and StandardLinkPlugin for a sample context.
RPC/OpenAPI & Handler Option Types
packages/client/src/adapters/fetch/rpc-link.ts, packages/openapi-client/src/adapters/fetch/openapi-link.ts, packages/openapi/src/adapters/fetch/openapi-handler.ts, packages/openapi/src/adapters/node/openapi-handler.ts, packages/server/src/adapters/fetch/rpc-handler.ts, packages/server/src/adapters/node/rpc-handler.ts, apps/content/docs/advanced/superjson.md
Reworked several public option types to compose LinkFetchClientOptions<T> first and use Omit<Standard*Options<T>, 'plugins'> for the other part (removing duplicate plugins), converted some type aliases into interface declarations, and adjusted constructor signatures to accept the new composed option types; runtime logic and plugin injection behavior preserved (e.g., strictGetMethodPluginEnabled).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as Caller
  participant LFC as LinkFetchClient
  participant CPL as CompositeLinkFetchPlugin
  participant P as Plugin...
  note over LFC,CPL: Initialization
  App->>LFC: new LinkFetchClient(linkOptions)
  LFC->>CPL: initRuntimeAdapter(linkOptions)
  CPL->>P: initRuntimeAdapter(linkOptions) (ordered)
Loading
sequenceDiagram
  autonumber
  participant App as Caller
  participant LFC as LinkFetchClient
  participant INT as AdapterInterceptors
  participant F as fetch
  participant Conv as toStandardLazyResponse

  App->>LFC: call(standardRequest, options, path, input)
  note right of LFC: req = toFetchRequest(standardRequest, linkOptions)
  LFC->>INT: intercept({request:req, init:{redirect:'manual'}, path, input, ...options})
  INT-->>LFC: request/init (possibly modified)
  LFC->>F: fetch(request, init)
  F-->>LFC: Response
  LFC->>Conv: toStandardLazyResponse(Response, { signal })
  Conv-->>App: StandardLazyResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • unnoq/orpc#338 — Overlaps LinkFetchClient types and call/fetch signature changes; likely directly related.
  • unnoq/orpc#333 — Refactors interceptor/plugin option shapes and runtime-adapter wiring; closely connected to plugin/initRuntimeAdapter behavior.
  • unnoq/orpc#292 — Adds/adjusts adapter-level plugin and Composite plugin logic; strongly tied to these edits.

Poem

I hopped through links with gentle paws,
Plugins lined up by ordering laws;
Interceptors sniff each trail,
Redirects sing the manual tale;
Tests clap soft — a rabbit’s cause. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/client/adapter-interceptor

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 20, 2025

@gemini-code-assist gemini-code-assist 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.

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 introduces a new adapterInterceptors option across various client-side link implementations, specifically enhancing the LinkFetchClient. This feature allows developers to intercept and modify fetch requests and responses, providing a powerful mechanism for custom request handling, logging, or authentication before the request is sent or after the response is received. It also lays the groundwork for a more robust plugin system for these adapters, enabling greater extensibility and modularity in client-side operations.

Highlights

  • New adapterInterceptors Option: The LinkFetchClientOptions now includes an adapterInterceptors option, allowing developers to define an array of functions that can intercept and modify fetch requests and responses. This provides a flexible way to implement cross-cutting concerns like logging, authentication, or caching.
  • Integrated Interception Logic: The core LinkFetchClient.call method has been updated to integrate the new interception logic. It now uses a shared intercept utility to process the adapterInterceptors chain, ensuring that each interceptor can perform actions before or after the actual network request.
  • New Plugin System for Adapters: A new plugin system has been introduced for LinkFetchClient through the LinkFetchPlugin interface and CompositeLinkFetchPlugin class. This system enables extensible runtime adapter initialization, allowing for more modular and configurable client behavior.
  • Expanded Test Coverage: Comprehensive test cases have been added and updated for LinkFetchClient to validate the correct functionality of the new adapterInterceptors and to ensure the proper initialization and execution of the new plugin system.
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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a valuable feature: adapter-level interceptors for fetch-based clients, enhancing modularity and extensibility. The implementation is well-executed, employing a composite pattern for plugins and maintaining a clear separation of concerns. The accompanying tests are comprehensive, covering interceptor functionality, plugin initialization, and type compatibility. I have one suggestion for a minor refactoring in the LinkFetchClient constructor to further improve code clarity and maintainability.

Comment thread packages/client/src/adapters/fetch/link-fetch-client.ts
@pkg-pr-new

pkg-pr-new Bot commented Aug 20, 2025

Copy link
Copy Markdown
More templates

@orpc/arktype

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

@orpc/client

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

@orpc/contract

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

@orpc/experimental-durable-event-iterator

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

@orpc/hey-api

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

@orpc/interop

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

@orpc/json-schema

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

@orpc/nest

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

@orpc/openapi

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

@orpc/openapi-client

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

@orpc/otel

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

@orpc/react

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

@orpc/react-query

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

@orpc/experimental-react-swr

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

@orpc/server

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

@orpc/shared

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

@orpc/solid-query

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

@orpc/standard-server

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

@orpc/standard-server-aws-lambda

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

@orpc/standard-server-fetch

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

@orpc/standard-server-node

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

@orpc/standard-server-peer

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

@orpc/svelte-query

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

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/vue-colada

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

@orpc/vue-query

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

@orpc/zod

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

commit: c653184

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (7)
packages/client/src/adapters/fetch/plugin.test-d.ts (1)

6-7: Prefer toEqualTypeOf for concise bidirectional assignability check

You can collapse the two mutual extends assertions into a single, clearer equality assertion.

-    expectTypeOf<LinkFetchPlugin<{ a: string }>>().toExtend<StandardLinkPlugin<{ a: string }>>()
-    expectTypeOf<StandardLinkPlugin<{ a: string }>>().toExtend<LinkFetchPlugin<{ a: string }>>()
+    expectTypeOf<LinkFetchPlugin<{ a: string }>>().toEqualTypeOf<StandardLinkPlugin<{ a: string }>>()
packages/client/src/adapters/fetch/plugin.test.ts (1)

34-35: Optionally assert stability when orders are equal

Consider adding a case with two plugins having the same order to lock in stable ordering (insertion order) expectations.

Example (additive):

it('keeps insertion order when orders are equal', () => {
  const a = { order: 0, initRuntimeAdapter: vi.fn() } as const
  const b = { order: 0, initRuntimeAdapter: vi.fn() } as const
  const composite = new CompositeLinkFetchPlugin([a, b])

  composite.initRuntimeAdapter({} as any)

  expect(a.initRuntimeAdapter).toHaveBeenCalledBefore(b.initRuntimeAdapter)
})
packages/client/src/adapters/fetch/plugin.ts (2)

6-8: Add a brief doc comment on initRuntimeAdapter’s purpose

A short JSDoc helps clarify when this runs and what mutations are expected (e.g., mutating options to initialize runtime adapters).

-export interface LinkFetchPlugin<T extends ClientContext> extends StandardLinkPlugin<T> {
-  initRuntimeAdapter?(options: LinkFetchClientOptions<T>): void
-}
+export interface LinkFetchPlugin<T extends ClientContext> extends StandardLinkPlugin<T> {
+  /**
+   * Called during LinkFetchClient construction to initialize runtime fetch adapters.
+   * Implementations may safely mutate the provided options object to wire adapter behavior.
+   */
+  initRuntimeAdapter?(options: LinkFetchClientOptions<T>): void
+}

10-17: Consider extracting shared types to avoid a type-only circular reference

plugin.ts imports LinkFetchClientOptions from link-fetch-client.ts (type-only) while link-fetch-client.ts imports CompositeLinkFetchPlugin as a value. This is safe at runtime, but moving shared types (e.g., LinkFetchClientOptions) to a small types module can simplify layering and reduce future coupling.

packages/client/src/adapters/fetch/link-fetch-client.test.ts (1)

58-74: Optionally assert interceptor call order

You already assert both are called. Adding an order assertion locks in sequencing guarantees.

     expect(interceptor1).toBeCalledTimes(1)
     expect(interceptor2).toBeCalledTimes(1)
+    expect(interceptor1).toHaveBeenCalledBefore(interceptor2)
packages/client/src/adapters/fetch/link-fetch-client.ts (2)

42-45: Normalize the fallback fetch to the extended signature

When options.fetch is not provided, this.fetch points to globalThis.fetch (2 params) while the declared type expects 5 params. JS ignores extra args, but wrapping improves clarity, avoids relying on parameter-count compatibility, and aligns with the declared type.

-    this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis)
+    this.fetch = options.fetch ?? ((request, init) => globalThis.fetch(request, init))

Optionally, throw a helpful error if neither options.fetch nor globalThis.fetch exist.


11-16: Consider widening init to a narrowed RequestInit subset if needed

If adapters require more control (e.g., cache, credentials), you might evolve init from { redirect?: ... } to Pick<RequestInit, 'redirect' | 'cache' | 'credentials' | ...>. Current narrow shape is fine if redirect is the only supported knob.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 03fed0c and 4284db9.

📒 Files selected for processing (5)
  • packages/client/src/adapters/fetch/link-fetch-client.test.ts (3 hunks)
  • packages/client/src/adapters/fetch/link-fetch-client.ts (1 hunks)
  • packages/client/src/adapters/fetch/plugin.test-d.ts (1 hunks)
  • packages/client/src/adapters/fetch/plugin.test.ts (1 hunks)
  • packages/client/src/adapters/fetch/plugin.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
packages/client/src/adapters/fetch/plugin.test.ts (1)
packages/client/src/adapters/fetch/plugin.ts (2)
  • LinkFetchPlugin (6-8)
  • CompositeLinkFetchPlugin (10-17)
packages/client/src/adapters/fetch/plugin.test-d.ts (2)
packages/client/src/adapters/fetch/plugin.ts (1)
  • LinkFetchPlugin (6-8)
packages/client/src/adapters/standard/plugin.ts (1)
  • StandardLinkPlugin (4-7)
packages/client/src/adapters/fetch/plugin.ts (2)
packages/client/src/adapters/standard/plugin.ts (2)
  • StandardLinkPlugin (4-7)
  • CompositeStandardLinkPlugin (9-21)
packages/client/src/adapters/fetch/link-fetch-client.ts (1)
  • LinkFetchClientOptions (18-30)
packages/client/src/adapters/fetch/link-fetch-client.test.ts (2)
packages/client/src/adapters/fetch/link-fetch-client.ts (1)
  • LinkFetchClient (32-60)
packages/client/src/adapters/fetch/plugin.ts (1)
  • initRuntimeAdapter (12-16)
packages/client/src/adapters/fetch/link-fetch-client.ts (8)
packages/client/src/types.ts (2)
  • ClientContext (6-6)
  • ClientOptions (8-12)
packages/standard-server-fetch/src/request.ts (2)
  • ToFetchRequestOptions (24-24)
  • toFetchRequest (26-36)
packages/shared/src/interceptor.ts (1)
  • Interceptor (13-16)
packages/client/src/adapters/fetch/plugin.ts (2)
  • LinkFetchPlugin (6-8)
  • CompositeLinkFetchPlugin (10-17)
packages/client/src/adapters/standard/types.ts (1)
  • StandardLinkClient (9-11)
packages/shared/src/array.ts (1)
  • toArray (1-3)
packages/standard-server/src/types.ts (2)
  • StandardRequest (13-24)
  • StandardLazyResponse (43-49)
packages/standard-server-fetch/src/response.ts (1)
  • toStandardLazyResponse (20-36)
⏰ 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). (4)
  • GitHub Check: publish-commit
  • GitHub Check: lint
  • GitHub Check: test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
packages/client/src/adapters/fetch/plugin.test-d.ts (1)

4-9: Verify type-test environment provides globals

This .test-d.ts uses describe/it/expectTypeOf as globals. Please ensure tsconfig includes vitest globals (or add a local reference) so editors and CI don’t flag missing types.

If needed, add this at the top:

/// <reference types="vitest/globals" />
packages/client/src/adapters/fetch/plugin.test.ts (1)

6-36: LGTM: ordering and propagation behavior are well covered

Good coverage validating forward propagation, identical options object identity, and ordering based on order values (including the default 0).

packages/client/src/adapters/fetch/link-fetch-client.test.ts (2)

39-41: Good assertion: toFetchRequest called with full linkOptions

Asserting the second argument is linkOptions guards the API change to pass the entire options bag through.


76-91: LGTM: plugin initialization is invoked with the full options

Nice validation that initRuntimeAdapter receives the exact options object, which matches the mutation-based initialization design.

packages/client/src/adapters/fetch/link-fetch-client.ts (2)

37-45: Confirm whether Standard plugin init should also be invoked

CompositeLinkFetchPlugin also supports the base init flow via CompositeStandardLinkPlugin. If any plugins rely on init (not just initRuntimeAdapter), you may want to call plugin.init(options as any) here or clarify that fetch adapters only use initRuntimeAdapter.


50-54: Interception flow looks correct

The intercept call correctly composes adapter interceptors, enforces redirect: 'manual' by default, and passes the normalized fetch signature through. Nice.

@codecov

codecov Bot commented Aug 21, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/server/src/adapters/fetch/rpc-handler.ts (1)

26-31: Avoid mutating caller-supplied options; build a new options object instead.

Current code pushes into options.plugins, which can surprise callers (shared object, duplicate inserts if reused). Prefer an immutable merge and pass that downstream.

Apply:

-    if (options.strictGetMethodPluginEnabled ?? true) {
-      options.plugins ??= []
-      options.plugins.push(new StrictGetMethodPlugin())
-    }
-
-    super(new StandardRPCHandler(router, options), options)
+    const enableStrict = options.strictGetMethodPluginEnabled ?? true
+    const nextOptions: RPCHandlerOptions<T> = {
+      ...options,
+      plugins: enableStrict
+        ? [...(options.plugins ?? []), new StrictGetMethodPlugin()]
+        : options.plugins,
+    }
+
+    super(new StandardRPCHandler(router, nextOptions), nextOptions)

Optional follow-up: guard against duplicate StrictGetMethodPlugin if users also add it manually.

packages/server/src/adapters/node/rpc-handler.ts (2)

26-29: StrictGetMethodPlugin is likely the wrong plugin type target (pushed into Node plugins)

NodeHttpHandlerOptions exposes plugins?: NodeHttpHandlerPlugin<T>[], but StrictGetMethodPlugin appears to be a Standard-layer plugin (imported from ../../plugins) rather than a NodeHttpHandlerPlugin. Pushing it into options.plugins risks a type mismatch or a no-op at runtime.

Refactor to inject the strict plugin into the StandardRPCHandler options instead, and avoid mutating the caller’s options object.

Apply this diff:

   constructor(router: Router<any, T>, options: NoInfer<RPCHandlerOptions<T>> = {}) {
-    if (options.strictGetMethodPluginEnabled ?? true) {
-      options.plugins ??= []
-      options.plugins.push(new StrictGetMethodPlugin())
-    }
-
-    super(new StandardRPCHandler(router, options), options)
+    // Build Standard options separately to attach Standard-level plugins.
+    const standardOptions: StandardRPCHandlerOptions<T> = { ...options }
+    if (options.strictGetMethodPluginEnabled ?? true) {
+      standardOptions.plugins ??= []
+      standardOptions.plugins.push(new StrictGetMethodPlugin())
+    }
+
+    super(new StandardRPCHandler(router, standardOptions), options)
   }

25-25: Missing import for NoInfer in Node RPC handler
The RPCHandler constructor uses NoInfer<RPCHandlerOptions<T>> on line 25, but there’s no import type { NoInfer } … at the top of packages/server/src/adapters/node/rpc-handler.ts. This will fail type-checking in isolated modules.

• Add an import for NoInfer from wherever it’s defined, for example:

import type { NoInfer } from '../../shared'  // adjust path to your utility/types package

• If NoInfer is intended as a global ambient type, ensure its .d.ts declaration is included in your TS config’s typeRoots.

🧹 Nitpick comments (6)
packages/client/src/adapters/fetch/rpc-link.ts (1)

7-9: Add documentation note clarifying plugin surface in RPCLinkOptions

Please augment the public API docs on RPCLinkOptions to call out that only fetch-level plugins live here, and that standard RPC plugins have been removed from this surface. This will help users migrate any existing StandardRPCLink plugins to the new fetch-layer plugin model.

• File needing update: packages/client/src/adapters/fetch/rpc-link.ts
• Context: the RPCLinkOptions interface extends LinkFetchClientOptions<T> (which still provides a plugins array for fetch plugins) and omits the 'plugins' field from StandardRPCLinkOptions<T> (standard RPC plugins)

Apply this diff:

 export interface RPCLinkOptions<T extends ClientContext>
   extends LinkFetchClientOptions<T>, Omit<StandardRPCLinkOptions<T>, 'plugins'> {}
+
+/**
+ * Notes:
+ * - Standard RPC plugins (from `StandardRPCLinkOptions`) are intentionally omitted here.
+ * - To supply fetch-layer plugins, use the `plugins` array inherited from `LinkFetchClientOptions`.
+ * - For request/response interception on the fetch adapter, prefer `adapterInterceptors`.
+ */

(optional) To double-check any client code still passing standard RPC plugins into the fetch adapter, you can run:

rg -nP -C2 'new\s+RPCLink\s*\('
rg -nP -C2 'RPCLinkOptions<'
rg -nP -C2 '\bplugins\s*:'
packages/openapi/src/adapters/fetch/openapi-handler.ts (1)

7-9: Public type surface now omits standard-layer plugins; ensure migration notes.

OpenAPIHandlerOptions now composes FetchHandlerOptions with StandardOpenAPIHandlerOptions minus plugins. This is coherent with the new adapter interceptor/plugin model. Please add a short TsDoc note to signal the plugin relocation.

Apply:

-export interface OpenAPIHandlerOptions<T extends Context> extends FetchHandlerOptions<T>, Omit<StandardOpenAPIHandlerOptions<T>, 'plugins'> {
-}
+export interface OpenAPIHandlerOptions<T extends Context>
+  extends FetchHandlerOptions<T>, Omit<StandardOpenAPIHandlerOptions<T>, 'plugins'> {
+  /**
+   * Standard-layer plugins are intentionally omitted.
+   * Use `FetchHandlerOptions.plugins` (fetch-layer plugins) and `adapterInterceptors` instead.
+   */
+}
packages/openapi/src/adapters/node/openapi-handler.ts (1)

7-9: Mirror of fetch OpenAPI handler: omit standard-layer plugins and document it.

Type change matches the fetch variant; suggest adding TsDoc to steer users toward NodeHttpHandlerOptions.plugins and adapterInterceptors.

Apply:

-export interface OpenAPIHandlerOptions<T extends Context> extends NodeHttpHandlerOptions<T>, Omit<StandardOpenAPIHandlerOptions<T>, 'plugins'> {
-}
+export interface OpenAPIHandlerOptions<T extends Context>
+  extends NodeHttpHandlerOptions<T>, Omit<StandardOpenAPIHandlerOptions<T>, 'plugins'> {
+  /**
+   * Standard-layer plugins are intentionally omitted.
+   * Use Node HTTP handler plugins (`NodeHttpHandlerOptions.plugins`)
+   * and `adapterInterceptors` for interception.
+   */
+}
packages/server/src/adapters/node/rpc-handler.ts (3)

9-16: Interface now omits Standard plugins — confirm intended API change and migration impact

By extending NodeHttpHandlerOptions and Omit<StandardRPCHandlerOptions, 'plugins'>, callers can no longer pass Standard-level plugins through RPCHandlerOptions. If this is an intentional consolidation to the Node plugin surface, please confirm and document the migration (e.g., “pass NodeHttpHandlerPlugin via Node options; Standard plugins are injected internally only”). Otherwise, reintroduce a path for Standard plugins.

Would you like me to draft a short migration note for this API change?


25-32: Do not mutate caller-supplied options

Mutating options.plugins directly has surprising side effects for callers. The refactor above clones options for the Standard layer, preserving immutability and separation of concerns between Standard and Node handler options.


15-16: Add tests for strictGetMethodPluginEnabled on/off behavior

Add integration tests for RPCHandler (Node adapter) that verify:

  • Default (undefined/true) rejects GET with a body and/or enforces method semantics.
  • When set to false, the strict behavior is not applied.

This guards the plugin wiring and future refactors.

I can draft the tests if you share the existing Node adapter test harness entry points.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4284db9 and 22284c7.

📒 Files selected for processing (6)
  • packages/client/src/adapters/fetch/rpc-link.ts (1 hunks)
  • packages/openapi-client/src/adapters/fetch/openapi-link.ts (1 hunks)
  • packages/openapi/src/adapters/fetch/openapi-handler.ts (1 hunks)
  • packages/openapi/src/adapters/node/openapi-handler.ts (1 hunks)
  • packages/server/src/adapters/fetch/rpc-handler.ts (1 hunks)
  • packages/server/src/adapters/node/rpc-handler.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
packages/client/src/adapters/fetch/rpc-link.ts (2)
packages/client/src/adapters/fetch/link-fetch-client.ts (1)
  • LinkFetchClientOptions (18-30)
packages/client/src/adapters/standard/rpc-link.ts (1)
  • StandardRPCLinkOptions (11-12)
packages/openapi-client/src/adapters/fetch/openapi-link.ts (2)
packages/client/src/adapters/fetch/link-fetch-client.ts (1)
  • LinkFetchClientOptions (18-30)
packages/openapi-client/src/adapters/standard/openapi-link.ts (1)
  • StandardOpenAPILinkOptions (12-13)
packages/openapi/src/adapters/node/openapi-handler.ts (3)
packages/openapi/src/adapters/fetch/openapi-handler.ts (2)
  • OpenAPIHandlerOptions (7-8)
  • OpenAPIHandler (16-20)
packages/server/src/adapters/node/handler.ts (2)
  • NodeHttpHandlerOptions (20-24)
  • NodeHttpHandler (26-70)
packages/openapi/src/adapters/standard/openapi-handler.ts (1)
  • StandardOpenAPIHandlerOptions (10-12)
packages/openapi/src/adapters/fetch/openapi-handler.ts (4)
packages/openapi/src/adapters/node/openapi-handler.ts (2)
  • OpenAPIHandlerOptions (7-8)
  • OpenAPIHandler (16-20)
packages/server/src/context.ts (1)
  • Context (1-1)
packages/server/src/adapters/fetch/handler.ts (2)
  • FetchHandlerOptions (19-23)
  • FetchHandler (25-68)
packages/openapi/src/adapters/standard/openapi-handler.ts (1)
  • StandardOpenAPIHandlerOptions (10-12)
packages/server/src/adapters/node/rpc-handler.ts (3)
packages/server/src/adapters/fetch/rpc-handler.ts (1)
  • RPCHandlerOptions (9-16)
packages/server/src/adapters/node/handler.ts (1)
  • NodeHttpHandlerOptions (20-24)
packages/server/src/adapters/standard/rpc-handler.ts (1)
  • StandardRPCHandlerOptions (11-13)
packages/server/src/adapters/fetch/rpc-handler.ts (3)
packages/server/src/adapters/node/rpc-handler.ts (1)
  • RPCHandlerOptions (9-16)
packages/server/src/adapters/fetch/handler.ts (1)
  • FetchHandlerOptions (19-23)
packages/server/src/adapters/standard/rpc-handler.ts (1)
  • StandardRPCHandlerOptions (11-13)
⏰ 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 (4)
packages/openapi-client/src/adapters/fetch/openapi-link.ts (1)

8-10: The above scripts will confirm whether LinkFetchClientOptions truly exposes a plugins field (or only adapterInterceptors), and will locate the base ToFetchRequestOptions type to see which fetch-layer hooks are available. Once we verify that, we can finalize the doc snippet accordingly.

packages/openapi/src/adapters/fetch/openapi-handler.ts (1)

17-18: Constructor signature tightened to the new options type — LGTM.

Type-only change; runtime remains the same. Consider adding a release note entry indicating that callers should migrate from the previous intersection type to OpenAPIHandlerOptions.

packages/server/src/adapters/fetch/rpc-handler.ts (1)

9-16: Type surface now drops standard-layer plugins; consistent with fetch-layer plugin model.

Looks good and consistent with the Node variant. Ensure docs highlight that plugin injection now happens via FetchHandlerOptions.plugins, with strictGetMethodPlugin controlled by the new flag.

packages/openapi/src/adapters/node/openapi-handler.ts (1)

17-18: Constructor retyped to the new options — LGTM.

No runtime impact; aligns with the fetch-side change. Add a brief migration note in the package CHANGELOG.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/content/docs/advanced/superjson.md (3)

86-87: Confirm plugins property source or reintroduce it in SuperJSONHandlerOptions

You’ve omitted plugins from StandardHandlerOptions<T> but still mutate options.plugins in the constructor. Please verify that FetchHandlerOptions<T> defines plugins; otherwise this won’t type-check.

If FetchHandlerOptions<T> does not include plugins, either:

  • Reintroduce it explicitly on this options interface, or
  • Stop mutating options.plugins and pass the plugin via the appropriate constructor option to StandardHandler.

Proposed explicit reintroduction (if needed):

 export interface SuperJSONHandlerOptions<T extends Context>
   extends FetchHandlerOptions<T>, Omit<StandardHandlerOptions<T>, 'plugins'> {
+  // Required so SuperJSONHandler can inject StrictGetMethodPlugin
+  plugins?: StandardHandlerOptions<T>['plugins']
   /**
    * Enable or disable the StrictGetMethodPlugin.
    *
    * @default true
    */
   strictGetMethodPluginEnabled?: boolean
 }

97-97: NoInfer is used but not imported; consider importing or dropping for docs simplicity

The constructor’s options: NoInfer<SuperJSONHandlerOptions<T>> will fail to type-check in the docs unless NoInfer is available in scope.

Two options:

  • Import it from your shared types module (verify the correct path in this repo):
 // ---cut---
 import type { StandardRPCSerializer } from '@orpc/client/standard'
 import type { Context, Router } from '@orpc/server'
 import type { FetchHandlerOptions } from '@orpc/server/fetch'
+import type { NoInfer } from '@orpc/shared' // verify: correct source of NoInfer?
  • Or, simplify the docs snippet by dropping NoInfer:
-  constructor(router: Router<any, T>, options: NoInfer<SuperJSONHandlerOptions<T>> = {}) {
+  constructor(router: Router<any, T>, options: SuperJSONHandlerOptions<T> = {}) {

130-132: Link options composition looks correct; consider documenting adapterInterceptors usage

Extending LinkFetchClientOptions<T> and omitting 'plugins' from StandardLinkOptions<T> aligns with the PR’s client-side changes. To make the new capability discoverable, add a short example showing adapterInterceptors usage with SuperJSONLink.

Example to append below the code block:

import { SuperJSONLink } from './superjson-link'

const link = new SuperJSONLink({
  // from LinkFetchClientOptions<T>:
  fetch, // custom fetch if needed
  adapterInterceptors: [
    {
      // runs before fetch
      request: async (req, ctx) => {
        req.headers.set('x-trace-id', ctx.requestId)
        return req
      },
      // runs after fetch
      response: async (res) => {
        if (!res.ok) console.warn('request failed', res.status)
        return res
      },
    },
  ],

  // from StandardRPCLinkCodecOptions<T> / StandardLinkOptions<T> (minus 'plugins'):
  baseURL: 'https://api.example.com',
})

This will help users adopt the new adapterInterceptors option introduced by this PR.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 22284c7 and c653184.

📒 Files selected for processing (1)
  • apps/content/docs/advanced/superjson.md (3 hunks)
⏰ 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). (4)
  • GitHub Check: lint
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: publish-commit
  • GitHub Check: test

@dinwwwh
dinwwwh merged commit b5e327f into main Aug 21, 2025
11 checks passed
@dinwwwh
dinwwwh deleted the feat/client/adapter-interceptor branch June 22, 2026 01:52
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.

1 participant