hensei-web/src/lib/query
Justin Edmund f457343e26
Complete TanStack Query v6 migration (#445)
## Overview

Complete migration from service layer to TanStack Query v6 with
mutations, queries, and automatic cache management.

## What Was Completed

### Phase 1: Entity Queries & Database Pages 
- Created `entity.queries.ts` with query options for weapons,
characters, and summons
- Migrated all database detail pages to use TanStack Query with
`withInitialData()` pattern
- SSR with client-side hydration working correctly

### Phase 2: Server Load Cleanup 
- Removed PartyService dependency from teams detail server load
- Server loads now use adapters directly instead of service layer
- Cleaner separation of concerns

### Phase 3: Party.svelte Refactoring 
- Removed all PartyService, GridService, and ConflictService
dependencies
- Migrated to TanStack Query mutations for all operations:
  - Grid operations: create, update, delete, swap, move
  - Party operations: update, delete, remix, favorite, unfavorite
- Added swap/move mutations for drag-and-drop operations
- Automatic cache invalidation and query refetching

### Phase 4: Service Layer Removal 
- Deleted all service layer files (~1,345 lines removed):
  - `party.service.ts` (620 lines)
  - `grid.service.ts` (450 lines)
  - `conflict.service.ts` (120 lines)
  - `gridOperations.ts` (unused utility)
- Deleted empty `services/` directory
- Created utility functions for cross-cutting concerns:
  - `localId.ts`: Anonymous user local ID management
  - `editKeys.ts`: Edit key management for anonymous editing
  - `party-context.ts`: Extracted PartyContext type

### Phase 5: /teams/new Migration 
- Migrated party creation wizard to use TanStack Query mutations
- Replaced all direct adapter calls with mutations (7 locations)
- Maintains existing behavior and flow
- Automatic cache invalidation for newly created parties

## Benefits

### Performance
- Automatic request deduplication
- Better cache utilization across pages
- Background refetching for fresh data
- Optimistic updates for instant UI feedback

### Developer Experience
- Single source of truth for data fetching
- Consistent patterns across entire app
- Query devtools for debugging
- Less boilerplate code

### Code Quality
- ~1,088 net lines removed
- Simpler mental model (no service layer)
- Better TypeScript inference
- Easier to test

### Architecture
- **100% TanStack Query coverage** - no service layer, no direct adapter
calls
- Clear separation: UI ← Queries/Mutations ← Adapters ← API
- Automatic cache management
- Consistent mutation patterns everywhere

## Testing

All features verified:
- Party creation (anonymous & authenticated)
- Grid operations (add, remove, update, swap, move)
- Party operations (update, delete, remix, favorite)
- Cache invalidation across tabs
- Error handling and rollback
- SSR with hydration

## Files Modified

### Created (3)
- `src/lib/types/party-context.ts`
- `src/lib/utils/editKeys.ts`
- `src/lib/utils/localId.ts`

### Deleted (4)
- `src/lib/services/party.service.ts`
- `src/lib/services/grid.service.ts`
- `src/lib/services/conflict.service.ts`
- `src/lib/utils/gridOperations.ts`

### Modified (13)
- `src/lib/api/mutations/grid.mutations.ts`
- `src/lib/components/grids/CharacterGrid.svelte`
- `src/lib/components/grids/SummonGrid.svelte`
- `src/lib/components/grids/WeaponGrid.svelte`
- `src/lib/components/party/Party.svelte`
- `src/routes/teams/new/+page.svelte`
- Database entity pages (characters, weapons, summons)
- Other supporting files

## Migration Complete

This PR completes the TanStack Query migration. The entire application
now uses TanStack Query v6 for all data fetching and mutations, with
zero remaining service layer code.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-29 22:32:15 -08:00
..
cacheHelpers.ts Complete TanStack Query v6 migration (#445) 2025-11-29 22:32:15 -08:00
keys.ts add tanstack query with infinite scroll support 2025-11-28 11:00:57 -08:00
MIGRATION.md feat: add TanStack Query v6 integration with SSR support (#441) 2025-11-29 00:36:59 -08:00
queryClient.ts add tanstack query with infinite scroll support 2025-11-28 11:00:57 -08:00
README.md feat: add TanStack Query v6 integration with SSR support (#441) 2025-11-29 00:36:59 -08:00
ssr.ts feat: add TanStack Query v6 integration with SSR support (#441) 2025-11-29 00:36:59 -08:00

TanStack Query SSR Integration

This directory contains utilities for integrating TanStack Query v6 with SvelteKit's server-side rendering.

Architecture Overview

The project uses a hybrid approach for SSR:

  1. QueryClient in Layout: The QueryClient is created in +layout.ts and passed to +layout.svelte via the load function. This enables prefetching in child page load functions.

  2. Server Data with initialData: Pages that use +page.server.ts can pass server-fetched data as initialData to TanStack Query using the withInitialData() helper.

  3. Prefetching in +page.ts: Pages that use +page.ts (universal load functions) can use prefetchQuery() to populate the QueryClient cache before rendering.

Usage Examples

Pattern 1: Using Server Data with initialData

For pages that already fetch data in +page.server.ts:

<!-- +page.svelte -->
<script lang="ts">
  import { createQuery } from '@tanstack/svelte-query'
  import { partyQueries } from '$lib/api/queries/party.queries'
  import { withInitialData } from '$lib/query/ssr'
  import type { PageData } from './$types'

  let { data } = $props<{ data: PageData }>()

  // Use server-fetched party as initial data
  // The query won't refetch until the data becomes stale
  const party = createQuery(() => ({
    ...partyQueries.byShortcode(data.party?.shortcode ?? ''),
    ...withInitialData(data.party),
    enabled: !!data.party?.shortcode
  }))
</script>

{#if $party.data}
  <h1>{$party.data.name}</h1>
{/if}

Pattern 2: Prefetching in Universal Load Functions

For pages that can use +page.ts (not server-only):

// +page.ts
import type { PageLoad } from './$types'
import { prefetchQuery } from '$lib/query/ssr'
import { partyQueries } from '$lib/api/queries/party.queries'

export const load: PageLoad = async ({ parent, params }) => {
  const { queryClient } = await parent()

  // Prefetch party data into the cache
  await prefetchQuery(queryClient, partyQueries.byShortcode(params.id))

  // No need to return data - it's already in the QueryClient cache
  return { shortcode: params.id }
}
<!-- +page.svelte -->
<script lang="ts">
  import { createQuery } from '@tanstack/svelte-query'
  import { partyQueries } from '$lib/api/queries/party.queries'
  import type { PageData } from './$types'

  let { data } = $props<{ data: PageData }>()

  // Data is already in cache from prefetch - no loading state on initial render
  const party = createQuery(() => partyQueries.byShortcode(data.shortcode))
</script>

{#if $party.data}
  <h1>{$party.data.name}</h1>
{/if}

Pattern 3: Infinite Queries with Prefetching

For paginated data:

// +page.ts
import type { PageLoad } from './$types'
import { prefetchInfiniteQuery } from '$lib/query/ssr'
import { partyQueries } from '$lib/api/queries/party.queries'

export const load: PageLoad = async ({ parent }) => {
  const { queryClient } = await parent()

  // Prefetch first page of parties
  await prefetchInfiniteQuery(queryClient, partyQueries.list())
}

Migration Guide

From Server-Only to TanStack Query

  1. Keep existing +page.server.ts - No changes needed to server load functions
  2. Add TanStack Query to component - Use createQuery with withInitialData
  3. Benefit from caching - Subsequent navigations use cached data

From Custom Resources to TanStack Query

  1. Replace resource imports with query/mutation imports
  2. Use createQuery instead of resource state
  3. Use mutations for CRUD operations with automatic cache invalidation

Files

  • queryClient.ts - QueryClient factory (legacy, kept for reference)
  • ssr.ts - SSR utilities (withInitialData, prefetchQuery, etc.)