UNPKG

@techsio/storefront-data

Version:

Medusa-first storefront data layer built on TanStack Query.

156 lines (155 loc) • 7.38 kB
import { useQuery, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import { createCacheConfig, getPrefetchCacheOptions } from "../shared/cache-config.js"; import { toErrorMessage } from "../shared/error-utils.js"; import { shouldSkipPrefetch } from "../shared/prefetch.js"; import { applyRegion } from "../shared/region.js"; import { useRegionContext } from "../shared/region-context.js"; import { useDelayedPrefetchController } from "../shared/use-delayed-prefetch-controller.js"; import { createCatalogQueryKeys } from "./query-keys.js"; import { createCatalogQueryOptionsFactory } from "./query-options.js"; import { resolvePositiveInteger } from "./utils.js"; function createCatalogHooks({ service, buildListParams, queryKeys, queryKeyNamespace = "storefront-data", cacheConfig, defaultPageSize = 12, requireRegion = true, fallbackFacets }) { const resolvedCacheConfig = cacheConfig ?? createCacheConfig(); const resolvedQueryKeys = queryKeys ?? createCatalogQueryKeys(queryKeyNamespace); const buildList = buildListParams ?? ((input)=>input); const { getListQueryOptions } = createCatalogQueryOptionsFactory({ service, buildListParams: buildList, queryKeys: resolvedQueryKeys, cacheConfig: resolvedCacheConfig }); function useCatalogProducts(input, options) { const contextRegion = useRegionContext(); const { enabled: inputEnabled, ...listInput } = input; const resolvedInput = applyRegion(listInput, contextRegion ?? void 0); const enabled = inputEnabled ?? (!requireRegion || Boolean(resolvedInput.region_id)); const cacheStrategy = options?.cacheStrategy ?? "semiStatic"; const query = useQuery({ ...getListQueryOptions(input, { cacheStrategy, queryOptions: options?.queryOptions, region: contextRegion }), enabled }); const { data, isLoading, isFetching, isSuccess, error } = query; const inputPage = resolvePositiveInteger(resolvedInput.page, 1); const inputLimit = resolvePositiveInteger(resolvedInput.limit, defaultPageSize); const currentPage = resolvePositiveInteger(data?.page, inputPage); const responseLimit = resolvePositiveInteger(data?.limit, inputLimit); const totalCount = data?.count ?? 0; const totalPages = data?.totalPages ?? (responseLimit > 0 ? Math.ceil(totalCount / responseLimit) : 0); return { products: data?.products ?? [], facets: data?.facets ?? fallbackFacets, isLoading, isFetching, isSuccess, error: toErrorMessage(error), totalCount, currentPage, totalPages, hasNextPage: currentPage < totalPages, hasPrevPage: currentPage > 1, query }; } function useSuspenseCatalogProducts(input, options) { const contextRegion = useRegionContext(); const { enabled: _inputEnabled, ...listInput } = input; const resolvedInput = applyRegion(listInput, contextRegion ?? void 0); if (requireRegion && !resolvedInput.region_id) throw new Error("Region is required for catalog queries"); const cacheStrategy = options?.cacheStrategy ?? "semiStatic"; const query = useSuspenseQuery({ ...getListQueryOptions(input, { cacheStrategy, queryOptions: options?.queryOptions, region: contextRegion }) }); const { data, isFetching } = query; const inputPage = resolvePositiveInteger(resolvedInput.page, 1); const inputLimit = resolvePositiveInteger(resolvedInput.limit, defaultPageSize); const currentPage = resolvePositiveInteger(data?.page, inputPage); const responseLimit = resolvePositiveInteger(data?.limit, inputLimit); const totalCount = data?.count ?? 0; const totalPages = data?.totalPages ?? (responseLimit > 0 ? Math.ceil(totalCount / responseLimit) : 0); return { products: data?.products ?? [], facets: data?.facets ?? fallbackFacets, isLoading: false, isFetching, isSuccess: true, error: null, totalCount, currentPage, totalPages, hasNextPage: currentPage < totalPages, hasPrevPage: currentPage > 1, query }; } function usePrefetchCatalogProducts(options) { const queryClient = useQueryClient(); const contextRegion = useRegionContext(); const { schedulePrefetch, cancelPrefetch } = useDelayedPrefetchController(); const cacheStrategy = options?.cacheStrategy ?? "semiStatic"; const defaultDelay = options?.defaultDelay ?? 250; const skipIfCached = options?.skipIfCached ?? true; const skipMode = options?.skipMode ?? "fresh"; const prefetchCacheOptions = getPrefetchCacheOptions(resolvedCacheConfig, cacheStrategy); const prefetchCatalogProducts = async (input)=>{ const { enabled: inputEnabled, ...listInput } = input; const resolvedInput = applyRegion(listInput, contextRegion ?? void 0); const isEnabled = inputEnabled ?? (!requireRegion || Boolean(resolvedInput.region_id)); if (!isEnabled) return; const listParams = buildList(resolvedInput); const queryKey = resolvedQueryKeys.list(listParams); if (shouldSkipPrefetch({ queryClient, queryKey, cacheOptions: prefetchCacheOptions, skipIfCached, skipMode })) return; await queryClient.prefetchQuery({ queryKey, queryFn: ({ signal })=>service.getCatalogProducts(listParams, signal), ...prefetchCacheOptions }); }; const delayedPrefetch = (input, delay = defaultDelay, prefetchId)=>{ const { enabled: _inputEnabled, ...listInput } = input; const resolvedInput = applyRegion(listInput, contextRegion ?? void 0); const listParams = buildList(resolvedInput); const queryKey = resolvedQueryKeys.list(listParams); const id = prefetchId ?? JSON.stringify(queryKey); return schedulePrefetch(()=>{ prefetchCatalogProducts(input); }, id, delay); }; return { prefetchCatalogProducts, delayedPrefetch, cancelPrefetch }; } const prefetchCatalogProducts = async (queryClient, input, region)=>{ const { enabled: inputEnabled, ...listInput } = input; const resolvedInput = applyRegion(listInput, region ?? void 0); const isEnabled = inputEnabled ?? (!requireRegion || Boolean(resolvedInput.region_id)); if (!isEnabled) return; await queryClient.prefetchQuery(getListQueryOptions(input, { cacheStrategy: "semiStatic", region })); }; return { getListQueryOptions, useCatalogProducts, useSuspenseCatalogProducts, usePrefetchCatalogProducts, prefetchCatalogProducts }; } export { createCatalogHooks };