query icon indicating copy to clipboard operation
query copied to clipboard

fix(angular-query): fix injectInfiniteQuery to narrow type

Open mdm317 opened this issue 5 months ago • 5 comments

  • fixes: #8984
  • related: #9016

Cause

When using Omit, a union type is transformed into an object.
This causes BaseQueryNarrowing to behave unexpectedly and fail to narrow types correctly.
I believe this comment is related to the similar issue.


Problem

  • Using Omit

    Currently, BaseQueryNarrowing only works in injectQuery when initialData is not present. Repro example.

  • Without Omit

The narrowing works (see: #9016),
but then isSuccess becomes a strange intersection type:

((this: CreateBaseQueryResult) => this is CreateBaseQueryResult) & (() => false) & ...

As coderrabbitai pointed out, this can cause potential issues.

  • Using DistributiveOmit

As suggested in a previous comment, using DistributiveOmit resolves the narrowing issue. However, it changes the inferred type of data as follows:

const { data } = injectQuery(() => ({
  queryKey: ['key'],
  queryFn: () => ({ wow: true }),
}))

// before
expectTypeOf(data).toEqualTypeOf<Signal<{ wow: boolean | undefined }>>()

// after (with DistributiveOmit)
expectTypeOf(data).toEqualTypeOf<Signal<{ wow: boolean }> | Signal<undefined>>()

I am not very familiar with Angular conventions, so I am not sure whether Signal<{ wow: boolean }> | Signal<undefined> is considered valid.

This Pr

Fix BaseQueryNarrowing to narrow based on the data provided at the call site

I think we can use DistributiveOmit if Signal<{ wow: boolean }> | Signal<undefined> is considered an acceptable type in Angular, without changing the existing BaseQueryNarrowing behavior.
Let me know your thoughts.

Summary by CodeRabbit

  • New Features

    • More precise TypeScript typings for query and infinite query results, improving type narrowing for success, error, and pending states.
    • injectQuery/injectInfiniteQuery now expose broader, more accurate return types for better DX.
  • Examples

    • Infinite query example updated to clearly separate loading, error, and success UI states.
  • Tests

    • Expanded coverage for initialData scenarios and error typing, including status-guarded assertions.
  • Refactor

    • Generalized internal typing to consistently carry state across result types, enhancing status-guard behavior without changing the public API.

mdm317 avatar Sep 14 '25 07:09 mdm317

Walkthrough

Adds state-aware type-narrowing to Angular query result types and updates implementations/tests to expose and validate narrowed types for infinite and regular queries; also adjusts a template conditional structure. No runtime API surface changes beyond internal return-value casts.

Changes

Cohort / File(s) Summary
Type system update
packages/angular-query-experimental/src/types.ts
Added CreateNarrowQueryResult and extended BaseQueryNarrowing to accept TState; updated CreateBaseQueryResult, DefinedCreateQueryResult, CreateInfiniteQueryResult, and DefinedCreateInfiniteQueryResult to include TState and map signals from OmitKeyof<TState, keyof BaseQueryNarrowing, 'safely'>.
inject implementations
packages/angular-query-experimental/src/inject-infinite-query.ts, packages/angular-query-experimental/src/inject-query.ts
Changed implementation return casts: injectInfiniteQuery now casts result to CreateInfiniteQueryResult (via unknown) and injectQuery implementation casts to `CreateQueryResult
Type-narrowing tests
packages/angular-query-experimental/src/__tests__/inject-infinite-query.test-d.ts, packages/angular-query-experimental/src/__tests__/inject-query.test-d.ts
Reorganized and expanded tests to assert isSuccess/isPending/isError narrowing for infinite queries (with and without initialData) and added an isError assertion for injectQuery initialData case. No runtime behavior changes.
Example template change
examples/angular/infinite-query-with-max-pages/src/app/components/example.component.html
Replaced chained conditional (if/else-if/else) with separate @if blocks for loading, error, and success states (decoupling else branches).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Component
  participant injectFn as injectInfiniteQuery / injectQuery
  participant Result as QueryResult (typed)

  Component->>injectFn: call inject*Query(...)
  Note right of injectFn: implementation casts runtime result\nto CreateQueryResult | DefinedCreateQueryResult\nor CreateInfiniteQueryResult
  injectFn-->>Result: return typed wrapper
  Component->>Result: call isSuccess() / isError() / isPending()
  alt isSuccess
    Note right of Result: Type narrows to success-shaped\nCreateNarrowQueryResult → data() is defined
  else isError
    Note right of Result: Type narrows to error-shaped\nCreateNarrowQueryResult → error() is defined
  else isPending
    Note right of Result: Type narrows to pending-shaped\nCreateNarrowQueryResult
  end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • TanStack/query#9270 — Devtools relocation and export refactor (overlaps with type/export surface changes and reorganizations).

Poem

I nibble types in twilight code,
I hop where narrow signals go.
isSuccess—data springs, neat and bright,
isError—error’s clear in sight.
A rabbit cheers for types made right. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning Most edits are focused on type-narrowing for injectInfiniteQuery, however there are a couple of edits that appear outside the linked issue scope: the example template file examples/angular/infinite-query-with-max-pages/src/app/components/example.component.html was refactored, and the injectQuery implementation return cast was broadened, neither of which are explicitly required by issue [#8984]. Please either remove or split non-essential example UI changes into a separate PR and add a brief justification in this PR for the injectQuery return-cast change (or add tests demonstrating it is necessary); run the full type-check and CI after isolating or documenting these edits before merging.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title is concise, specific, and accurately summarizes the primary change — fixing type narrowing for injectInfiniteQuery in the angular-query package — and it aligns with the PR objectives and the provided changeset (type updates, implementation cast, and tests).
Linked Issues Check ✅ Passed Based on the provided changeset, the PR implements the requested fix for the linked issue [#8984] by making BaseQueryNarrowing state-aware (new CreateNarrowQueryResult and TState generics), updating CreateInfiniteQueryResult/related types, adding tests that assert narrowing for injectInfiniteQuery (and an injectQuery test), and adjusting the injectInfiniteQuery implementation to return the new narrowed type, which together provide reproducible coverage of the narrowing behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • [ ] 📝 Generate Docstrings
🧪 Generate unit tests
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment

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.

coderabbitai[bot] avatar Sep 14 '25 07:09 coderabbitai[bot]

View your CI Pipeline Execution ↗ for commit 29a7076a4c584b2870289b0185c935742d1bf182

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 3m 24s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 9s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-21 01:06:29 UTC

nx-cloud[bot] avatar Sep 14 '25 15:09 nx-cloud[bot]

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9653
@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9653
@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9653
@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9653
@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9653
@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9653
@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9653
@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9653
@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9653
@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9653
@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9653
@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9653
@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9653
@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9653
@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9653
@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9653
@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9653
@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9653
@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9653
@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9653

commit: 29a7076

pkg-pr-new[bot] avatar Sep 20 '25 23:09 pkg-pr-new[bot]

Codecov Report

:white_check_mark: All modified and coverable lines are covered by tests. :white_check_mark: Project coverage is 94.13%. Comparing base (7d370b9) to head (29a7076). :warning: Report is 3 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main    #9653       +/-   ##
===========================================
+ Coverage   46.19%   94.13%   +47.93%     
===========================================
  Files         213       21      -192     
  Lines        8453      426     -8027     
  Branches     1909       99     -1810     
===========================================
- Hits         3905      401     -3504     
+ Misses       4105       24     -4081     
+ Partials      443        1      -442     
Components Coverage Δ
@tanstack/angular-query-experimental 93.85% <ø> (-0.23%) :arrow_down:
@tanstack/eslint-plugin-query ∅ <ø> (∅)
@tanstack/query-async-storage-persister ∅ <ø> (∅)
@tanstack/query-broadcast-client-experimental ∅ <ø> (∅)
@tanstack/query-codemods ∅ <ø> (∅)
@tanstack/query-core ∅ <ø> (∅)
@tanstack/query-devtools ∅ <ø> (∅)
@tanstack/query-persist-client-core ∅ <ø> (∅)
@tanstack/query-sync-storage-persister ∅ <ø> (∅)
@tanstack/query-test-utils ∅ <ø> (∅)
@tanstack/react-query ∅ <ø> (∅)
@tanstack/react-query-devtools ∅ <ø> (∅)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client ∅ <ø> (∅)
@tanstack/solid-query ∅ <ø> (∅)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client ∅ <ø> (∅)
@tanstack/svelte-query ∅ <ø> (∅)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client ∅ <ø> (∅)
@tanstack/vue-query ∅ <ø> (∅)
@tanstack/vue-query-devtools ∅ <ø> (∅)
:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

codecov[bot] avatar Sep 21 '25 01:09 codecov[bot]

⚠️ No Changeset found

Latest commit: ecb7fd3d4447f23700d6078310e323b825278ef6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

changeset-bot[bot] avatar Sep 26 '25 09:09 changeset-bot[bot]