CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--vue-query

Hooks for managing, caching and syncing asynchronous and remote data in Vue

Pending
Overview
Eval results
Files

helpers.mddocs/

Helper Functions

Type-safe helper functions for creating query and infinite query options, providing enhanced TypeScript inference and reusable query configurations.

Capabilities

queryOptions

Helper function for creating type-safe query options with enhanced TypeScript inference.

/**
 * Create type-safe query options with enhanced TypeScript inference
 * @param options - Query options with defined initial data
 * @returns Enhanced query options with type information
 */
function queryOptions<TQueryFnData, TError, TData, TQueryKey>(
  options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
  queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
};

/**
 * Create type-safe query options with enhanced TypeScript inference
 * @param options - Query options with undefined initial data
 * @returns Enhanced query options with type information
 */
function queryOptions<TQueryFnData, TError, TData, TQueryKey>(
  options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
  queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
};

// Base implementation for runtime usage
function queryOptions(options: unknown): unknown;

Usage Examples:

import { queryOptions, useQuery } from '@tanstack/vue-query';

// Basic query options
const todosQuery = queryOptions({
  queryKey: ['todos'],
  queryFn: () => fetch('/api/todos').then(res => res.json()),
  staleTime: 1000 * 60 * 5, // 5 minutes
});

// Use with useQuery - full type inference
const { data: todos } = useQuery(todosQuery);

// Query options with parameters
const userQuery = (userId: number) => queryOptions({
  queryKey: ['user', userId],
  queryFn: ({ queryKey }) => 
    fetch(`/api/users/${queryKey[1]}`).then(res => res.json()),
  enabled: !!userId,
});

// Use with dynamic parameters
const userId = ref(1);
const { data: user } = useQuery(userQuery(userId.value));

// Query options with data transformation
const postsQuery = queryOptions({
  queryKey: ['posts'],
  queryFn: () => fetch('/api/posts').then(res => res.json()),
  select: (posts) => posts.filter(post => post.published),
  staleTime: 1000 * 60 * 10,
});

// Query options with initial data
const cachedUserQuery = (userId: number, initialData?: User) => queryOptions({
  queryKey: ['user', userId],
  queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
  initialData,
  staleTime: initialData ? 0 : 1000 * 60 * 5,
});

// Reusable query configurations
const createApiQuery = <T>(endpoint: string) => queryOptions({
  queryKey: ['api', endpoint],
  queryFn: () => fetch(`/api/${endpoint}`).then(res => res.json()) as Promise<T>,
  retry: 3,
  staleTime: 1000 * 60 * 5,
});

// Use reusable configuration
const usersQuery = createApiQuery<User[]>('users');
const productsQuery = createApiQuery<Product[]>('products');

// Complex query with multiple options
const dashboardQuery = queryOptions({
  queryKey: ['dashboard', 'summary'],
  queryFn: async () => {
    const [users, posts, analytics] = await Promise.all([
      fetch('/api/users/count').then(r => r.json()),
      fetch('/api/posts/recent').then(r => r.json()),
      fetch('/api/analytics/summary').then(r => r.json()),
    ]);
    return { users, posts, analytics };
  },
  select: (data) => ({
    userCount: data.users.total,
    recentPosts: data.posts.slice(0, 5),
    pageViews: data.analytics.pageViews,
    conversionRate: data.analytics.conversions / data.analytics.visits,
  }),
  staleTime: 1000 * 60 * 10,
  refetchInterval: 1000 * 30, // Refresh every 30 seconds
  meta: { critical: true },
});

// Conditional query options
const conditionalQuery = (enabled: boolean) => queryOptions({
  queryKey: ['conditional-data'],
  queryFn: () => fetch('/api/data').then(res => res.json()),
  enabled,
  retry: enabled ? 3 : 0,
});

infiniteQueryOptions

Helper function for creating type-safe infinite query options with enhanced TypeScript inference.

/**
 * Create type-safe infinite query options with enhanced TypeScript inference
 * @param options - Infinite query options with undefined initial data
 * @returns Enhanced infinite query options with type information
 */
function infiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>(
  options: UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
): UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> & {
  queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>;
};

