Repository navigation
fix!: only accept jsonified client factories in createContractJsonifiedUtilsFactory - #2328
Conversation
…sFactory `createContractJsonifiedUtilsFactory` in @orpc/tanstack-query and @orpc/pinia-colada only accepted a `ContractClientFactory`, so passing the factory from @orpc/openapi's `createContractJsonifiedClientFactory` (which the Contract Client Factory docs use with OpenAPI Link) failed with TS2345. Accept either factory; both are the same function at runtime and only declare different client types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | eb50f51 | Commit Preview URL Branch Preview URL |
Oct 24 2026, 08:25 AM |
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused TypeScript API correction that aligns jsonified utility factories with jsonified client factories and adds coverage for both valid and invalid combinations. The implementation and emitted runtime behavior remain unchanged; documentation is updated to reflect the corrected usage. You can add or adjust custom eligibility rules. Learn more. |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-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: |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Union parameter on
createContractJsonifiedUtilsFactory—@orpc/tanstack-queryand@orpc/pinia-coladanow acceptContractClientFactory | ContractJsonifiedClientFactory, with the argument cast toContractClientFactorybefore delegating tocreateContractUtilsFactory. Sound:createContractJsonifiedClientFactoryiscreateContractClientFactory(...)cast to the jsonified type atpackages/openapi/src/client-factory.ts:29, so both are the identical function at runtime and the cast is a no-op. - Type tests — both
contract-utils.test-d.tsfiles add a jsonified-factory acceptance case (verifying interceptorcontextand procedure/router utils infer as jsonified) plus a@ts-expect-errorcase assertingcreateContractUtilsFactorystill rejects a jsonified factory. - JSDoc —
@remarks+@seebacklink added to both functions, matching the plain factory.
I re-ran pnpm --filter @orpc/tanstack-query type:check, pnpm --filter @orpc/pinia-colada type:check, and root pnpm exec tsc --noEmit (which includes */*/src/**/*.test-d.ts) — all pass, confirming the new assertions including the @ts-expect-error.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…edUtilsFactory The jsonified utils factory always produces utils for `JsonifiedClient`, so it now takes only a `ContractJsonifiedClientFactory` (from `createContractJsonifiedClientFactory`), mirroring `createContractUtilsFactory`, which takes only a `ContractClientFactory`. Passing a plain client factory now fails to type-check instead of being silently re-typed as jsonified. The Contract Client Factory docs now say which client factory to pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN
Dismissing prior approval to re-evaluate 80b69a7
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Narrowed
createContractJsonifiedUtilsFactoryto aContractJsonifiedClientFactory— both@orpc/tanstack-queryand@orpc/pinia-coladanow accept only the jsonified client factory and cast it toContractClientFactorybefore delegating, replacing the earlier union parameter so a mismatched factory is a type error in both directions (documented as a breaking change). - Type tests updated — the jsonified-utils cases now build the factory as
ContractJsonifiedClientFactory, and new@ts-expect-errorcases assertcreateContractUtilsFactoryrejects a jsonified factory andcreateContractJsonifiedUtilsFactoryrejects a plain one. - JSDoc added —
createContractJsonifiedUtilsFactorygains a doc block with an@seebacklink in both packages. - Docs updated — the TanStack Query and Pinia Colada warnings now tell users to pass a factory created by
createContractJsonifiedClientFactory.
I re-ran pnpm --filter @orpc/tanstack-query type:check, pnpm --filter @orpc/pinia-colada type:check, root pnpm exec tsc (which covers the *.test-d.ts assertions, including both @ts-expect-error cases), the two contract-utils.test.ts files, and node scripts/verify-docs-jsdoc.ts — all green.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Hoist the plain and jsonified client factory fixtures to module scope so every case uses the same two, and drop an inline comment that restated the cast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN
Dismissing prior approval to re-evaluate b1e7314
Add a JSDoc to createContractJsonifiedClientFactory mirroring createContractClientFactory's, and the meta.path note to createContractJsonifiedUtilsFactory's JSDoc, matching its plain sibling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN
Dismissing prior approval to re-evaluate a77208c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion below.
Reviewed changes
- Dropped the factory-rejection type tests —
contract-utils.test-d.tsin both@orpc/tanstack-queryand@orpc/pinia-coladano longer assert thatcreateContractUtilsFactoryrejects aContractJsonifiedClientFactoryor thatcreateContractJsonifiedUtilsFactoryrejects a plainContractClientFactory. The fixture hoisting and positive jsonified cases from the earlier commits are unchanged.
ℹ️ The PR's factory-rejection guarantee is no longer covered by any test
Commit eb50f514 removed both @ts-expect-error rejection cases, leaving only the positive path. I re-ran root tsc against a scratch *.test-d.ts containing those exact two assertions (with a control run confirming an unused directive is still reported) and both hold with the current compiler, so they were not stale. Since the narrowed signature is the entire point of this fix!, dropping the negative tests means nothing in the repo would fail if the parameter were later widened back to a union or any.
Technical details
# Restore rejection coverage for `createContractJsonifiedUtilsFactory`
## Affected sites
- `packages/tanstack-query/src/contract-utils.test-d.ts` — `describe('createContractJsonifiedUtilsFactory')` block now contains only its positive cases.
- `packages/pinia-colada/src/contract-utils.test-d.ts` — same.
- Guarded signature: `packages/tanstack-query/src/contract-utils.ts:73` and `packages/pinia-colada/src/contract-utils.ts:73` (`clientFactory: ContractJsonifiedClientFactory<TClientContext>`).
## Required outcome
- At minimum, an assertion that a plain `ContractClientFactory` is rejected by `createContractJsonifiedUtilsFactory` (the behavior this PR introduces).
- Preferably also the mirror assertion that a `ContractJsonifiedClientFactory` is rejected by `createContractUtilsFactory`.
## Suggested approach (optional)
Re-add the two `it(...)` blocks with a `// @ts-expect-error` directive, reusing the file-scope `clientFactory` / `jsonifiedClientFactory` fixtures that already exist. If the removal was deliberate to avoid locking in mutual non-assignability, a note in the PR description explaining the intent would settle it.deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Removes internal-only helpers from the package entry points, so they can change freely after v2 is stable. Each removed symbol was checked to have no use in another workspace package, the docs, playgrounds, `tests/` or `benches/`. ## Removed exports (36) | Entry point | Removed | | --- | --- | | `@orpc/server` | `augmentRouter`, `AugmentRouterOptions` (type), `augmentImplementedRouter`, `AugmentImplementedRouterOptions` (type), `createRouterImplementer`, `decorateMiddleware`, `createGuardedProcedureLazy`, `createContractProcedure`, `withHiddenRouterContract`, `getHiddenRouterContract`, `cloneORPCError`, `reconcileORPCError` | | `@orpc/server/standard` | `CompositeStandardHandlerPlugin`, `TracingHandlerPlugin` | | `@orpc/client` | `resolveFriendlyClientOptions` | | `@orpc/client/standard` | `CompositeStandardLinkPlugin` | | `@orpc/contract` | `augmentContractRouter`, `HIDDEN_META_PLUGINS_SYMBOL`, `getAsyncIteratorObjectSchemaDetails`, `AsyncIteratorObjectSchemaDetails` (type) | | `@orpc/openapi` | `isBodylessMethod`, `DEFAULT_OPENAPI_SUCCESS_DESCRIPTION`, `DEFAULT_OPENAPI_INPUT_STRUCTURE`, `DEFAULT_OPENAPI_OUTPUT_STRUCTURE` | | `@orpc/openapi/standard` | `serializeHeaders` | | `@orpc/tanstack-query` | `generateOperationKey`, `isProcedureUtilsOptions`, `CompositeRouterUtilsPlugin`, `serializableStreamedQuery` | | `@orpc/pinia-colada` | `generateOperationKey`, `isProcedureUtilsOptions`, `CompositeRouterUtilsPlugin`, `serializableStreamedQuery`, `liveQuery` | | `@orpc/swr` | `generateOperationKey`, `isSubsetOf` | `cloneORPCError` and `reconcileORPCError` are only dropped from `@orpc/server`'s re-exports. Other packages import them from `@orpc/client` and `@orpc/contract`, so they stay exported there. Nothing new is exported, and no export changed between value and type-only. `@orpc/shared` is unchanged. ## How it's done - Modules that mix public and internal symbols are now re-exported with explicit name lists in `index.ts` instead of `export *`. A new export added to one of these modules has to be added to its `index.ts` list to become public: - `@orpc/server`: implementer-router, middleware-decorated, procedure-utils, router-utils - `@orpc/server/standard`, `@orpc/client/standard`: plugin - `@orpc/client`: utils - `@orpc/contract`: router-utils, schema-built-in - `@orpc/openapi`: constants, utils - `@orpc/tanstack-query`, `@orpc/pinia-colada`: key, plugin, procedure-utils, shared-utils, stream-query - `@orpc/swr`: key, procedure-utils, shared-utils - `@orpc/msw`: http-procedure-utils - These modules are no longer re-exported at all: `router-hidden` (server), `utils` (`@orpc/openapi/standard` and `@orpc/swr`), `live-query` (pinia-colada). - `TracingHandlerPlugin`, `HIDDEN_META_PLUGINS_SYMBOL`, `AugmentRouterOptions` and `AugmentImplementedRouterOptions` are only used in their own file, so they lost the `export` keyword there. - The OpenAPI matcher test builds its contract-first router with `implement(contract).router()` instead of importing `withHiddenRouterContract`. - The `@orpc/effect` index test no longer asserts that internal helpers are not exported. ### Kept on purpose - Adapter building blocks: `StandardHandler`, `RPCMatcher`, `createStandardPeerRequestHandler`, `WebSocketHandler`, `WebSocketLinkTransport`, … - The `@orpc/json-schema` utilities and the meta readers (`getMcpToolMeta`, `getTanstackQueryMeta`, …). - `mergeProcedureUtilsOptions`, for `RouterUtilsPlugin` authors. - Unique-symbol context keys that appear in public types. - Deprecated aliases. - Classes whose type is public, such as `SharedRouterImplementer`, `ProcedureUtils`/`SharedUtils` and `HTTPProcedureUtils`, stay value exports. The bundled `.d.mts` declares them as values even when the source says `export type`, so a type-only export would type-check and then crash at load. ## Breaking changes The 36 symbols above can no longer be imported from the listed entry points. ## Verification - Up to date with `main` (`72feedb`, which includes #2321 and #2328). - Repo checks pass on the merged head: `pnpm lint`, `pnpm type:check` (including `@orpc/nest`), the root `pnpm vitest run`, the `@orpc/cloudflare` and `@orpc/bun` test suites, and `pnpm docs:validate`. - The export diff against `main` is exactly the 36 removals, with nothing added and no export switched between value and type-only. - **Declaration portability**, checked like #2308 by packing every package from this branch and from its base at the time (`23fc494`): - About 90 consumer projects, in 10 groups covering every package, each install only what a real user of that integration would. They use `composite: true` and exercise documented patterns, including values passed between entry points. - Each compiles with TS 6 (bundler and NodeNext) and TS 5.9, and the emitted `.d.ts` is re-checked with `skipLibCheck: false`. - Result: no new diagnostic of any code (TS2345 and TS2742/TS2883 included), and the emitted `.d.ts`/`.js` are byte-identical to that base. - Static checks on the built `.d.mts`, re-run after merging `main` at `b3945c9`: - No type referenced by a package's public declarations became unnameable for a consumer that installs only that package. - No class is declared in more than one `.d.mts` file of a package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN --------- Co-authored-by: Claude <noreply@anthropic.com>

