Repository navigation
fix(openapi): keep server code out of the OpenAPI Link entry - #2358
florian-lefebvre wants to merge 7 commits into
Conversation
OpenAPILink moves to @orpc/openapi/fetch/link so Metro stops including the OpenAPI handler, @orpc/server, and rou3 in client bundles. @orpc/openapi/fetch keeps OpenAPIHandler. The link imports OpenAPILinkCodec directly, serializeHeaders moves to its own module, and Lazy, Lazyable, and unlazy move to @orpc/contract.
There was a problem hiding this comment.
Important
The @orpc/server re-export silently drops the LazyDefinition public type, which the refactor was otherwise meant to preserve.
Reviewed changes
OpenAPILinkmoved to a client-only entry —@orpc/openapi/fetchnow exports onlyOpenAPIHandler; the link lives at@orpc/openapi/fetch/link(added to both dev andpublishConfigexports). Verified viapnpm --filter @orpc/openapi prepack:dist/adapters/fetch/link.mjsimports only@orpc/client/*,@orpc/contract,@orpc/shared, and@standard-server/{core,fetch}— no@orpc/server, norou3.- Server code kept out of the link graph — the link imports
OpenAPILinkCodecdirectly instead of the../standardbarrel,serializeHeadersmoved fromstandard/utils.tstostandard/serialize-headers.ts, and the handler adapters importopenapi-handler-codecdirectly. Newfetch/link.test.tsguards all of this byvi.mock-ing the server modules androu3with throwing factories. Lazy/Lazyable/unlazyrelocated to@orpc/contract—@orpc/contract's index now star-exports./lazy, andpackages/server/src/lazy.tsre-exports from@orpc/contract;openapi/src/meta.tsand the link codec now sourceLazy/unlazyfrom@orpc/contract.- Docs, skills, playgrounds, and tests updated to the new import path; no remaining
OpenAPILinkimport from@orpc/openapi/fetchin tracked files.
Ran pnpm vitest run on the fetch/standard/contract-lazy tests (23 pass) and pnpm --filter @orpc/contract --filter @orpc/openapi type:check (passes).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Restore LazyDefinition on the @orpc/server public surface after the lazy module moved to @orpc/contract.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The only change since the prior pullfrog review is a one-line type re-export, which resolves the outstanding finding.
- Restored
LazyDefinitionon@orpc/server—packages/server/src/lazy.tsnow re-exportsLazyableandLazyDefinitionfrom@orpc/contract, closing the type-only regression the previous review flagged.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
Thanks for tracking this down! Could we make this non-breaking? Keep Bundlers with tree-shaking (esbuild, webpack, Rspack) already drop the handler from Changes:
|
@orpc/openapi/fetch exports OpenAPILink again, so the existing import path keeps working. The client-only @orpc/openapi/fetch/link entry stays for bundlers such as Metro that cannot tree-shake the handler out of the fetch entry. Docs keep /fetch as the documented import and point to /fetch/link for those bundlers.
…-shake The Link page keeps @orpc/openapi/fetch as the documented import and points Metro readers to the client-only @orpc/openapi/fetch/link entry in paragraph text.
The link entry fix does not need the serializeHeaders module rename or deep imports of the handler codec in the server adapters. Restore utils.ts and keep the ../standard imports in the handler adapters, so the diff only contains changes the fix requires.
|
@dinwwwh thanks for the review, done! |
There was a problem hiding this comment.
✅ No new issues found. The reverted scope is sound and the link entry is verifiably server-free.
Reviewed changes
Incremental delta since the prior pullfrog review (523140f → 1a410c9): the PR was reduced from a breaking entry move to a non-breaking, additive change.
- Restored
OpenAPILinkon@orpc/openapi/fetch—packages/openapi/src/adapters/fetch/index.tsagain exports bothOpenAPIHandlerandOpenAPILink, so existing imports keep working;@orpc/openapi/fetch/linkremains the client-only entry. - Dropped the unrelated churn — the handler-adapter import rewrites and the
standard/utils.ts→serialize-headers.tssplit were reverted;serializeHeadersagain lives instandard/utils.ts, and noserialize-headersreferences remain anywhere. - Docs narrowed to the new guidance — only
openapi/link.mdxchanged: the default import stays@orpc/openapi/fetch, with/linkrecommended for bundlers that do not tree-shake (e.g. Metro). All other docs still import from@orpc/openapi/fetch, consistent with the additive approach. Lazyrelocation kept —Lazy/unlazy/Lazyable/LazyDefinitionstill live in@orpc/contractand are re-exported from@orpc/server; theopenapi-link-codec.tsunlazyimport still points at@orpc/contract, which is what keeps server code out of the link graph.
Verified with pnpm --filter @orpc/openapi prepack: dist/adapters/fetch/link.mjs and every shared chunk it imports reference only @orpc/client, @orpc/contract, @orpc/shared, and @standard-server/* — no @orpc/server, no rou3 (the one shared chunk that does reference them, openapi.NQL3UlBV.mjs, is not in the link graph). Targeted vitest run on the changed suites (6 pass) and type:check for @orpc/contract/@orpc/openapi also pass.
ℹ️ PR description still announces a breaking change
The body says "This is a breaking import change for link users" and shows a @orpc/openapi/fetch → @orpc/openapi/fetch/link migration diff, but 522804d24 restored the OpenAPILink export on the main entry, so the change is now additive. Worth updating the description so reviewers and any release-note tooling do not advertise a breaking migration that no longer exists.
Technical details
# Stale breaking-change claim in PR description
## Affected sites
- PR #2358 description — "This is a breaking import change for link users." plus the diff block showing `-import { OpenAPILink } from '@orpc/openapi/fetch'`.
## Required outcome
- The description reflects the current additive approach: `OpenAPILink` stays on `@orpc/openapi/fetch`; `/link` is only recommended for bundlers that do not tree-shake.
## Suggested approach (optional)
- Note that the `!`-marked commit `cba48c46` was superseded by `522804d24`, and that the PR title (used for the squash commit) carries no breaking marker.
</details>
<!--
Pullfrog review metadata. These findings were written against 1a410c9;
if commits have landed on florian-lefebvre/sunfish since, treat every specific bug, file, or
line callout as POTENTIALLY STALE and re-diff before acting on it.
- Mode: IncrementalReview (delta against prior pullfrog review)
- Files reviewed: 11
- Commits reviewed: 6
- Base: main (49751e2)
- Head: florian-lefebvre/sunfish (1a410c9)
- Reviewed commits:
- 1a410c97 — Merge branch 'main' into florian-lefebvre/sunfish
- bfcaffc3 — refactor(openapi): drop unrelated churn from the link entry fix
- 35a9822c — docs(openapi): document the link import for bundlers that do not tree-shake
- 522804d2 — fix(openapi): keep OpenAPILink exported from the fetch entry
- 523140fa — fix(server): re-export LazyDefinition type
- cba48c46 — fix(openapi)!: keep server code out of the OpenAPI Link entry
- Prior pullfrog review: 523140f (https://github.com/middleapi/orpc/pull/2358#pullrequestreview-5701071610)
-->
<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->
<sup><a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a> | [View workflow run](https://github.com/middleapi/orpc/actions/runs/41020258770/job/123945726977) | Using `deepseek-v4.1-flash` (free via [Pullfrog for OSS](https://pullfrog.com/for-oss)) | [𝕏](https://x.com/pullfrogai)</sup>|
I updated it |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 9f214d8 | Commit Preview URL Branch Preview URL |
Oct 30 2026, 08:10 AM |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the prior pullfrog review (1a410c9 → 9f214d8) is a cleanup of the Lazy relocation plus a docs reorganization; no behavioral regressions found.
- Removed the
@orpc/serverlazy shim — deletedpackages/server/src/lazy.tsand pointed every server module'sLazy/unlazy/Lazyableimport at@orpc/contractdirectly. - Preserved
@orpc/server's public surface —index.tsnow re-exportsLazy/unlazy(value block) andLazyable/LazyDefinition(type block) from@orpc/contract, exactly what the shim forwarded before. - Fixed the contract self-import —
packages/contract/src/lazy.tssourcesMeta/AnyMetaPluginfrom./metainstead of self-referencing@orpc/contract. - Relocated the
/linktip — moved out ofopenapi/link.mdxintoopenapi/adapters.mdxandadapters/fetch-api.mdx. - Simplified the lazy
instanceofassertion —builder.test.tsnow asserts againstContractModule.Lazy(a single class instance).
Verified the link entry is still server-free: pnpm --filter @orpc/openapi prepack plus a full import-graph walk from dist/adapters/fetch/link.mjs reach only @orpc/client, @orpc/contract, @orpc/shared, and @standard-server/* — no @orpc/server, no rou3. type:check for @orpc/{server,contract,openapi}, targeted vitest run (48 pass), and eslint on the changed files all pass.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
Thanks for the changes, and sorry for the back-and-forth. I'd rather not add a separate Once that's released, enable tree-shaking in your app: EXPO_UNSTABLE_TREE_SHAKING=1
EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1 |
|
No problem, I also prefer that solution thanks! I already have treeshaking enabled. I'll let you close the PR |

Bundlers that tree-shake, such as esbuild, webpack, and Rspack, keep the OpenAPI Handler out of client bundles that import from
@orpc/openapi/fetch. If your bundler cannot tree-shake, such as Metro, importOpenAPILinkfrom@orpc/openapi/fetch/linkinstead. That entry contains only the link, so server code stays out of the bundle:AI was used to prepare this change.