/**
 * Create type-safe infinite query options with enhanced TypeScript inference
 * @param options - Infinite query options with defined initial data
 * @returns Enhanced infinite query options with type information
 */
function infiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>(
  options: DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
): DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> & {
  queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>;
};

// Base implementation for runtime usage
function infiniteQueryOptions(options: unknown): unknown;

Usage Examples:

import { infiniteQueryOptions, useInfiniteQuery } from '@tanstack/vue-query';

// Basic infinite query options
const postsInfiniteQuery = infiniteQueryOptions({
  queryKey: ['posts', 'infinite'],
  queryFn: ({ pageParam = 1 }) =>
    fetch(`/api/posts?page=${pageParam}&limit=10`).then(res => res.json()),
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages) =>
    lastPage.hasMore ? allPages.length + 1 : undefined,
  staleTime: 1000 * 60 * 5,
});

// Use with useInfiniteQuery
const {
  data: posts,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage
} = useInfiniteQuery(postsInfiniteQuery);

// Infinite query with search parameters
const searchInfiniteQuery = (searchTerm: string) => infiniteQueryOptions({
  queryKey: ['search', searchTerm, 'infinite'],
  queryFn: ({ queryKey, pageParam = 0 }) =>
    fetch(`/api/search?q=${queryKey[1]}&offset=${pageParam}&limit=20`)
      .then(res => res.json()),
  initialPageParam: 0,
  getNextPageParam: (lastPage, allPages, lastPageParam) =>
    lastPage.results.length === 20 ? lastPageParam + 20 : undefined,
  getPreviousPageParam: (firstPage, allPages, firstPageParam) =>
    firstPageParam > 0 ? Math.max(0, firstPageParam - 20) : undefined,
  enabled: !!searchTerm && searchTerm.length > 2,
  staleTime: 1000 * 60 * 2,
});

// Use with reactive search term
const searchTerm = ref('');
const searchResults = useInfiniteQuery(
  computed(() => searchInfiniteQuery(searchTerm.value))
);

// Infinite query with cursor-based pagination
const cursorInfiniteQuery = infiniteQueryOptions({
  queryKey: ['timeline'],
  queryFn: ({ pageParam }) =>
    fetch(`/api/timeline${pageParam ? `?cursor=${pageParam}` : ''}`)
      .then(res => res.json()),
  initialPageParam: undefined,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage) => firstPage.prevCursor,
  select: (data) => ({
    pages: data.pages,
    pageParams: data.pageParams,
    items: data.pages.flatMap(page => page.items),
  }),
});

// Complex infinite query with filtering
const filteredInfiniteQuery = (category: string, sortBy: string) => infiniteQueryOptions({
  queryKey: ['products', 'infinite', category, sortBy],
  queryFn: ({ queryKey, pageParam = 1 }) => {
    const [, , category, sortBy] = queryKey;
    return fetch(
      `/api/products?category=${category}&sort=${sortBy}&page=${pageParam}`
    ).then(res => res.json());
  },
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages) => {
    if (lastPage.products.length < lastPage.pageSize) return undefined;
    return allPages.length + 1;
  },
  select: (data) => ({
    ...data,
    totalProducts: data.pages.reduce((sum, page) => sum + page.products.length, 0),
    categories: [...new Set(data.pages.flatMap(page => 
      page.products.map(p => p.category)
    ))],
  }),
  staleTime: 1000 * 60 * 5,
  maxPages: 10, // Limit to prevent excessive memory usage
});

// Infinite query with optimistic updates
const commentsInfiniteQuery = (postId: number) => infiniteQueryOptions({
  queryKey: ['post', postId, 'comments'],
  queryFn: ({ pageParam = 1 }) =>
    fetch(`/api/posts/${postId}/comments?page=${pageParam}`)
      .then(res => res.json()),
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages) =>
    lastPage.hasMore ? allPages.length + 1 : undefined,
  select: (data) => ({
    ...data,
    allComments: data.pages.flatMap(page => page.comments),
    totalCount: data.pages[0]?.totalCount || 0,
  }),
});

