Skip to content

Conversation

@arnoud-dv
Copy link
Collaborator

@arnoud-dv arnoud-dv commented Oct 23, 2025

🎯 Changes

βœ… Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

πŸš€ Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

Release Notes

  • Refactor
    • Renamed injectMutationState parameter from injectMutationStateFn to mutationStateFn for improved API consistency.
    • Refactored internal mutation state management with optimized signal handling patterns, streamlined subscription model, and enhanced state deduplication for improved reliability.

@changeset-bot
Copy link

changeset-bot bot commented Oct 23, 2025

πŸ¦‹ Changeset detected

Latest commit: eddedf7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@tanstack/angular-query-experimental Patch
@tanstack/angular-query-persist-client Patch

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Walkthrough

The inject-mutation-state implementation undergoes a significant refactoring to adopt a linked-state signal pattern, replacing multiple intermediate signals with a single unified approach. The public API parameter is renamed from injectMutationStateFn to mutationStateFn. Related changesets are updated to reflect this architectural change.

Changes

Cohort / File(s) Summary
Core State Management Refactor
packages/angular-query-experimental/src/inject-mutation-state.ts
Parameter renamed from injectMutationStateFn to mutationStateFn; multiple state signals consolidated into single linkedStateSignal; subscription model switched from batched notifier/subscriber logic to direct mutationCache.subscribe(); import statements consolidated; untracked usage introduced for zone-aware updates; return value now uses computed signal with double dereference.
Changeset Updates
.changeset/inject-is-mutating-readonly.md, .changeset/tidy-parents-send.md
Deleted patch entry for injectIsMutating readonly behavior; added new patch documentation for injectMutationState refactor adopting signal sandwich pattern.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Code
    participant Inject as injectMutationState()
    participant Cache as mutationCache
    participant Signal as linkedStateSignal

    rect rgb(200, 220, 255)
    Note over Inject: Initialization
    Inject->>Signal: Initialize from getResult()
    end

    rect rgb(220, 240, 220)
    Note over Cache,Signal: State Updates
    Cache->>Inject: mutation event
    Inject->>Signal: updateMutationState(untracked)
    Signal->>Signal: replaceEqualDeep deduplicate
    end

    rect rgb(240, 240, 200)
    Note over Inject,User: Return & Subscribe
    Inject->>User: computed(linkedStateSignal()())
    User->>User: Signal subscription
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The refactor introduces a new architectural pattern (linked-state signal) with meaningful logic changes to state initialization, subscription registration, and derived state computation. While confined to a single file, the conceptual shift from multi-signal to unified signal-sandwich pattern and the introduction of untracked zone handling require careful verification of correctness and semantics.

Poem

🐰 A signal sandwich, stacked with care,
No more three signals floating in the air!
One linked state to rule them all,
Deduplicated, standing tall,
The mutation flows through untracked zones,
Angular Query's best-dressed bones! πŸ₯•

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description follows the required template structure with the 🎯 Changes, βœ… Checklist, and πŸš€ Release Impact sections present. The checklist items are properly completed, indicating the contributor followed the contributing guide, tested locally, and generated an appropriate changeset. However, the critical 🎯 Changes section is completely emptyβ€”it contains only the template comment placeholder without any actual description of the changes made or their motivation, leaving reviewers without the author's explanation of what was refactored and why. Fill in the 🎯 Changes section with a clear description of the changes made in this PR, including an explanation of how the signal sandwich pattern improves the injectMutationState implementation and any relevant motivation for this refactoring. This section should explain the key modifications such as the parameter rename, the state management refactor with signals, and any behavioral changes.
βœ… Passed checks (2 passed)
Check name Status Explanation
Title Check βœ… Passed The PR title "refactor(angular-query): injectMutationState use signal sandwich pattern" is specific, clear, and directly reflects the main change in the pull request. The title uses conventional commit format and accurately summarizes that the primary change is refactoring injectMutationState to adopt a signal sandwich pattern, which is confirmed by the raw_summary showing state management refactoring with signal changes, parameter renaming, and subscription model updates. The title is concise, meaningful, and provides sufficient clarity for scanning repository history.
Docstring Coverage βœ… Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • 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.

