Skip to content

feat(clients): add ExtResult support to TanStack Query hooks#2490

Open
genu wants to merge 3 commits intozenstackhq:devfrom
genu:genu/tanstack-client-update
Open

feat(clients): add ExtResult support to TanStack Query hooks#2490
genu wants to merge 3 commits intozenstackhq:devfrom
genu:genu/tanstack-client-update

Conversation

@genu
Copy link
Contributor

@genu genu commented Mar 17, 2026

Add type inference helpers to client-helpers enabling computed fields from plugins to be reflected in result types across React, Vue, and Svelte adapters.

Changes:

  • Add InferSchema, InferOptions, and InferExtResult type utilities to client-helpers
  • Update useClientQueries to accept a ClientContract type parameter instead of requiring separate schema + options generics
  • Simplify API by inferring schema, options, and ext-result types automatically
  • Update React, Vue, and Svelte sliced-client tests to use the new API surface

This enables better type inference when using plugins that extend query results with computed fields.

Summary by CodeRabbit

  • New Features

    • Thread ExtResult-style extension through React, Vue, and Svelte clients; improved schema/options inference and re-exports of related helper types.
    • Added public type utilities for ORM write actions, query error/info shapes, and cached query metadata.
  • Tests

    • Simplified sliced-client tests to derive client typings from instantiated clients instead of large inline generics.

Add InferSchema, InferOptions, and InferExtResult type utilities to client-helpers. Update React, Vue, and Svelte adapters to accept ClientContract type parameter, enabling computed fields from plugins to be reflected in result types. Simplify useClientQueries API by inferring schema and options from the client type. Update sliced-client tests to use new API surface.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds InferSchema/InferExtResult/InferOptions and ORM-related types to client-helpers; threads an ExtResult generic through TanStack Query clients (React, Svelte, Vue), re-exports SchemaDef and the new helpers, and adjusts tests to derive client types from instantiated ZenStackClient objects.

Changes

Cohort / File(s) Summary
Type Utilities
packages/clients/client-helpers/src/types.ts
Adds InferSchema, InferExtResult, InferOptions types; exports ORMWriteActions, ORMWriteActionType, QueryError, and QueryInfo — expands public typing for client contracts and ORM/query metadata.
TanStack Query — React
packages/clients/tanstack-query/src/react.ts
Re-exports InferOptions, InferSchema, SchemaDef. Threads ExtResult through ClientHooks, ModelQueryHooks, ModelMutationModelResult, per-model hook generics, and updates useClientQueries/useModelQueries overloads to accept ClientContract or SchemaDef.
TanStack Query — Svelte
packages/clients/tanstack-query/src/svelte/index.svelte.ts
Re-exports InferOptions, InferSchema, SchemaDef. Adds ExtResult generics across client hooks and updates useClientQueries/useModelQueries signatures to infer schema/options/ext-result from ClientContract or SchemaDef.
TanStack Query — Vue
packages/clients/tanstack-query/src/vue.ts
Re-exports InferOptions, InferSchema, SchemaDef. Extends ClientHooks, ModelQueryHooks, ModelMutationModelResult, and per-model hook types with ExtResult; updates useClientQueries to accept ClientContract or SchemaDef.
Tests (typed)
packages/clients/tanstack-query/test/react-sliced-client.test-d.ts, packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts, packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts
Removes GetQueryOptions imports; refactors tests to construct ZenStackClient slices and pass their typeof types (e.g., typeof _slicedOps) to useClientQueries instead of large inline generics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • merge dev to main (v3.4.0) #2414: Adds InferSchema/InferExtResult/InferOptions and ORM-related types in client-helpers that are consumed and re-exported by the tanstack-query client changes.

Poem

🐰 I nibbled types from branch to root,

Threaded ExtResult through every route,
React, Svelte, Vue — I hopped and stitched,
Clients now infer from seeds I ditched,
A crunchy hop, a tidy type delight. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main change: adding ExtResult support to TanStack Query hooks, which aligns with the core objective of enabling type inference for computed fields across adapters.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Contributor

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

Caution

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

⚠️ Outside diff range comments (1)
packages/clients/tanstack-query/src/vue.ts (1)