// Reusable infinite query factory
const createInfiniteListQuery = <T>(
  endpoint: string,
  pageSize: number = 20
) => infiniteQueryOptions({
  queryKey: [endpoint, 'infinite'],
  queryFn: ({ pageParam = 0 }) =>
    fetch(`/api/${endpoint}?offset=${pageParam}&limit=${pageSize}`)
      .then(res => res.json()) as Promise<{
        items: T[];
        hasMore: boolean;
        total: number;
      }>,
  initialPageParam: 0,
  getNextPageParam: (lastPage, allPages) =>
    lastPage.hasMore ? allPages.length * pageSize : undefined,
  staleTime: 1000 * 60 * 5,
});

// Use reusable factory
const usersInfiniteQuery = createInfiniteListQuery<User>('users');
const ordersInfiniteQuery = createInfiniteListQuery<Order>('orders', 50);

Advanced Patterns

Usage Examples:

// Query options with derived data
const createDerivedQuery = <TData, TDerived>(
  baseOptions: Parameters<typeof queryOptions>[0],
  derive: (data: TData) => TDerived
) => queryOptions({
  ...baseOptions,
  select: (data: TData) => derive(data),
});

const statsQuery = createDerivedQuery(
  {
    queryKey: ['raw-stats'],
    queryFn: () => fetch('/api/stats').then(res => res.json()),
  },
  (rawStats) => ({
    summary: {
      total: rawStats.reduce((sum, stat) => sum + stat.value, 0),
      average: rawStats.length > 0 ? rawStats.reduce((sum, stat) => sum + stat.value, 0) / rawStats.length : 0,
      max: Math.max(...rawStats.map(stat => stat.value)),
      min: Math.min(...rawStats.map(stat => stat.value)),
    },
    byCategory: rawStats.reduce((acc, stat) => {
      acc[stat.category] = (acc[stat.category] || 0) + stat.value;
      return acc;
    }, {}),
  })
);

// Query options with error boundary
const createSafeQuery = <T>(
  options: Parameters<typeof queryOptions>[0],
  fallbackData: T
) => queryOptions({
  ...options,
  select: (data) => data || fallbackData,
  retry: (failureCount, error) => {
    if (error.status === 404) return false;
    return failureCount < 3;
  },
  throwOnError: false,
});

const safeUserQuery = (userId: number) => createSafeQuery(
  {
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
  },
  { id: userId, name: 'Unknown User', email: '' }
);

// Prefetch helper using query options
const usePrefetch = () => {
  const queryClient = useQueryClient();
  
  const prefetchQuery = async (options: ReturnType<typeof queryOptions>) => {
    await queryClient.prefetchQuery(options);
  };
  
  const prefetchInfiniteQuery = async (
    options: ReturnType<typeof infiniteQueryOptions>
  ) => {
    await queryClient.prefetchInfiniteQuery(options);
  };
  
  return { prefetchQuery, prefetchInfiniteQuery };
};

// Usage with prefetch
const { prefetchQuery } = usePrefetch();

const handleUserHover = (userId: number) => {
  prefetchQuery(userQuery(userId));
};

Types

// Type enhancement for query options
type DataTag<TQueryKey, TQueryFnData, TError> = TQueryKey & {
  _dataType?: TQueryFnData;
  _errorType?: TError;
};

// Query options variants
interface DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
  extends UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> {
  initialData: TQueryFnData | (() => TQueryFnData);
}

interface UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>
  extends UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> {
  initialData?: undefined | (() => undefined);
}

// Infinite query options variants
interface DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
  extends UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> {
  initialData:
    | InfiniteData<TQueryFnData, TPageParam>
    | (() => InfiniteData<TQueryFnData, TPageParam>);
}

interface UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
  extends UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam> {
  initialData?: undefined;
}

// Data structure for infinite queries
interface InfiniteData<TData, TPageParam = unknown> {
  pages: TData[];
  pageParams: TPageParam[];
}

// Base query key type
type QueryKey = ReadonlyArray<unknown>;

// Error type
type DefaultError = Error;

Install with Tessl CLI

npx tessl i tessl/npm-tanstack--vue-query

docs

helpers.md

index.md

mutations.md

plugin-setup.md

queries.md

query-client.md

status-utilities.md

tile.json