@nx-cloud
Copy link

nx-cloud bot commented Oct 23, 2025

View your CI Pipeline Execution β†— for commit eddedf7

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... βœ… Succeeded 2m 6s View β†—
nx run-many --target=build --exclude=examples/*... βœ… Succeeded 8s View β†—

☁️ Nx Cloud last updated this comment at 2025-10-23 20:16:39 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 23, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9808

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9808

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: eddedf7

@codecov
Copy link

codecov bot commented Oct 23, 2025

Codecov Report

βœ… All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@907087f). Learn more about missing BASE report.
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #9808   +/-   ##
=======================================
  Coverage        ?   94.03%           
=======================================
  Files           ?       21           
  Lines           ?      419           
  Branches        ?       95           
=======================================
  Hits            ?      394           
  Misses          ?       24           
  Partials        ?        1           
Components Coverage Ξ”
@tanstack/angular-query-experimental 93.75% <100.00%> (?)
@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 βˆ… <ΓΈ> (?)
πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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: 0

🧹 Nitpick comments (2)
packages/angular-query-experimental/src/inject-mutation-state.ts (2)

53-53: Improve parameter documentation.

The documentation for mutationStateFn is incomplete (ends with a dash). Consider adding a description such as:

- * @param mutationStateFn -
+ * @param mutationStateFn - A function returning mutation state options (filters, select). Reactive: if it reads from signals, the result will reset when those signals change.

This clarifies both the purpose and the reactive behavior of this parameter.


68-78: Signal sandwich pattern is correct but complex.

The computed(() => signal(...)) pattern effectively implements a linked signal for Angular < 19 compatibility. The pattern correctly:

  • Creates a new signal with fresh state when mutationStateFn() dependencies change
  • Maintains a stable signal for updates when dependencies don't change
  • Allows the subscription to update the current signal state

The comment explaining the extra parentheses is helpful, though the pattern remains non-obvious for maintainers unfamiliar with this approach.

Consider adding an example to the comment:

// Example usage:
// const options = signal({ filters: { status: 'pending' } });
// const state = injectMutationState(() => options());
// // state()() returns the current array
// // When options() changes, state resets to fresh derived data
// // When mutations change, state updates reactively
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 6c272bd and 66d80fa.

πŸ“’ Files selected for processing (2)
  • .changeset/inject-mutation-state-signal-sandwich.md (1 hunks)
  • packages/angular-query-experimental/src/inject-mutation-state.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/angular-query-experimental/src/inject-mutation-state.ts (2)
packages/query-core/src/mutation.ts (1)
  • MutationState (26-41)
packages/vue-query/src/useMutationState.ts (1)
  • MutationStateOptions (51-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
πŸ”‡ Additional comments (5)
.changeset/inject-mutation-state-signal-sandwich.md (1)

1-5: Consider whether this should be a minor version instead of patch.

While the internal refactor is non-breaking, the parameter rename from injectMutationStateFn to mutationStateFn (line 58 in inject-mutation-state.ts) changes the public API. Although this is a positional parameter with a default value, the name change could affect:

  • Users relying on parameter names for code clarity
  • Auto-complete and IDE hints
  • Generated documentation

Given this is an experimental package, a patch release may be acceptable, but consider if a minor version bump would better signal the API surface change.

packages/angular-query-experimental/src/inject-mutation-state.ts (4)

9-11: LGTM: Import changes align with the refactor.

The addition of untracked and explicit import of replaceEqualDeep support the new signal sandwich pattern and deduplication logic.


80-88: Update flow is correct; consider caching implications.

The updateMutationState function correctly:

  • Runs inside Angular zone for change detection
  • Uses untracked to prevent accidental dependency creation (defensive, as there's no reactive context here)
  • Updates the current signal with deduplicated results

Minor consideration: mutationStateFn() is called on every mutation cache update (line 84). If this function is expensive or reads from multiple signals, consider the performance implications. Users should ensure their mutationStateFn is lightweight and stable.

The pattern assumes mutationStateFn() is stable and cheap to call. Consider adding a comment documenting this expectation, or verify that this assumption holds in typical usage patterns.


90-94: LGTM: Subscription pattern is correct.

Running the subscription outside Angular zone and explicitly entering the zone in updateMutationState is the correct pattern for optimal change detection. Cleanup is properly registered.


96-96: LGTM: Return value correctly unwraps the signal sandwich.

The computed(() => linkedStateSignal()()) pattern correctly creates a read-only signal that updates when either:

  • The outer computed re-runs (when mutationStateFn() dependencies change)
  • The inner signal updates (when mutations change)

The double-parentheses syntax is unusual but correctly documented in the comment above.

@arnoud-dv arnoud-dv force-pushed the refactor/inject-mutation-state-signal-sandwich branch from 66d80fa to eddedf7 Compare October 23, 2025 20:13
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: 0

🧹 Nitpick comments (3)
packages/angular-query-experimental/src/inject-mutation-state.ts (2)

80-89: Return a readonly array to discourage external mutation.

Consumers can currently mutate the returned array, undermining structural sharing and deduplication. Prefer ReadonlyArray<TResult> throughout and annotate the inner signal accordingly.

Apply this diff:

-): Array<TResult> {
+): ReadonlyArray<TResult> {
   return mutationCache
     .findAll(options.filters)
     .map(
       (mutation): TResult =>
         (options.select ? options.select(mutation) : mutation.state) as TResult,
     )
 }
@@
-export function injectMutationState<TResult = MutationState>(
+export function injectMutationState<TResult = MutationState>(
   mutationStateFn: () => MutationStateOptions<TResult> = () => ({}),
   options?: InjectMutationStateOptions,
-): Signal<Array<TResult>> {
+): Signal<ReadonlyArray<TResult>> {
@@
-  const linkedStateSignal = computed(() =>
-    signal(getResult(mutationCache, mutationStateFn())),
-  )
+  const linkedStateSignal = computed(() =>
+    signal<ReadonlyArray<TResult>>(getResult(mutationCache, mutationStateFn())),
+  )
@@
-  return computed(() => linkedStateSignal()())
+  return computed<ReadonlyArray<TResult>>(() => linkedStateSignal()())

Also applies to: 20-40, 57-61, 76-79, 96-96


51-59: Param rename is doc-only; consider clarifying to avoid perceived API break.

Renaming the parameter to mutationStateFn is non-breaking in TS (positional, not named args). Consider noting this in the changeset to prevent confusion for users scanning release notes.

.changeset/tidy-parents-send.md (1)

5-5: Clarify that the parameter rename is non-breaking.

Add a sentence to state that the injectMutationState parameter rename is documentation-only and does not change the call signature or behavior.

Proposed wording:

  • β€œThis is a refactor only; the injectMutationState call signature is unchanged. The parameter was renamed in docs for clarity; no code changes are required by consumers.”
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 66d80fa and eddedf7.

πŸ“’ Files selected for processing (3)
  • .changeset/inject-is-mutating-readonly.md (0 hunks)
  • .changeset/tidy-parents-send.md (1 hunks)
  • packages/angular-query-experimental/src/inject-mutation-state.ts (3 hunks)
πŸ’€ Files with no reviewable changes (1)
  • .changeset/inject-is-mutating-readonly.md
🧰 Additional context used
🧬 Code graph analysis (1)
packages/angular-query-experimental/src/inject-mutation-state.ts (2)
packages/query-core/src/mutation.ts (1)
  • MutationState (26-41)
packages/vue-query/src/useMutationState.ts (1)
  • MutationStateOptions (51-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
πŸ”‡ Additional comments (2)
packages/angular-query-experimental/src/inject-mutation-state.ts (2)

68-79: Signal sandwich is correctly applied (linked-signal substitute).

Creating a writable signal inside a computed and reading/writing it via result()()/result().update(...) is a solid Angular <19-compatible linked-signal pattern. Nicely done.


90-95: Good zone boundary and teardown.

Subscribing outside Angular and re-entering via ngZone.run(...), plus destroyRef.onDestroy(unsubscribe), prevents change-detection thrash and leaks. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant