hensei-web/src/lib/query/ssr.ts
Devin AI 5bf53ea671 fix: resolve TypeScript type errors in TanStack Query integration
- Make total and perPage optional in PartyPageResult interface to match adapter return type
- Fix withInitialData to properly handle null values with NonNullable<TData>
- Fix slot indexing in job.mutations.ts by casting through unknown to keyof type

Co-Authored-By: Justin Edmund <justin@jedmund.com>
2025-11-29 08:24:45 +00:00

190 lines
5.6 KiB
TypeScript

/**
* SSR Utilities for TanStack Query
*
* Provides utilities for integrating server-side data fetching with TanStack Query.
* These utilities support the initialData pattern for pages that use +page.server.ts
* load functions.
*
* @module query/ssr
*/
import type { QueryClient } from '@tanstack/svelte-query'
/**
* Options for creating a query with initial data from SSR
*/
export interface InitialDataOptions<TData> {
/**
* The data fetched on the server to use as initial data.
* TanStack Query accepts TData | undefined but NOT null.
*/
initialData?: TData
/**
* Optional timestamp when the data was fetched on the server.
* If not provided, defaults to 0 (will be considered stale immediately).
* Use Date.now() on the server to get accurate timestamps.
*/
initialDataUpdatedAt?: number
}
/**
* Creates query options with initial data from server-side rendering.
*
* Use this helper when you have data fetched in a +page.server.ts load function
* and want to use it as initial data for a TanStack Query.
*
* Note: This helper strips `null` from the input since TanStack Query's
* initialData only accepts `TData | undefined`, not `null`.
*
* @example
* ```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
* const party = createQuery(() => ({
* ...partyQueries.byShortcode(data.party?.shortcode ?? ''),
* ...withInitialData(data.party)
* }))
* </script>
* ```
*
* @param initialData - The data fetched on the server (null is converted to undefined)
* @param updatedAt - Optional timestamp when data was fetched (defaults to 0)
* @returns Query options object with initialData and initialDataUpdatedAt
*/
export function withInitialData<TData>(
initialData: TData | undefined | null,
updatedAt?: number
): InitialDataOptions<NonNullable<TData>> {
return {
initialData: (initialData ?? undefined) as NonNullable<TData> | undefined,
initialDataUpdatedAt: updatedAt ?? 0
}
}
/**
* Prefetches a query on the server and returns the data.
*
* Use this in +page.ts load functions when you want to prefetch data
* into the QueryClient cache. This is the recommended approach for
* pages that don't use +page.server.ts.
*
* Note: This will NOT work with +page.server.ts load functions.
* Use withInitialData() instead for server-only load functions.
*
* @example
* ```typescript
* // +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()
*
* await prefetchQuery(queryClient, partyQueries.byShortcode(params.id))
*
* // No need to return data - it's in the cache
* }
* ```
*
* @param queryClient - The QueryClient instance from parent layout
* @param options - Query options from a query factory
*/
export async function prefetchQuery<TData>(
queryClient: QueryClient,
options: {
queryKey: readonly unknown[]
queryFn: () => Promise<TData>
staleTime?: number
gcTime?: number
}
): Promise<void> {
await queryClient.prefetchQuery({
queryKey: options.queryKey,
queryFn: options.queryFn,
staleTime: options.staleTime,
gcTime: options.gcTime
})
}
/**
* Prefetches an infinite query on the server.
*
* Use this in +page.ts load functions when you want to prefetch
* paginated data into the QueryClient cache.
*
* @example
* ```typescript
* // +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()
*
* await prefetchInfiniteQuery(queryClient, partyQueries.list())
* }
* ```
*
* @param queryClient - The QueryClient instance from parent layout
* @param options - Infinite query options from a query factory
*/
export async function prefetchInfiniteQuery<TData>(
queryClient: QueryClient,
options: {
queryKey: readonly unknown[]
queryFn: (context: { pageParam: number }) => Promise<TData>
initialPageParam: number
staleTime?: number
gcTime?: number
}
): Promise<void> {
await queryClient.prefetchInfiniteQuery({
queryKey: options.queryKey,
queryFn: options.queryFn,
initialPageParam: options.initialPageParam,
staleTime: options.staleTime,
gcTime: options.gcTime
})
}
/**
* Sets query data directly in the cache.
*
* Use this when you have data from a server load function and want
* to populate the QueryClient cache directly. This is useful when
* migrating from server-only load functions to TanStack Query.
*
* @example
* ```typescript
* // In a component or effect
* import { useQueryClient } from '@tanstack/svelte-query'
* import { setQueryData } from '$lib/query/ssr'
* import { partyKeys } from '$lib/api/queries/party.queries'
*
* const queryClient = useQueryClient()
*
* // Populate cache with server data
* setQueryData(queryClient, partyKeys.detail(shortcode), serverParty)
* ```
*
* @param queryClient - The QueryClient instance
* @param queryKey - The query key to set data for
* @param data - The data to set in the cache
*/
export function setQueryData<TData>(
queryClient: QueryClient,
queryKey: readonly unknown[],
data: TData
): void {
queryClient.setQueryData(queryKey, data)
}