Skip to content

refactor: consolidate shared mutation option adapters - #158

Merged
mbret merged 1 commit into
mainfrom
chore/consolidation-2026-07-27
Jul 27, 2026
Merged

refactor: consolidate shared mutation option adapters#158
mbret merged 1 commit into
mainfrom
chore/consolidation-2026-07-27

Conversation

@mbret

@mbret mbret commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Consolidation: shared mutation option adapters

What was duplicated

The three mutation hooks in src/lib/queries/ carried copies of the same two pieces of logic:

  1. mutationFn source resolution — the typeof mutationFn === "function" ? mutationFn(variables) : mutationFn branch that resolves the public option (an Observable or a function returning one) to its source Observable. Duplicated verbatim, 3×:

    • src/lib/queries/useMutation$.ts
    • src/lib/queries/useSwitchMutation$.ts
    • src/lib/queries/useConcatMutation$.ts
  2. Callback envelope adaptationuseSwitchMutation$ and useConcatMutation$ both run their inner mutation with the user variables wrapped in an envelope ({ variables, abort } / { variables, ready$ }), and both reimplemented the same four wrappers that unwrap { variables } before forwarding to the user-facing onMutate/onSuccess/onError/onSettled (including the same conditional onMutate handling). Duplicated 2×:

    • src/lib/queries/useSwitchMutation$.ts
    • src/lib/queries/useConcatMutation$.ts

What it became

A new internal module src/lib/queries/mutationOptions.ts (deliberately not re-exported from src/index.ts, so no public API change — same placement pattern as the existing shared createObservableQueryFn.ts) exporting:

  • resolveMutationFnSource(mutationFn, variables) — used by all three hooks.
  • adaptCallbacksToWrappedVariables(options) — used by both wrapper hooks; its return type keys are required so spreading it after ...options overrides (rather than unions with) the raw-variables callbacks.

Why they are truly the same concept

All three hooks implement the same public UseMutation$Options.mutationFn contract, and both wrapper hooks implement the same "inner mutation runs with enveloped variables, user callbacks see raw variables" bridge. A fix to either behavior (e.g. the unwrap logic or a new callback) previously had to be applied in two or three places in lockstep.

LOC / size delta

  • The three hooks: −30 lines (15 insertions, 45 deletions).
  • New shared module: +60 lines, of which roughly half is doc comments and the type plumbing that keeps the call-site spreads sound; the duplicated logic now exists once.
  • Built bundle: 20.00 kB → 19.20 kB (dist/index.cjs), objective confirmation that duplicated code was removed.

Behavior preservation

No behavior change: same resolution branch, same conditional onMutate wrapping, same optional-chaining semantics for the other callbacks, same object-spread ordering (explicit mutationFn and adapted callbacks override the raw options).

Gates (baseline was fully green; identical after the change):

  • npm run check (biome) ✅
  • npm run build (tsc + vite) ✅
  • npm run test:ci — 23 files, 134/134 tests ✅

Reviewed and skipped (for future runs)

  • QueriesOptions$ / QueriesResults$ recursive types in useQueries$.ts: near-identical shape, but they deliberately mirror TanStack's upstream QueriesOptions/QueriesResults pair — merging them would obscure the correspondence.
  • createLocalStorageAdapter / createLocalforageAdapter: both JSON-wrap a backing store, but the localforage adapter's no-op removeItem/clear look like divergent behavior, not a copy — unifying would change semantics or need mode flags.
  • Test-file scaffolding duplication (jscpd flags ~35 clones in *.test.tsx): intentional per-test explicitness; low value to consolidate.

Generated by Claude Code

The three mutation hooks carried copies of the same two pieces of logic:

- resolving the public `mutationFn` option (an Observable or a function
  returning one) to its source Observable — duplicated verbatim in
  useMutation$, useSwitchMutation$ and useConcatMutation$.
- adapting the user-facing onMutate/onSuccess/onError/onSettled callbacks
  to the wrapped `{ variables, ... }` envelope the inner mutation runs
  with — duplicated between useSwitchMutation$ and useConcatMutation$.

Extract both into src/lib/queries/mutationOptions.ts (internal, not
re-exported from the package index) and point the three hooks at it.

Behavior-preserving: same resolution branch, same conditional onMutate
wrapping, same optional-chaining for the other callbacks. All 134 tests
pass and the built bundle shrinks from 20.00 kB to 19.20 kB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiNQdVnmenWmgf24TfCV9b
@mbret
mbret merged commit 60a083b into main Jul 27, 2026
2 checks passed
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