@techsio/storefront-data
Version:
Medusa-first storefront data layer built on TanStack Query.
156 lines (155 loc) • 7.38 kB
JavaScript
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 };