134-148: ⚠️ Potential issue | 🟠 Major

Preserve client-specific Options in the Vue mutation result type.

Line 142 still builds SimplifiedResult with QueryOptions<Schema> instead of the Options generic. That means mutateAsync resolves the sliced/narrowed type, but the returned mutation object's data is widened back to the generic schema shape.

Suggested fix
 export type ModelMutationModelResult<
     Schema extends SchemaDef,
     Model extends GetModels<Schema>,
     TArgs,
     Array extends boolean = false,
     Options extends QueryOptions<Schema> = QueryOptions<Schema>,
     ExtResult extends ExtResultBase<Schema> = {},
 > = Omit<
-    ModelMutationResult<SimplifiedResult<Schema, Model, TArgs, QueryOptions<Schema>, false, Array, ExtResult>, TArgs>,
+    ModelMutationResult<SimplifiedResult<Schema, Model, TArgs, Options, false, Array, ExtResult>, TArgs>,
     'mutateAsync'
 > & {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/clients/tanstack-query/src/vue.ts` around lines 134 - 148, The
ModelMutationModelResult type currently constructs the Omit<>'d
ModelMutationResult using SimplifiedResult<..., QueryOptions<Schema>, ...>,
which widens the returned data; replace that QueryOptions<Schema> with the
generic Options so the client-specific Options generic is preserved (update the
SimplifiedResult type parameter in the Omit<> instantiation to use Options and
ensure any other occurrences in ModelMutationModelResult that reference
QueryOptions<Schema> are switched to Options, keeping mutateAsync's existing
signature aligned).
🧹 Nitpick comments (1)
packages/clients/tanstack-query/test/react-sliced-client.test-d.ts (1)

8-20: Consider an explicit ExtResult d.ts case here.

All of these Client types come from new ZenStackClient(...), so they only exercise the Schema + Options inference path; the fifth ClientContract generic stays at its default {}. That means this suite validates the new one-generic API shape, but not the computed-field inference this PR is adding. A small fixture with a non-empty ExtResult client type would lock that down, and it can be mirrored in the Vue/Svelte suites.

Also applies to: 31-41, 50-64, 77-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/clients/tanstack-query/test/react-sliced-client.test-d.ts` around
lines 8 - 20, The tests only exercise Schema+Options inference for new
ZenStackClient and omit the computed-field/ExtResult path; add a parallel TS
fixture that explicitly supplies a non-empty ExtResult (the fifth ClientContract
generic) to ZenStackClient so the type-checking covers computed-field inference
— e.g., create a const/alias using new ZenStackClient<Schema, Options, ?, ?, {
myComputedField: string }> or otherwise instantiate the client with an explicit
ExtResult type and assert the expected derived types; apply the same
explicit-ExtResult case to the other occurrences noted and mirror the change in
the Vue and Svelte test suites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/clients/tanstack-query/src/vue.ts`:
- Around line 134-148: The ModelMutationModelResult type currently constructs
the Omit<>'d ModelMutationResult using SimplifiedResult<...,
QueryOptions<Schema>, ...>, which widens the returned data; replace that
QueryOptions<Schema> with the generic Options so the client-specific Options
generic is preserved (update the SimplifiedResult type parameter in the Omit<>
instantiation to use Options and ensure any other occurrences in
ModelMutationModelResult that reference QueryOptions<Schema> are switched to
Options, keeping mutateAsync's existing signature aligned).

---

Nitpick comments:
In `@packages/clients/tanstack-query/test/react-sliced-client.test-d.ts`:
- Around line 8-20: The tests only exercise Schema+Options inference for new
ZenStackClient and omit the computed-field/ExtResult path; add a parallel TS
fixture that explicitly supplies a non-empty ExtResult (the fifth ClientContract
generic) to ZenStackClient so the type-checking covers computed-field inference
— e.g., create a const/alias using new ZenStackClient<Schema, Options, ?, ?, {
myComputedField: string }> or otherwise instantiate the client with an explicit
ExtResult type and assert the expected derived types; apply the same
explicit-ExtResult case to the other occurrences noted and mirror the change in
the Vue and Svelte test suites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1752260e-22ad-4ce9-a63c-81e82e0b0d3e

📥 Commits

Reviewing files that changed from the base of the PR and between 12aeb7b and a06004e.

📒 Files selected for processing (7)
  • packages/clients/client-helpers/src/types.ts
  • packages/clients/tanstack-query/src/react.ts
  • packages/clients/tanstack-query/src/svelte/index.svelte.ts
  • packages/clients/tanstack-query/src/vue.ts
  • packages/clients/tanstack-query/test/react-sliced-client.test-d.ts
  • packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts
  • packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts

Copy link
Member

@ymc9 ymc9 left a comment

Choose a reason for hiding this comment

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

Thanks for adding this @genu . It's a cleaner and better approach. I should have done it this way in the previous release. The only downside is you'll pull in the full typing of the ORM client to the client side (more type-level dependencies), which is probably fine.

Please check my few comments.

Array extends boolean = false,
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
> = Omit<ModelMutationResult<SimplifiedResult<Schema, Model, TArgs, Options, false, Array>, TArgs>, 'mutateAsync'> & {
ExtResult extends ExtResultBase<Schema> = {},
Copy link
Member

Choose a reason for hiding this comment

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

Hooks are not subject to result extension, so maybe we remove this parameter and pass {} down?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done — removed the ExtResult type parameter from ClientHooks, ModelQueryHooks, ModelMutationModelResult, and useModelQueries. Now passing {} directly everywhere. Also cleaned up the unused ExtResultBase and InferExtResult imports.

Copy link
Member

Choose a reason for hiding this comment

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

Hi @genu , my apologies that I didn't make it clear in the previous review comment here. I wanted to say that "custom procedure hooks" are not subject to result extension. Other regular hooks should carry such typing as you did previously ... sorry for the trouble

- Rename `Client` type parameter to `SchemaOrClient` in useClientQueries
- Remove `ExtResult` type parameter from hooks types (ClientHooks,
  ModelQueryHooks, ModelMutationModelResult, useModelQueries) since
  hooks are not subject to result extension; pass `{}` directly instead
- Clean up unused ExtResultBase and InferExtResult imports
@genu genu requested a review from ymc9 March 17, 2026 22:32
Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/clients/tanstack-query/src/vue.ts`:
- Around line 138-145: The type ModelMutationResult currently hardcodes
QueryOptions<Schema> for the base mutation result which loses the generic
Options narrowing; update the base type so it uses the generic Options parameter
instead of QueryOptions<Schema> (i.e., replace the QueryOptions<Schema>
occurrence inside the Omit<SimplifiedResult<...>> with Options) so the overall
type and the mutateAsync signature both consistently reference Options
(affecting ModelMutationResult, SimplifiedResult and ModelMutationOptions
usage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 83deb214-8e61-4520-bcc0-8c5de02a3f63

📥 Commits

Reviewing files that changed from the base of the PR and between a06004e and d6a7bc5.

📒 Files selected for processing (3)
  • packages/clients/tanstack-query/src/react.ts
  • packages/clients/tanstack-query/src/svelte/index.svelte.ts
  • packages/clients/tanstack-query/src/vue.ts

…delResult

The base mutation result type was hardcoding QueryOptions<Schema> instead
of using the Options generic parameter, breaking consistency with React
and Svelte adapters.
Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/clients/tanstack-query/src/vue.ts`:
- Around line 20-21: The Vue adapter is not threading the plugin-computed
extension type (InferExtResult) through its types and exports, causing plugin
fields to be lost; import and re-export InferExtResult alongside
InferOptions/InferSchema, then add an ExtResult generic (bounded by
InferExtResult) into the Vue-specific types that currently hardcode "{}" —
specifically update SimplifiedResult, SimplifiedPlainResult, all Find*Args
generics, and the return type of useClientQueries to accept and propagate
ExtResult so plugin-computed fields flow into query/mutation result types.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1313625a-59ba-4010-95b1-71958f5f1774

📥 Commits

Reviewing files that changed from the base of the PR and between d6a7bc5 and 346799a.

📒 Files selected for processing (1)
  • packages/clients/tanstack-query/src/vue.ts

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