feat: add TanStack Query v6 query and mutation modules
- Add party.queries.ts with query options factories for party operations - Add job.queries.ts with query options factories for job/skill operations - Add user.queries.ts with query options factories for user operations - Add party.mutations.ts with mutation configs and cache invalidation - Add grid.mutations.ts with optimistic updates for weapon/character/summon operations - Add job.mutations.ts with mutations for job/skill updates - Add deprecation notices to unused resource classes (search.resource, party.resource) This establishes the foundation for migrating from custom resource classes to TanStack Query v6 with proper cache management and optimistic updates. Co-Authored-By: Justin Edmund <justin@jedmund.com>
This commit is contained in:
parent
53405da7eb
commit
be24a84e19
8 changed files with 1687 additions and 1 deletions
|
|
@ -4,6 +4,27 @@
|
||||||
* Provides reactive state management for party operations with
|
* Provides reactive state management for party operations with
|
||||||
* automatic loading states, error handling, and optimistic updates.
|
* automatic loading states, error handling, and optimistic updates.
|
||||||
*
|
*
|
||||||
|
* @deprecated This resource class is deprecated in favor of TanStack Query.
|
||||||
|
* Use `partyQueries` from `$lib/api/queries/party.queries` and
|
||||||
|
* mutation hooks from `$lib/api/mutations/party.mutations` instead.
|
||||||
|
*
|
||||||
|
* Migration example:
|
||||||
|
* ```typescript
|
||||||
|
* // Before (PartyResource)
|
||||||
|
* const party = createPartyResource()
|
||||||
|
* party.load('ABC123')
|
||||||
|
* party.update({ shortcode: 'ABC123', name: 'New Name' })
|
||||||
|
*
|
||||||
|
* // After (TanStack Query)
|
||||||
|
* import { createQuery } from '@tanstack/svelte-query'
|
||||||
|
* import { partyQueries } from '$lib/api/queries/party.queries'
|
||||||
|
* import { useUpdateParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const party = createQuery(() => partyQueries.byShortcode('ABC123'))
|
||||||
|
* const updateParty = useUpdateParty()
|
||||||
|
* updateParty.mutate({ shortcode: 'ABC123', name: 'New Name' })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* @module adapters/resources/party
|
* @module adapters/resources/party
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,23 @@
|
||||||
* Provides reactive state management for search operations with
|
* Provides reactive state management for search operations with
|
||||||
* automatic loading states, error handling, and debouncing.
|
* automatic loading states, error handling, and debouncing.
|
||||||
*
|
*
|
||||||
|
* @deprecated This resource class is deprecated in favor of TanStack Query.
|
||||||
|
* Use `searchQueries` from `$lib/api/queries/search.queries` with `createInfiniteQuery` instead.
|
||||||
|
*
|
||||||
|
* Migration example:
|
||||||
|
* ```typescript
|
||||||
|
* // Before (SearchResource)
|
||||||
|
* const search = createSearchResource({ debounceMs: 300 })
|
||||||
|
* search.searchWeapons({ query })
|
||||||
|
*
|
||||||
|
* // After (TanStack Query)
|
||||||
|
* import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
|
* import { searchQueries } from '$lib/api/queries/search.queries'
|
||||||
|
*
|
||||||
|
* let debouncedQuery = $state('')
|
||||||
|
* const weapons = createInfiniteQuery(() => searchQueries.weapons(debouncedQuery))
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* @module adapters/resources/search
|
* @module adapters/resources/search
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
568
src/lib/api/mutations/grid.mutations.ts
Normal file
568
src/lib/api/mutations/grid.mutations.ts
Normal file
|
|
@ -0,0 +1,568 @@
|
||||||
|
/**
|
||||||
|
* Grid Mutation Configurations
|
||||||
|
*
|
||||||
|
* Provides mutation configurations for grid item operations (weapons, characters, summons)
|
||||||
|
* with cache invalidation and optimistic updates using TanStack Query v6.
|
||||||
|
*
|
||||||
|
* @module api/mutations/grid
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQueryClient, createMutation } from '@tanstack/svelte-query'
|
||||||
|
import {
|
||||||
|
gridAdapter,
|
||||||
|
type CreateGridWeaponParams,
|
||||||
|
type CreateGridCharacterParams,
|
||||||
|
type CreateGridSummonParams,
|
||||||
|
type UpdateUncapParams,
|
||||||
|
type ResolveConflictParams
|
||||||
|
} from '$lib/api/adapters/grid.adapter'
|
||||||
|
import { partyKeys } from '$lib/api/queries/party.queries'
|
||||||
|
import type { Party, GridWeapon, GridCharacter, GridSummon } from '$lib/types/api/party'
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Weapon Mutations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create grid weapon mutation
|
||||||
|
*
|
||||||
|
* Adds a weapon to a party's grid.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useCreateGridWeapon } from '$lib/api/mutations/grid.mutations'
|
||||||
|
*
|
||||||
|
* const createWeapon = useCreateGridWeapon()
|
||||||
|
*
|
||||||
|
* function handleAddWeapon() {
|
||||||
|
* createWeapon.mutate({
|
||||||
|
* partyId: 'party-uuid',
|
||||||
|
* weaponId: 'weapon-id',
|
||||||
|
* position: 1
|
||||||
|
* })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useCreateGridWeapon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: CreateGridWeaponParams) => gridAdapter.createWeapon(params),
|
||||||
|
onSuccess: (_data, params) => {
|
||||||
|
// Invalidate the party to refetch with new weapon
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(params.partyId) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update grid weapon mutation
|
||||||
|
*
|
||||||
|
* Updates a weapon in a party's grid with optimistic updates.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useUpdateGridWeapon } from '$lib/api/mutations/grid.mutations'
|
||||||
|
*
|
||||||
|
* const updateWeapon = useUpdateGridWeapon()
|
||||||
|
*
|
||||||
|
* function handleUpdateWeapon(id: string, partyShortcode: string) {
|
||||||
|
* updateWeapon.mutate({
|
||||||
|
* id,
|
||||||
|
* partyShortcode,
|
||||||
|
* updates: { element: 2 }
|
||||||
|
* })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useUpdateGridWeapon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ id, updates }: { id: string; partyShortcode: string; updates: Partial<GridWeapon> }) =>
|
||||||
|
gridAdapter.updateWeapon(id, updates),
|
||||||
|
onMutate: async ({ id, partyShortcode, updates }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.weapons) {
|
||||||
|
const updatedWeapons = previousParty.weapons.map((w) =>
|
||||||
|
w.id === id ? { ...w, ...updates } : w
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
weapons: updatedWeapons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete grid weapon mutation
|
||||||
|
*
|
||||||
|
* Removes a weapon from a party's grid.
|
||||||
|
*/
|
||||||
|
export function useDeleteGridWeapon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: { id?: string; partyId: string; partyShortcode: string; position?: number }) =>
|
||||||
|
gridAdapter.deleteWeapon({ id: params.id, partyId: params.partyId, position: params.position }),
|
||||||
|
onMutate: async ({ partyShortcode, id, position }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.weapons) {
|
||||||
|
const updatedWeapons = previousParty.weapons.filter((w) =>
|
||||||
|
id ? w.id !== id : w.position !== position
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
weapons: updatedWeapons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update weapon uncap mutation
|
||||||
|
*
|
||||||
|
* Updates a weapon's uncap level with optimistic updates.
|
||||||
|
*/
|
||||||
|
export function useUpdateWeaponUncap() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: UpdateUncapParams & { partyShortcode: string }) =>
|
||||||
|
gridAdapter.updateWeaponUncap(params),
|
||||||
|
onMutate: async ({ partyShortcode, id, uncapLevel, transcendenceStep }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.weapons) {
|
||||||
|
const updatedWeapons = previousParty.weapons.map((w) =>
|
||||||
|
w.id === id
|
||||||
|
? {
|
||||||
|
...w,
|
||||||
|
uncapLevel,
|
||||||
|
...(transcendenceStep !== undefined && { transcendenceStep })
|
||||||
|
}
|
||||||
|
: w
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
weapons: updatedWeapons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve weapon conflict mutation
|
||||||
|
*
|
||||||
|
* Resolves conflicts when adding a weapon that conflicts with existing weapons.
|
||||||
|
*/
|
||||||
|
export function useResolveWeaponConflict() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: ResolveConflictParams & { partyShortcode: string }) =>
|
||||||
|
gridAdapter.resolveWeaponConflict(params),
|
||||||
|
onSuccess: (_data, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Character Mutations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create grid character mutation
|
||||||
|
*
|
||||||
|
* Adds a character to a party's grid.
|
||||||
|
*/
|
||||||
|
export function useCreateGridCharacter() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: CreateGridCharacterParams) => gridAdapter.createCharacter(params),
|
||||||
|
onSuccess: (_data, params) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(params.partyId) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update grid character mutation
|
||||||
|
*
|
||||||
|
* Updates a character in a party's grid with optimistic updates.
|
||||||
|
*/
|
||||||
|
export function useUpdateGridCharacter() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ id, updates }: { id: string; partyShortcode: string; updates: Partial<GridCharacter> }) =>
|
||||||
|
gridAdapter.updateCharacter(id, updates),
|
||||||
|
onMutate: async ({ id, partyShortcode, updates }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.characters) {
|
||||||
|
const updatedCharacters = previousParty.characters.map((c) =>
|
||||||
|
c.id === id ? { ...c, ...updates } : c
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
characters: updatedCharacters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete grid character mutation
|
||||||
|
*
|
||||||
|
* Removes a character from a party's grid.
|
||||||
|
*/
|
||||||
|
export function useDeleteGridCharacter() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: { id?: string; partyId: string; partyShortcode: string; position?: number }) =>
|
||||||
|
gridAdapter.deleteCharacter({ id: params.id, partyId: params.partyId, position: params.position }),
|
||||||
|
onMutate: async ({ partyShortcode, id, position }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.characters) {
|
||||||
|
const updatedCharacters = previousParty.characters.filter((c) =>
|
||||||
|
id ? c.id !== id : c.position !== position
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
characters: updatedCharacters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update character uncap mutation
|
||||||
|
*
|
||||||
|
* Updates a character's uncap level with optimistic updates.
|
||||||
|
*/
|
||||||
|
export function useUpdateCharacterUncap() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: UpdateUncapParams & { partyShortcode: string }) =>
|
||||||
|
gridAdapter.updateCharacterUncap(params),
|
||||||
|
onMutate: async ({ partyShortcode, id, uncapLevel, transcendenceStep }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.characters) {
|
||||||
|
const updatedCharacters = previousParty.characters.map((c) =>
|
||||||
|
c.id === id
|
||||||
|
? {
|
||||||
|
...c,
|
||||||
|
uncapLevel,
|
||||||
|
...(transcendenceStep !== undefined && { transcendenceStep })
|
||||||
|
}
|
||||||
|
: c
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
characters: updatedCharacters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve character conflict mutation
|
||||||
|
*
|
||||||
|
* Resolves conflicts when adding a character that conflicts with existing characters.
|
||||||
|
*/
|
||||||
|
export function useResolveCharacterConflict() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: ResolveConflictParams & { partyShortcode: string }) =>
|
||||||
|
gridAdapter.resolveCharacterConflict(params),
|
||||||
|
onSuccess: (_data, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Summon Mutations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create grid summon mutation
|
||||||
|
*
|
||||||
|
* Adds a summon to a party's grid.
|
||||||
|
*/
|
||||||
|
export function useCreateGridSummon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: CreateGridSummonParams) => gridAdapter.createSummon(params),
|
||||||
|
onSuccess: (_data, params) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(params.partyId) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update grid summon mutation
|
||||||
|
*
|
||||||
|
* Updates a summon in a party's grid with optimistic updates.
|
||||||
|
*/
|
||||||
|
export function useUpdateGridSummon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ id, updates }: { id: string; partyShortcode: string; updates: Partial<GridSummon> }) =>
|
||||||
|
gridAdapter.updateSummon(id, updates),
|
||||||
|
onMutate: async ({ id, partyShortcode, updates }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.summons) {
|
||||||
|
const updatedSummons = previousParty.summons.map((s) =>
|
||||||
|
s.id === id ? { ...s, ...updates } : s
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
summons: updatedSummons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete grid summon mutation
|
||||||
|
*
|
||||||
|
* Removes a summon from a party's grid.
|
||||||
|
*/
|
||||||
|
export function useDeleteGridSummon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: { id?: string; partyId: string; partyShortcode: string; position?: number }) =>
|
||||||
|
gridAdapter.deleteSummon({ id: params.id, partyId: params.partyId, position: params.position }),
|
||||||
|
onMutate: async ({ partyShortcode, id, position }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.summons) {
|
||||||
|
const updatedSummons = previousParty.summons.filter((s) =>
|
||||||
|
id ? s.id !== id : s.position !== position
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
summons: updatedSummons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update summon uncap mutation
|
||||||
|
*
|
||||||
|
* Updates a summon's uncap level with optimistic updates.
|
||||||
|
*/
|
||||||
|
export function useUpdateSummonUncap() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: UpdateUncapParams & { partyShortcode: string }) =>
|
||||||
|
gridAdapter.updateSummonUncap(params),
|
||||||
|
onMutate: async ({ partyShortcode, id, uncapLevel, transcendenceStep }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.summons) {
|
||||||
|
const updatedSummons = previousParty.summons.map((s) =>
|
||||||
|
s.id === id
|
||||||
|
? {
|
||||||
|
...s,
|
||||||
|
uncapLevel,
|
||||||
|
...(transcendenceStep !== undefined && { transcendenceStep })
|
||||||
|
}
|
||||||
|
: s
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
summons: updatedSummons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update quick summon mutation
|
||||||
|
*
|
||||||
|
* Updates a summon's quick summon setting with optimistic updates.
|
||||||
|
*/
|
||||||
|
export function useUpdateQuickSummon() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: {
|
||||||
|
id?: string
|
||||||
|
partyId: string
|
||||||
|
partyShortcode: string
|
||||||
|
position?: number
|
||||||
|
quickSummon: boolean
|
||||||
|
}) =>
|
||||||
|
gridAdapter.updateQuickSummon({
|
||||||
|
id: params.id,
|
||||||
|
partyId: params.partyId,
|
||||||
|
position: params.position,
|
||||||
|
quickSummon: params.quickSummon
|
||||||
|
}),
|
||||||
|
onMutate: async ({ partyShortcode, id, quickSummon }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(partyShortcode))
|
||||||
|
|
||||||
|
if (previousParty?.summons) {
|
||||||
|
const updatedSummons = previousParty.summons.map((s) =>
|
||||||
|
s.id === id ? { ...s, quickSummon } : s
|
||||||
|
)
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), {
|
||||||
|
...previousParty,
|
||||||
|
summons: updatedSummons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { partyShortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(partyShortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { partyShortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(partyShortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
182
src/lib/api/mutations/job.mutations.ts
Normal file
182
src/lib/api/mutations/job.mutations.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
/**
|
||||||
|
* Job Mutation Configurations
|
||||||
|
*
|
||||||
|
* Provides mutation configurations for job-related operations
|
||||||
|
* with cache invalidation using TanStack Query v6.
|
||||||
|
*
|
||||||
|
* @module api/mutations/job
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQueryClient, createMutation } from '@tanstack/svelte-query'
|
||||||
|
import { partyAdapter } from '$lib/api/adapters/party.adapter'
|
||||||
|
import { partyKeys } from '$lib/api/queries/party.queries'
|
||||||
|
import type { Party } from '$lib/types/api/party'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update party job mutation
|
||||||
|
*
|
||||||
|
* Updates the job for a party with optimistic updates.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useUpdatePartyJob } from '$lib/api/mutations/job.mutations'
|
||||||
|
*
|
||||||
|
* const updateJob = useUpdatePartyJob()
|
||||||
|
*
|
||||||
|
* function handleJobSelect(jobId: string) {
|
||||||
|
* updateJob.mutate({ shortcode: 'abc123', jobId })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useUpdatePartyJob() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ shortcode, jobId }: { shortcode: string; jobId: string }) =>
|
||||||
|
partyAdapter.updateJob(shortcode, jobId),
|
||||||
|
onMutate: async ({ shortcode, jobId }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(shortcode))
|
||||||
|
|
||||||
|
// Optimistically update the job ID
|
||||||
|
// Note: We don't have the full job object here, so we just update the ID
|
||||||
|
// The full job will be fetched when the query is invalidated
|
||||||
|
if (previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), {
|
||||||
|
...previousParty,
|
||||||
|
job: previousParty.job ? { ...previousParty.job, id: jobId } : undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { shortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { shortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update party job skills mutation
|
||||||
|
*
|
||||||
|
* Updates the job skills for a party.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useUpdatePartyJobSkills } from '$lib/api/mutations/job.mutations'
|
||||||
|
*
|
||||||
|
* const updateSkills = useUpdatePartyJobSkills()
|
||||||
|
*
|
||||||
|
* function handleSkillsUpdate(skills: Array<{ id: string; slot: number }>) {
|
||||||
|
* updateSkills.mutate({ shortcode: 'abc123', skills })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useUpdatePartyJobSkills() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({
|
||||||
|
shortcode,
|
||||||
|
skills
|
||||||
|
}: {
|
||||||
|
shortcode: string
|
||||||
|
skills: Array<{ id: string; slot: number }>
|
||||||
|
}) => partyAdapter.updateJobSkills(shortcode, skills),
|
||||||
|
onSuccess: (_data, { shortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove party job skill mutation
|
||||||
|
*
|
||||||
|
* Removes a job skill from a party.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useRemovePartyJobSkill } from '$lib/api/mutations/job.mutations'
|
||||||
|
*
|
||||||
|
* const removeSkill = useRemovePartyJobSkill()
|
||||||
|
*
|
||||||
|
* function handleRemoveSkill(slot: number) {
|
||||||
|
* removeSkill.mutate({ shortcode: 'abc123', slot })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useRemovePartyJobSkill() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ shortcode, slot }: { shortcode: string; slot: number }) =>
|
||||||
|
partyAdapter.removeJobSkill(shortcode, slot),
|
||||||
|
onMutate: async ({ shortcode, slot }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(shortcode))
|
||||||
|
|
||||||
|
// Optimistically remove the skill from the slot
|
||||||
|
if (previousParty?.jobSkills) {
|
||||||
|
const updatedSkills = { ...previousParty.jobSkills }
|
||||||
|
delete updatedSkills[slot]
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), {
|
||||||
|
...previousParty,
|
||||||
|
jobSkills: updatedSkills
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, { shortcode }, context) => {
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, { shortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update party accessory mutation
|
||||||
|
*
|
||||||
|
* Updates the accessory for a party.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useUpdatePartyAccessory } from '$lib/api/mutations/job.mutations'
|
||||||
|
*
|
||||||
|
* const updateAccessory = useUpdatePartyAccessory()
|
||||||
|
*
|
||||||
|
* function handleAccessorySelect(accessoryId: string) {
|
||||||
|
* updateAccessory.mutate({ shortcode: 'abc123', accessoryId })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useUpdatePartyAccessory() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ shortcode, accessoryId }: { shortcode: string; accessoryId: string }) =>
|
||||||
|
partyAdapter.updateAccessory(shortcode, accessoryId),
|
||||||
|
onSuccess: (_data, { shortcode }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
313
src/lib/api/mutations/party.mutations.ts
Normal file
313
src/lib/api/mutations/party.mutations.ts
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
/**
|
||||||
|
* Party Mutation Configurations
|
||||||
|
*
|
||||||
|
* Provides mutation configurations for party CRUD operations
|
||||||
|
* with cache invalidation and optimistic updates using TanStack Query v6.
|
||||||
|
*
|
||||||
|
* @module api/mutations/party
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQueryClient, createMutation } from '@tanstack/svelte-query'
|
||||||
|
import {
|
||||||
|
partyAdapter,
|
||||||
|
type CreatePartyParams,
|
||||||
|
type UpdatePartyParams
|
||||||
|
} from '$lib/api/adapters/party.adapter'
|
||||||
|
import { partyKeys } from '$lib/api/queries/party.queries'
|
||||||
|
import { userKeys } from '$lib/api/queries/user.queries'
|
||||||
|
import type { Party } from '$lib/types/api/party'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create party mutation
|
||||||
|
*
|
||||||
|
* Creates a new party and invalidates relevant caches.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useCreateParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const createParty = useCreateParty()
|
||||||
|
*
|
||||||
|
* function handleCreate() {
|
||||||
|
* createParty.mutate({ name: 'My Party', visibility: 'public' })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useCreateParty() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: CreatePartyParams) => partyAdapter.create(params),
|
||||||
|
onSuccess: (party) => {
|
||||||
|
// Set the new party in cache
|
||||||
|
queryClient.setQueryData(partyKeys.detail(party.shortcode), party)
|
||||||
|
// Invalidate party lists to include the new party
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.lists() })
|
||||||
|
// Invalidate user's party lists
|
||||||
|
queryClient.invalidateQueries({ queryKey: userKeys.all })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update party mutation
|
||||||
|
*
|
||||||
|
* Updates an existing party with optimistic updates.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useUpdateParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const updateParty = useUpdateParty()
|
||||||
|
*
|
||||||
|
* function handleUpdate() {
|
||||||
|
* updateParty.mutate({ shortcode: 'abc123', name: 'Updated Name' })
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useUpdateParty() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (params: UpdatePartyParams) => partyAdapter.update(params),
|
||||||
|
onMutate: async (params) => {
|
||||||
|
// Cancel any outgoing refetches
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(params.shortcode) })
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(params.shortcode))
|
||||||
|
|
||||||
|
// Optimistically update the cache
|
||||||
|
if (previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(params.shortcode), {
|
||||||
|
...previousParty,
|
||||||
|
...params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, params, context) => {
|
||||||
|
// Rollback on error
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(params.shortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, params) => {
|
||||||
|
// Always refetch after error or success
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(params.shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete party mutation
|
||||||
|
*
|
||||||
|
* Deletes a party and removes it from all caches.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useDeleteParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const deleteParty = useDeleteParty()
|
||||||
|
*
|
||||||
|
* function handleDelete(shortcode: string) {
|
||||||
|
* deleteParty.mutate(shortcode)
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useDeleteParty() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (shortcode: string) => partyAdapter.delete(shortcode),
|
||||||
|
onSuccess: (_data, shortcode) => {
|
||||||
|
// Remove the party from cache
|
||||||
|
queryClient.removeQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
// Invalidate party lists
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.lists() })
|
||||||
|
// Invalidate user's party lists
|
||||||
|
queryClient.invalidateQueries({ queryKey: userKeys.all })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remix party mutation
|
||||||
|
*
|
||||||
|
* Creates a copy of an existing party.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useRemixParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const remixParty = useRemixParty()
|
||||||
|
*
|
||||||
|
* function handleRemix(shortcode: string) {
|
||||||
|
* remixParty.mutate(shortcode)
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useRemixParty() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (shortcode: string) => partyAdapter.remix(shortcode),
|
||||||
|
onSuccess: (newParty) => {
|
||||||
|
// Set the new party in cache
|
||||||
|
queryClient.setQueryData(partyKeys.detail(newParty.shortcode), newParty)
|
||||||
|
// Invalidate party lists to include the new party
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.lists() })
|
||||||
|
// Invalidate user's party lists
|
||||||
|
queryClient.invalidateQueries({ queryKey: userKeys.all })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Favorite party mutation
|
||||||
|
*
|
||||||
|
* Adds a party to the user's favorites.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useFavoriteParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const favoriteParty = useFavoriteParty()
|
||||||
|
*
|
||||||
|
* function handleFavorite(shortcode: string) {
|
||||||
|
* favoriteParty.mutate(shortcode)
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useFavoriteParty() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (shortcode: string) => partyAdapter.favorite(shortcode),
|
||||||
|
onMutate: async (shortcode) => {
|
||||||
|
// Cancel any outgoing refetches
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(shortcode))
|
||||||
|
|
||||||
|
// Optimistically update the cache
|
||||||
|
if (previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), {
|
||||||
|
...previousParty,
|
||||||
|
favorited: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, shortcode, context) => {
|
||||||
|
// Rollback on error
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, shortcode) => {
|
||||||
|
// Invalidate favorites list
|
||||||
|
queryClient.invalidateQueries({ queryKey: userKeys.favorites() })
|
||||||
|
// Refetch the party to get accurate state
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unfavorite party mutation
|
||||||
|
*
|
||||||
|
* Removes a party from the user's favorites.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useUnfavoriteParty } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const unfavoriteParty = useUnfavoriteParty()
|
||||||
|
*
|
||||||
|
* function handleUnfavorite(shortcode: string) {
|
||||||
|
* unfavoriteParty.mutate(shortcode)
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useUnfavoriteParty() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (shortcode: string) => partyAdapter.unfavorite(shortcode),
|
||||||
|
onMutate: async (shortcode) => {
|
||||||
|
// Cancel any outgoing refetches
|
||||||
|
await queryClient.cancelQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousParty = queryClient.getQueryData<Party>(partyKeys.detail(shortcode))
|
||||||
|
|
||||||
|
// Optimistically update the cache
|
||||||
|
if (previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), {
|
||||||
|
...previousParty,
|
||||||
|
favorited: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previousParty }
|
||||||
|
},
|
||||||
|
onError: (_err, shortcode, context) => {
|
||||||
|
// Rollback on error
|
||||||
|
if (context?.previousParty) {
|
||||||
|
queryClient.setQueryData(partyKeys.detail(shortcode), context.previousParty)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: (_data, _err, shortcode) => {
|
||||||
|
// Invalidate favorites list
|
||||||
|
queryClient.invalidateQueries({ queryKey: userKeys.favorites() })
|
||||||
|
// Refetch the party to get accurate state
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.detail(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerate preview mutation
|
||||||
|
*
|
||||||
|
* Triggers regeneration of a party's preview image.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```svelte
|
||||||
|
* <script lang="ts">
|
||||||
|
* import { useRegeneratePreview } from '$lib/api/mutations/party.mutations'
|
||||||
|
*
|
||||||
|
* const regeneratePreview = useRegeneratePreview()
|
||||||
|
*
|
||||||
|
* function handleRegenerate(shortcode: string) {
|
||||||
|
* regeneratePreview.mutate(shortcode)
|
||||||
|
* }
|
||||||
|
* </script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useRegeneratePreview() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: (shortcode: string) => partyAdapter.regeneratePreview(shortcode),
|
||||||
|
onSuccess: (_data, shortcode) => {
|
||||||
|
// Invalidate preview status to trigger refetch
|
||||||
|
queryClient.invalidateQueries({ queryKey: partyKeys.preview(shortcode) })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
182
src/lib/api/queries/job.queries.ts
Normal file
182
src/lib/api/queries/job.queries.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
/**
|
||||||
|
* Job Query Options Factory
|
||||||
|
*
|
||||||
|
* Provides type-safe, reusable query configurations for job operations
|
||||||
|
* using TanStack Query v6 patterns.
|
||||||
|
*
|
||||||
|
* @module api/queries/job
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { queryOptions, infiniteQueryOptions } from '@tanstack/svelte-query'
|
||||||
|
import {
|
||||||
|
jobAdapter,
|
||||||
|
type SearchJobSkillsParams
|
||||||
|
} from '$lib/api/adapters/job.adapter'
|
||||||
|
import type { Job, JobSkill, JobAccessory } from '$lib/types/api/entities'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard page result format for job skill infinite queries
|
||||||
|
*/
|
||||||
|
export interface JobSkillPageResult {
|
||||||
|
results: JobSkill[]
|
||||||
|
page: number
|
||||||
|
totalPages: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job query options factory
|
||||||
|
*
|
||||||
|
* Provides query configurations for all job-related operations.
|
||||||
|
* These can be used with `createQuery`, `createInfiniteQuery`, or for prefetching.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
|
* import { jobQueries } from '$lib/api/queries/job.queries'
|
||||||
|
*
|
||||||
|
* // All jobs
|
||||||
|
* const jobs = createQuery(() => jobQueries.list())
|
||||||
|
*
|
||||||
|
* // Skills for a specific job with infinite scroll
|
||||||
|
* const skills = createInfiniteQuery(() => jobQueries.skills(jobId, { query: searchTerm }))
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const jobQueries = {
|
||||||
|
/**
|
||||||
|
* All jobs list query options
|
||||||
|
*
|
||||||
|
* @returns Query options for fetching all jobs
|
||||||
|
*/
|
||||||
|
list: () =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['jobs'] as const,
|
||||||
|
queryFn: () => jobAdapter.getAll(),
|
||||||
|
staleTime: 1000 * 60 * 30, // 30 minutes - jobs rarely change
|
||||||
|
gcTime: 1000 * 60 * 60 // 1 hour
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single job query options
|
||||||
|
*
|
||||||
|
* @param id - Job ID
|
||||||
|
* @returns Query options for fetching a single job
|
||||||
|
*/
|
||||||
|
byId: (id: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['jobs', id] as const,
|
||||||
|
queryFn: () => jobAdapter.getById(id),
|
||||||
|
enabled: !!id,
|
||||||
|
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||||
|
gcTime: 1000 * 60 * 60 // 1 hour
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job skills query options (non-paginated)
|
||||||
|
*
|
||||||
|
* @param jobId - Job ID to fetch skills for
|
||||||
|
* @returns Query options for fetching all skills for a job
|
||||||
|
*/
|
||||||
|
skillsByJob: (jobId: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['jobs', jobId, 'skills'] as const,
|
||||||
|
queryFn: () => jobAdapter.getSkills(jobId),
|
||||||
|
enabled: !!jobId,
|
||||||
|
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||||
|
gcTime: 1000 * 60 * 60 // 1 hour
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job skills search infinite query options
|
||||||
|
*
|
||||||
|
* @param jobId - Job ID to search skills for
|
||||||
|
* @param params - Optional search parameters (query, filters, locale)
|
||||||
|
* @returns Infinite query options for searching job skills
|
||||||
|
*/
|
||||||
|
skills: (
|
||||||
|
jobId: string,
|
||||||
|
params?: Omit<SearchJobSkillsParams, 'jobId' | 'page'>
|
||||||
|
) =>
|
||||||
|
infiniteQueryOptions({
|
||||||
|
queryKey: ['jobs', jobId, 'skills', 'search', params] as const,
|
||||||
|
queryFn: async ({ pageParam }): Promise<JobSkillPageResult> => {
|
||||||
|
const response = await jobAdapter.searchSkills({
|
||||||
|
jobId,
|
||||||
|
...params,
|
||||||
|
page: pageParam
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
results: response.results,
|
||||||
|
page: response.page,
|
||||||
|
totalPages: response.totalPages,
|
||||||
|
total: response.total
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
if (lastPage.page < lastPage.totalPages) {
|
||||||
|
return lastPage.page + 1
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
enabled: !!jobId,
|
||||||
|
staleTime: 1000 * 60 * 5, // 5 minutes - search results can change
|
||||||
|
gcTime: 1000 * 60 * 15 // 15 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job accessories query options
|
||||||
|
*
|
||||||
|
* @param jobId - Job ID to fetch accessories for
|
||||||
|
* @returns Query options for fetching job accessories
|
||||||
|
*/
|
||||||
|
accessories: (jobId: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['jobs', jobId, 'accessories'] as const,
|
||||||
|
queryFn: () => jobAdapter.getAccessories(jobId),
|
||||||
|
enabled: !!jobId,
|
||||||
|
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||||
|
gcTime: 1000 * 60 * 60 // 1 hour
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All job skills query options (not filtered by job)
|
||||||
|
*
|
||||||
|
* @returns Query options for fetching all job skills
|
||||||
|
*/
|
||||||
|
allSkills: () =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['jobs', 'skills', 'all'] as const,
|
||||||
|
queryFn: () => jobAdapter.getAllSkills(),
|
||||||
|
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||||
|
gcTime: 1000 * 60 * 60 // 1 hour
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query key helpers for cache invalidation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { useQueryClient } from '@tanstack/svelte-query'
|
||||||
|
* import { jobKeys } from '$lib/api/queries/job.queries'
|
||||||
|
*
|
||||||
|
* const queryClient = useQueryClient()
|
||||||
|
*
|
||||||
|
* // Invalidate all jobs
|
||||||
|
* queryClient.invalidateQueries({ queryKey: jobKeys.all })
|
||||||
|
*
|
||||||
|
* // Invalidate skills for a specific job
|
||||||
|
* queryClient.invalidateQueries({ queryKey: jobKeys.skills('job-id') })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const jobKeys = {
|
||||||
|
all: ['jobs'] as const,
|
||||||
|
lists: () => [...jobKeys.all] as const,
|
||||||
|
detail: (id: string) => [...jobKeys.all, id] as const,
|
||||||
|
skills: (jobId: string) => [...jobKeys.all, jobId, 'skills'] as const,
|
||||||
|
skillsSearch: (jobId: string, params?: Omit<SearchJobSkillsParams, 'jobId' | 'page'>) =>
|
||||||
|
[...jobKeys.skills(jobId), 'search', params] as const,
|
||||||
|
accessories: (jobId: string) => [...jobKeys.all, jobId, 'accessories'] as const,
|
||||||
|
allSkills: () => [...jobKeys.all, 'skills', 'all'] as const
|
||||||
|
}
|
||||||
185
src/lib/api/queries/party.queries.ts
Normal file
185
src/lib/api/queries/party.queries.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
/**
|
||||||
|
* Party Query Options Factory
|
||||||
|
*
|
||||||
|
* Provides type-safe, reusable query configurations for party operations
|
||||||
|
* using TanStack Query v6 patterns.
|
||||||
|
*
|
||||||
|
* @module api/queries/party
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { queryOptions, infiniteQueryOptions } from '@tanstack/svelte-query'
|
||||||
|
import {
|
||||||
|
partyAdapter,
|
||||||
|
type ListUserPartiesParams
|
||||||
|
} from '$lib/api/adapters/party.adapter'
|
||||||
|
import type { Party } from '$lib/types/api/party'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard page result format for infinite queries
|
||||||
|
*/
|
||||||
|
export interface PartyPageResult {
|
||||||
|
results: Party[]
|
||||||
|
page: number
|
||||||
|
totalPages: number
|
||||||
|
total: number
|
||||||
|
perPage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for listing parties
|
||||||
|
*/
|
||||||
|
export interface ListPartiesParams {
|
||||||
|
page?: number
|
||||||
|
per?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Party query options factory
|
||||||
|
*
|
||||||
|
* Provides query configurations for all party-related operations.
|
||||||
|
* These can be used with `createQuery`, `createInfiniteQuery`, or for prefetching.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
|
* import { partyQueries } from '$lib/api/queries/party.queries'
|
||||||
|
*
|
||||||
|
* // Single party by shortcode
|
||||||
|
* const party = createQuery(() => partyQueries.byShortcode(shortcode))
|
||||||
|
*
|
||||||
|
* // Infinite list of parties
|
||||||
|
* const parties = createInfiniteQuery(() => partyQueries.list())
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const partyQueries = {
|
||||||
|
/**
|
||||||
|
* Single party query options
|
||||||
|
*
|
||||||
|
* @param shortcode - Party shortcode identifier
|
||||||
|
* @returns Query options for fetching a single party
|
||||||
|
*/
|
||||||
|
byShortcode: (shortcode: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['party', shortcode] as const,
|
||||||
|
queryFn: () => partyAdapter.getByShortcode(shortcode),
|
||||||
|
enabled: !!shortcode,
|
||||||
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||||
|
gcTime: 1000 * 60 * 30 // 30 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public parties list (explore page) infinite query options
|
||||||
|
*
|
||||||
|
* @param params - Optional pagination parameters
|
||||||
|
* @returns Infinite query options for listing public parties
|
||||||
|
*/
|
||||||
|
list: (params?: Omit<ListPartiesParams, 'page'>) =>
|
||||||
|
infiniteQueryOptions({
|
||||||
|
queryKey: ['parties', 'list', params] as const,
|
||||||
|
queryFn: async ({ pageParam }): Promise<PartyPageResult> => {
|
||||||
|
const response = await partyAdapter.list({
|
||||||
|
...params,
|
||||||
|
page: pageParam
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
results: response.results,
|
||||||
|
page: response.page,
|
||||||
|
totalPages: response.totalPages,
|
||||||
|
total: response.total,
|
||||||
|
perPage: response.perPage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
if (lastPage.page < lastPage.totalPages) {
|
||||||
|
return lastPage.page + 1
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
staleTime: 1000 * 60 * 2, // 2 minutes - parties change more frequently
|
||||||
|
gcTime: 1000 * 60 * 15 // 15 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User parties list infinite query options
|
||||||
|
*
|
||||||
|
* @param username - Username to fetch parties for
|
||||||
|
* @param params - Optional filter parameters
|
||||||
|
* @returns Infinite query options for listing user's parties
|
||||||
|
*/
|
||||||
|
userParties: (
|
||||||
|
username: string,
|
||||||
|
params?: Omit<ListUserPartiesParams, 'username' | 'page'>
|
||||||
|
) =>
|
||||||
|
infiniteQueryOptions({
|
||||||
|
queryKey: ['parties', 'user', username, params] as const,
|
||||||
|
queryFn: async ({ pageParam }): Promise<PartyPageResult> => {
|
||||||
|
const response = await partyAdapter.listUserParties({
|
||||||
|
username,
|
||||||
|
...params,
|
||||||
|
page: pageParam
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
results: response.results,
|
||||||
|
page: response.page,
|
||||||
|
totalPages: response.totalPages,
|
||||||
|
total: response.total,
|
||||||
|
perPage: response.perPage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
if (lastPage.page < lastPage.totalPages) {
|
||||||
|
return lastPage.page + 1
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
enabled: !!username,
|
||||||
|
staleTime: 1000 * 60 * 2, // 2 minutes
|
||||||
|
gcTime: 1000 * 60 * 15 // 15 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Party preview status query options
|
||||||
|
*
|
||||||
|
* @param shortcode - Party shortcode identifier
|
||||||
|
* @returns Query options for fetching party preview status
|
||||||
|
*/
|
||||||
|
previewStatus: (shortcode: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['party', shortcode, 'preview'] as const,
|
||||||
|
queryFn: () => partyAdapter.getPreviewStatus(shortcode),
|
||||||
|
enabled: !!shortcode,
|
||||||
|
staleTime: 1000 * 30, // 30 seconds - preview status changes
|
||||||
|
gcTime: 1000 * 60 * 5 // 5 minutes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query key helpers for cache invalidation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { useQueryClient } from '@tanstack/svelte-query'
|
||||||
|
* import { partyKeys } from '$lib/api/queries/party.queries'
|
||||||
|
*
|
||||||
|
* const queryClient = useQueryClient()
|
||||||
|
*
|
||||||
|
* // Invalidate a specific party
|
||||||
|
* queryClient.invalidateQueries({ queryKey: partyKeys.detail('abc123') })
|
||||||
|
*
|
||||||
|
* // Invalidate all party lists
|
||||||
|
* queryClient.invalidateQueries({ queryKey: partyKeys.lists() })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const partyKeys = {
|
||||||
|
all: ['parties'] as const,
|
||||||
|
lists: () => [...partyKeys.all, 'list'] as const,
|
||||||
|
list: (params?: ListPartiesParams) => [...partyKeys.lists(), params] as const,
|
||||||
|
userLists: () => [...partyKeys.all, 'user'] as const,
|
||||||
|
userList: (username: string, params?: Omit<ListUserPartiesParams, 'username'>) =>
|
||||||
|
[...partyKeys.userLists(), username, params] as const,
|
||||||
|
details: () => ['party'] as const,
|
||||||
|
detail: (shortcode: string) => [...partyKeys.details(), shortcode] as const,
|
||||||
|
preview: (shortcode: string) => [...partyKeys.detail(shortcode), 'preview'] as const
|
||||||
|
}
|
||||||
218
src/lib/api/queries/user.queries.ts
Normal file
218
src/lib/api/queries/user.queries.ts
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
/**
|
||||||
|
* User Query Options Factory
|
||||||
|
*
|
||||||
|
* Provides type-safe, reusable query configurations for user operations
|
||||||
|
* using TanStack Query v6 patterns.
|
||||||
|
*
|
||||||
|
* @module api/queries/user
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { queryOptions, infiniteQueryOptions } from '@tanstack/svelte-query'
|
||||||
|
import { userAdapter, type UserInfo, type UserProfile } from '$lib/api/adapters/user.adapter'
|
||||||
|
import type { Party } from '$lib/types/api/party'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard page result format for user parties infinite queries
|
||||||
|
*/
|
||||||
|
export interface UserPartiesPageResult {
|
||||||
|
results: Party[]
|
||||||
|
page: number
|
||||||
|
totalPages: number
|
||||||
|
total: number
|
||||||
|
perPage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard page result format for favorites infinite queries
|
||||||
|
*/
|
||||||
|
export interface FavoritesPageResult {
|
||||||
|
items: Party[]
|
||||||
|
page: number
|
||||||
|
totalPages: number
|
||||||
|
total: number
|
||||||
|
perPage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User query options factory
|
||||||
|
*
|
||||||
|
* Provides query configurations for all user-related operations.
|
||||||
|
* These can be used with `createQuery`, `createInfiniteQuery`, or for prefetching.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
|
* import { userQueries } from '$lib/api/queries/user.queries'
|
||||||
|
*
|
||||||
|
* // Current user
|
||||||
|
* const currentUser = createQuery(() => userQueries.me())
|
||||||
|
*
|
||||||
|
* // User profile with parties
|
||||||
|
* const profile = createQuery(() => userQueries.profile(username))
|
||||||
|
*
|
||||||
|
* // User's parties with infinite scroll
|
||||||
|
* const parties = createInfiniteQuery(() => userQueries.parties(username))
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const userQueries = {
|
||||||
|
/**
|
||||||
|
* Current user query options
|
||||||
|
*
|
||||||
|
* @returns Query options for fetching the current authenticated user
|
||||||
|
*/
|
||||||
|
me: () =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['user', 'me'] as const,
|
||||||
|
queryFn: () => userAdapter.getCurrentUser(),
|
||||||
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||||
|
gcTime: 1000 * 60 * 30 // 30 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User info query options
|
||||||
|
*
|
||||||
|
* @param username - Username to fetch info for
|
||||||
|
* @returns Query options for fetching user info
|
||||||
|
*/
|
||||||
|
info: (username: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['user', username, 'info'] as const,
|
||||||
|
queryFn: () => userAdapter.getInfo(username),
|
||||||
|
enabled: !!username,
|
||||||
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||||
|
gcTime: 1000 * 60 * 30 // 30 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User profile query options (includes first page of parties)
|
||||||
|
*
|
||||||
|
* @param username - Username to fetch profile for
|
||||||
|
* @returns Query options for fetching user profile
|
||||||
|
*/
|
||||||
|
profile: (username: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['user', username, 'profile'] as const,
|
||||||
|
queryFn: () => userAdapter.getProfile(username),
|
||||||
|
enabled: !!username,
|
||||||
|
staleTime: 1000 * 60 * 2, // 2 minutes - profile data changes
|
||||||
|
gcTime: 1000 * 60 * 15 // 15 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User parties infinite query options
|
||||||
|
*
|
||||||
|
* @param username - Username to fetch parties for
|
||||||
|
* @returns Infinite query options for fetching user's parties
|
||||||
|
*/
|
||||||
|
parties: (username: string) =>
|
||||||
|
infiniteQueryOptions({
|
||||||
|
queryKey: ['user', username, 'parties'] as const,
|
||||||
|
queryFn: async ({ pageParam }): Promise<UserPartiesPageResult> => {
|
||||||
|
const response = await userAdapter.getProfileParties(username, pageParam)
|
||||||
|
return {
|
||||||
|
results: response.results,
|
||||||
|
page: response.page,
|
||||||
|
totalPages: response.totalPages,
|
||||||
|
total: response.total,
|
||||||
|
perPage: response.perPage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
if (lastPage.page < lastPage.totalPages) {
|
||||||
|
return lastPage.page + 1
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
enabled: !!username,
|
||||||
|
staleTime: 1000 * 60 * 2, // 2 minutes
|
||||||
|
gcTime: 1000 * 60 * 15 // 15 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User favorites infinite query options
|
||||||
|
*
|
||||||
|
* @returns Infinite query options for fetching user's favorite parties
|
||||||
|
*/
|
||||||
|
favorites: () =>
|
||||||
|
infiniteQueryOptions({
|
||||||
|
queryKey: ['user', 'favorites'] as const,
|
||||||
|
queryFn: async ({ pageParam }): Promise<FavoritesPageResult> => {
|
||||||
|
const response = await userAdapter.getFavorites({ page: pageParam })
|
||||||
|
return {
|
||||||
|
items: response.items,
|
||||||
|
page: response.page,
|
||||||
|
totalPages: response.totalPages,
|
||||||
|
total: response.total,
|
||||||
|
perPage: response.perPage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
if (lastPage.page < lastPage.totalPages) {
|
||||||
|
return lastPage.page + 1
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
staleTime: 1000 * 60 * 2, // 2 minutes
|
||||||
|
gcTime: 1000 * 60 * 15 // 15 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Username availability check query options
|
||||||
|
*
|
||||||
|
* @param username - Username to check availability for
|
||||||
|
* @returns Query options for checking username availability
|
||||||
|
*/
|
||||||
|
checkUsername: (username: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['user', 'check', 'username', username] as const,
|
||||||
|
queryFn: () => userAdapter.checkUsernameAvailability(username),
|
||||||
|
enabled: !!username && username.length >= 3,
|
||||||
|
staleTime: 1000 * 30, // 30 seconds - availability can change
|
||||||
|
gcTime: 1000 * 60 * 5 // 5 minutes
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email availability check query options
|
||||||
|
*
|
||||||
|
* @param email - Email to check availability for
|
||||||
|
* @returns Query options for checking email availability
|
||||||
|
*/
|
||||||
|
checkEmail: (email: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ['user', 'check', 'email', email] as const,
|
||||||
|
queryFn: () => userAdapter.checkEmailAvailability(email),
|
||||||
|
enabled: !!email && email.includes('@'),
|
||||||
|
staleTime: 1000 * 30, // 30 seconds - availability can change
|
||||||
|
gcTime: 1000 * 60 * 5 // 5 minutes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query key helpers for cache invalidation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { useQueryClient } from '@tanstack/svelte-query'
|
||||||
|
* import { userKeys } from '$lib/api/queries/user.queries'
|
||||||
|
*
|
||||||
|
* const queryClient = useQueryClient()
|
||||||
|
*
|
||||||
|
* // Invalidate current user
|
||||||
|
* queryClient.invalidateQueries({ queryKey: userKeys.me() })
|
||||||
|
*
|
||||||
|
* // Invalidate a user's profile
|
||||||
|
* queryClient.invalidateQueries({ queryKey: userKeys.profile('username') })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const userKeys = {
|
||||||
|
all: ['user'] as const,
|
||||||
|
me: () => [...userKeys.all, 'me'] as const,
|
||||||
|
info: (username: string) => [...userKeys.all, username, 'info'] as const,
|
||||||
|
profile: (username: string) => [...userKeys.all, username, 'profile'] as const,
|
||||||
|
parties: (username: string) => [...userKeys.all, username, 'parties'] as const,
|
||||||
|
favorites: () => [...userKeys.all, 'favorites'] as const,
|
||||||
|
checkUsername: (username: string) => [...userKeys.all, 'check', 'username', username] as const,
|
||||||
|
checkEmail: (email: string) => [...userKeys.all, 'check', 'email', email] as const
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue