hensei-web/TANSTACK_QUERY_MIGRATION_CONTINUATION.md
Devin AI a9a03d3aa7 docs: add TanStack Query migration continuation guide
This document provides context for continuing the migration in future PRs:
- Remaining work (Party component mutations, resource class removal)
- Patterns and best practices for infinite queries, debounced search, mutations
- Known issues and file locations
- Reference implementations

Co-Authored-By: Justin Edmund <justin@jedmund.com>
2025-11-29 09:10:38 +00:00

7.9 KiB

TanStack Query Migration - Continuation Guide

This document provides context for continuing the TanStack Query v6 migration in hensei-web.

Migration Status

Completed (PR #441 - merged)

  • Query options factories: party.queries.ts, job.queries.ts, user.queries.ts, search.queries.ts
  • Mutation configurations: party.mutations.ts, grid.mutations.ts, job.mutations.ts
  • SSR utilities: withInitialData, prefetchQuery, prefetchInfiniteQuery
  • Example components: JobSelectionSidebar.svelte, teams/[id]/+page.svelte

Completed (PR #442 - pending merge)

  • JobSkillSelectionSidebar.svelte - Job skill search with infinite scroll
  • SearchContent.svelte - Search modal for weapons/characters/summons
  • [username]/+page.svelte - User profile page with teams/favorites tabs
  • teams/explore/+page.svelte - Public teams listing

Remaining Work

Follow-Up Prompt 5: Party Component Mutations

Priority: High Complexity: Large

The Party.svelte component (1535 lines) needs to be migrated to use TanStack Query mutations instead of direct service calls.

Files to modify:

  • src/lib/components/party/Party.svelte

Current state: Uses PartyService, GridService, ConflictService, and direct partyAdapter calls.

Target state: Use mutation hooks from:

  • src/lib/api/mutations/party.mutations.ts - useUpdateParty, useDeleteParty, useRemixParty, useFavoriteParty, useUnfavoriteParty, useRegeneratePreview
  • src/lib/api/mutations/grid.mutations.ts - useCreateGridWeapon, useUpdateGridWeapon, useDeleteGridWeapon, etc.
  • src/lib/api/mutations/job.mutations.ts - useUpdatePartyJob, useUpdatePartyJobSkills, useRemovePartyJobSkill, useUpdatePartyAccessory

Recommended sub-tasks:

  1. 5a: Party metadata mutations - name, description, visibility using useUpdateParty
  2. 5b: Grid weapon mutations - add/update/delete weapons using grid mutations
  3. 5c: Grid character mutations - add/update/delete characters using grid mutations
  4. 5d: Grid summon mutations - add/update/delete summons using grid mutations
  5. 5e: Job and skill mutations - job selection, skill management using job mutations

Key functions to migrate in Party.svelte:

  • updatePartyDetails() - replace partyService.update() with useUpdateParty().mutate()
  • toggleFavorite() - replace partyService.favorite()/unfavorite() with useFavoriteParty()/useUnfavoriteParty()
  • remixParty() - replace partyService.remix() with useRemixParty()
  • deleteParty() - replace partyService.delete() with useDeleteParty()
  • handleSelectJob() - replace partyAdapter.updateJob() with useUpdatePartyJob()
  • handleSelectJobSkill() - replace partyAdapter.updateJobSkills() with useUpdatePartyJobSkills()
  • Drag-drop operations - replace gridService.moveWeapon/Character/Summon() with appropriate mutations

Follow-Up Prompt 6: Remove Deprecated Resource Classes

Priority: Low Complexity: Small Prerequisite: All components migrated away from resource classes

Files to delete:

  • src/lib/api/adapters/resources/search.resource.svelte.ts
  • src/lib/api/adapters/resources/party.resource.svelte.ts
  • src/lib/api/adapters/resources/job.resource.svelte.ts
  • src/lib/api/adapters/resources/infiniteScroll.resource.svelte.ts

Steps:

  1. Search for any remaining imports: grep -r "from.*resources/" src/
  2. Migrate any remaining usages
  3. Delete the resource files
  4. Update any barrel exports (index.ts files)
  5. Run build to verify no import errors

Current blockers: InfiniteScroll.svelte still imports InfiniteScrollResource type

Patterns and Best Practices

Infinite Query Pattern

import { createInfiniteQuery } from '@tanstack/svelte-query'
import { IsInViewport } from 'runed'

// Create the query with thunk for reactivity
const query = createInfiniteQuery(() => ({
  ...queryOptions.list(filters),
  initialData: serverData ? {
    pages: [{ results: serverData.items, page: 1, totalPages: serverData.totalPages }],
    pageParams: [1]
  } : undefined,
  initialDataUpdatedAt: 0
}))

// Flatten and deduplicate results
const rawResults = $derived(query.data?.pages.flatMap((page) => page.results) ?? [])
const items = $derived(Array.from(new Map(rawResults.map((item) => [item.id, item])).values()))

// Infinite scroll with IsInViewport
let sentinelEl = $state<HTMLElement>()
const inViewport = new IsInViewport(() => sentinelEl, { rootMargin: '200px' })

$effect(() => {
  if (inViewport.current && query.hasNextPage && !query.isFetchingNextPage && !query.isLoading) {
    query.fetchNextPage()
  }
})

Debounced Search Pattern

let searchQuery = $state('')
let debouncedSearchQuery = $state('')
let debounceTimer: ReturnType<typeof setTimeout> | undefined

$effect(() => {
  const query = searchQuery
  if (debounceTimer) clearTimeout(debounceTimer)
  debounceTimer = setTimeout(() => {
    debouncedSearchQuery = query
  }, 300)
  return () => { if (debounceTimer) clearTimeout(debounceTimer) }
})

// Use debouncedSearchQuery in the query, not searchQuery
const query = createInfiniteQuery(() => queryOptions.search(debouncedSearchQuery))

Type Assertions for Conditional Queries

When a query can return different types based on conditions, use type assertions:

const query = createInfiniteQuery(() => {
  if (condition) {
    return queryOptionsA()
  }
  return queryOptionsB() as unknown as ReturnType<typeof queryOptionsA>
})

Mutation Pattern

import { useUpdateParty } from '$lib/api/mutations/party.mutations'

const updatePartyMutation = useUpdateParty()

function handleSave() {
  updatePartyMutation.mutate(
    { partyId, updates },
    {
      onSuccess: () => { /* handle success */ },
      onError: (error) => { /* handle error */ }
    }
  )
}

// Use mutation state for UI
{#if updatePartyMutation.isPending}
  <span>Saving...</span>
{/if}

Known Issues

Pre-existing Build Errors

The build has pre-existing errors unrelated to TanStack Query migration:

  • Cannot find module '$lib/paraglide/server' in hooks.server.ts
  • Cannot find module '$lib/paraglide/runtime' in hooks.ts
  • Cannot find module '$lib/paraglide/messages' in various components

These are paraglide i18n setup issues and should be ignored when checking for migration-related errors.

Duplicate Key Error Fix

When using infinite queries, the API may return duplicate items across pages. Always deduplicate:

const rawResults = $derived(query.data?.pages.flatMap((page) => page.results) ?? [])
const items = $derived(Array.from(new Map(rawResults.map((item) => [item.id, item])).values()))

File Locations

Query Options Factories

  • src/lib/api/queries/party.queries.ts
  • src/lib/api/queries/job.queries.ts
  • src/lib/api/queries/user.queries.ts
  • src/lib/api/queries/search.queries.ts

Mutation Hooks

  • src/lib/api/mutations/party.mutations.ts
  • src/lib/api/mutations/grid.mutations.ts
  • src/lib/api/mutations/job.mutations.ts

SSR Utilities

  • src/lib/query/ssr.ts

Reference Implementations

  • src/lib/components/sidebar/JobSelectionSidebar.svelte - Simple infinite query
  • src/lib/components/sidebar/JobSkillSelectionSidebar.svelte - Infinite query with search
  • src/lib/components/sidebar/SearchContent.svelte - Infinite query with filters and deduplication
  • src/routes/teams/[id]/+page.svelte - SSR with initialData
  • src/routes/[username]/+page.svelte - Conditional queries (teams vs favorites)
  • src/routes/teams/explore/+page.svelte - Simple infinite scroll page

Commands

# Run TypeScript check
pnpm run check

# Run development server
pnpm run dev

# Check for resource class imports
grep -r "from.*resources/" src/

# Check for createInfiniteScrollResource usage
grep -r "createInfiniteScrollResource" src/

Branch Information

  • Base branch: svelte-main
  • PR #442 branch: devin/1764405731-tanstack-query-migration-phase2