Problem
Contract Client Factory tells OpenAPI Link users two things:
createContractJsonifiedClientFactoryfrom@orpc/openapi;createContractJsonifiedUtilsFactoryfrom@orpc/tanstack-query/@orpc/pinia-colada.Following both fails, because the utils factory only accepted a plain
ContractClientFactory:Fix
In both
@orpc/tanstack-queryand@orpc/pinia-colada,createContractJsonifiedUtilsFactorynow takes the jsonified client factory:The jsonified utils factory always produces utils for
JsonifiedClient, so its input is now the client factory that declares jsonified clients. This mirrorscreateContractUtilsFactory, which takes only aContractClientFactory:createContractClientFactory(@orpc/contract)createContractUtilsFactorycreateContractJsonifiedClientFactory(@orpc/openapi)createContractJsonifiedUtilsFactoryPassing the other kind to either utils factory is now a type error. Previously a plain client factory passed to the jsonified utils factory was silently re-typed as jsonified.
createContractJsonifiedClientFactoryiscreateContractClientFactory(...)cast toContractJsonifiedClientFactory, so there is no runtime change. Neither factory type is assignable to the other, so the restriction is enforced in both directions.Docs:
createContractJsonifiedUtilsFactorygets a JSDoc block with a@seelink and the samemeta.pathnote ascreateContractUtilsFactory.createContractJsonifiedClientFactory(@orpc/openapi) gets a JSDoc mirroringcreateContractClientFactory's. It had none, and this PR makes it the required input.createContractJsonifiedClientFactory.Breaking change
createContractJsonifiedUtilsFactoryno longer accepts a plainContractClientFactory(types only). Create the client factory withcreateContractJsonifiedClientFactoryfrom@orpc/openapiinstead.Tests
In both packages, the
createContractJsonifiedUtilsFactorycases incontract-utils.test-d.tsnow use aContractJsonifiedClientFactory. They cover interceptor input/output/error types and the procedure and router utils types. Before this PR they failed with the TS2345 above.Verification
pnpm lint,pnpm vitest run packages/tanstack-query packages/pinia-colada,pnpm type:check(which includes the playgrounds) andpnpm docs:validatepass.mainand from this branch:@orpc/contract,@orpc/client,@orpc/openapiand the integration. They follow the docs: OpenAPI Link withcontractRef, a required{ token: string }context, and scoped options.composite: truewith TS 6 (bundler and NodeNext) and TS 5.9.main: the docs flow fails with the TS2345 above, and a plain client factory passed to the jsonified utils factory is still accepted.queryOptions/fetchQuery/mutationOptions, and the emitted JS is byte-identical tomain..d.tswithskipLibCheck: falsereports TS2304 fordataTagSymbolwhen a consumer exports an inferredqueryKey. This happens identically onmainand with@tanstack/query-corealone.🤖 Generated with Claude Code
https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN