Skip to content

fix!: only accept jsonified client factories in createContractJsonifiedUtilsFactory - #2328

Merged
dinwwwh merged 5 commits into
mainfrom
claude/fix-jsonified-utils-factory
Oct 24, 2026
Merged

dinwwwh merged 5 commits into
mainfrom
claude/fix-jsonified-utils-factory

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Problem

Contract Client Factory tells OpenAPI Link users two things:

  • create the client factory with createContractJsonifiedClientFactory from @orpc/openapi;
  • create the utils with createContractJsonifiedUtilsFactory from @orpc/tanstack-query / @orpc/pinia-colada.

Following both fails, because the utils factory only accepted a plain ContractClientFactory:

const createClient = createContractJsonifiedClientFactory(link, { contractRef })
const createUtils = createContractJsonifiedUtilsFactory(createClient, {})
// TS2345: Argument of type 'ContractJsonifiedClientFactory<ClientContext>' is not assignable to parameter of type
// 'ContractClientFactory<ClientContext>'. Type 'JsonifiedClient<RouterContractClient<T, ClientContext>>' is not
// assignable to type 'RouterContractClient<T, ClientContext>'.

Fix

In both @orpc/tanstack-query and @orpc/pinia-colada, createContractJsonifiedUtilsFactory now takes the jsonified client factory:

- clientFactory: ContractClientFactory<TClientContext>,
+ clientFactory: ContractJsonifiedClientFactory<TClientContext>,

The jsonified utils factory always produces utils for JsonifiedClient, so its input is now the client factory that declares jsonified clients. This mirrors createContractUtilsFactory, which takes only a ContractClientFactory:

Client factory Utils factory
createContractClientFactory (@orpc/contract) createContractUtilsFactory
createContractJsonifiedClientFactory (@orpc/openapi) createContractJsonifiedUtilsFactory

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

createContractJsonifiedClientFactory is createContractClientFactory(...) cast to ContractJsonifiedClientFactory, so there is no runtime change. Neither factory type is assignable to the other, so the restriction is enforced in both directions.

Docs:

  • createContractJsonifiedUtilsFactory gets a JSDoc block with a @see link and the same meta.path note as createContractUtilsFactory.
  • createContractJsonifiedClientFactory (@orpc/openapi) gets a JSDoc mirroring createContractClientFactory's. It had none, and this PR makes it the required input.
  • Docs page: the two warnings (TanStack Query and Pinia Colada sections) now say to pass a client factory created by createContractJsonifiedClientFactory.

Breaking change

createContractJsonifiedUtilsFactory no longer accepts a plain ContractClientFactory (types only). Create the client factory with createContractJsonifiedClientFactory from @orpc/openapi instead.

Tests

In both packages, the createContractJsonifiedUtilsFactory cases in contract-utils.test-d.ts now use a ContractJsonifiedClientFactory. 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) and pnpm docs:validate pass.
  • Consumer projects, against packed tarballs of every package from main and from this branch:
    • Setup: one TanStack Query consumer and one Pinia Colada consumer, each installing only @orpc/contract, @orpc/client, @orpc/openapi and the integration. They follow the docs: OpenAPI Link with contractRef, a required { token: string } context, and scoped options.
    • Builds: composite: true with TS 6 (bundler and NodeNext) and TS 5.9.
    • On main: the docs flow fails with the TS2345 above, and a plain client factory passed to the jsonified utils factory is still accepted.
    • On this branch: zero diagnostics, and passing the wrong kind of client factory to either utils factory is a type error. The required context reaches every interceptor and call site, jsonified output types reach queryOptions/fetchQuery/mutationOptions, and the emitted JS is byte-identical to main.
  • Not caused by this PR: re-checking the emitted .d.ts with skipLibCheck: false reports TS2304 for dataTagSymbol when a consumer exports an inferred queryKey. This happens identically on main and with @tanstack/query-core alone.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U63hBfpeESmJiHXznRFzjN

…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
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 24, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Approvability

Verdict: Approved at a77208c

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.

@codspeed

codspeed Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/fix-jsonified-utils-factory (eb50f51) with main (0e59dd2)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@codecov

codecov Bot commented Oct 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new

pkg-pr-new Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

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

@orpc/arktype

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

@orpc/bun

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

@orpc/client

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

@orpc/cloudflare

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

@orpc/contract

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

@orpc/experimental-effect

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

@orpc/evlog

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

@orpc/hibernation

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

@orpc/json-schema

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

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2328

@orpc/mcp

npm i https://pkg.pr.new/@orpc/mcp@2328

@orpc/msw

npm i https://pkg.pr.new/@orpc/msw@2328

@orpc/nest

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

@orpc/next

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

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2328

@orpc/openapi

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

@orpc/opentelemetry

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

@orpc/pinia-colada

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

@orpc/pino

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

@orpc/publisher

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

@orpc/ratelimit

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

@orpc/server

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

@orpc/shared

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

@orpc/swr

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

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: eb50f51

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Union parameter on createContractJsonifiedUtilsFactory — @orpc/tanstack-query and @orpc/pinia-colada now accept ContractClientFactory | ContractJsonifiedClientFactory, with the argument cast to ContractClientFactory before delegating to createContractUtilsFactory. Sound: createContractJsonifiedClientFactory is createContractClientFactory(...) cast to the jsonified type at packages/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.ts files add a jsonified-factory acceptance case (verifying interceptor context and procedure/router utils infer as jsonified) plus a @ts-expect-error case asserting createContractUtilsFactory still rejects a jsonified factory.
  • JSDoc — @remarks + @see backlink 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.

Pullfrog  | View workflow run | Using 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
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 24, 2026 07:08

Dismissing prior approval to re-evaluate 80b69a7

@dinwwwh dinwwwh changed the title fix: accept jsonified client factories in createContractJsonifiedUtilsFactory fix!: only accept jsonified client factories in createContractJsonifiedUtilsFactory Oct 24, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 24, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Narrowed createContractJsonifiedUtilsFactory to a ContractJsonifiedClientFactory — both @orpc/tanstack-query and @orpc/pinia-colada now accept only the jsonified client factory and cast it to ContractClientFactory before 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-error cases assert createContractUtilsFactory rejects a jsonified factory and createContractJsonifiedUtilsFactory rejects a plain one.
  • JSDoc added — createContractJsonifiedUtilsFactory gains a doc block with an @see backlink 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.

Pullfrog  | View workflow run | Using 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
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 24, 2026 07:19

Dismissing prior approval to re-evaluate b1e7314

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 24, 2026
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
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 24, 2026 07:51

Dismissing prior approval to re-evaluate a77208c

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — one minor suggestion below.

Reviewed changes

  • Dropped the factory-rejection type tests — contract-utils.test-d.ts in both @orpc/tanstack-query and @orpc/pinia-colada no longer assert that createContractUtilsFactory rejects a ContractJsonifiedClientFactory or that createContractJsonifiedUtilsFactory rejects a plain ContractClientFactory. 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.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit b3945c9 into main Oct 24, 2026
13 checks passed
dinwwwh added a commit that referenced this pull request Oct 24, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants