@techsio/storefront-data
Version:
Medusa-first storefront data layer built on TanStack Query.
402 lines (401 loc) • 16.9 kB
JavaScript
import { useMutation, useQueries, useQuery, useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
import { createCacheConfig, getPrefetchCacheOptions } from "../shared/cache-config.js";
import { syncCartCaches } from "../shared/cart-cache-sync.js";
import { toErrorMessage } from "../shared/error-utils.js";
import { shouldSkipPrefetch } from "../shared/prefetch.js";
import { useDelayedPrefetchController } from "../shared/use-delayed-prefetch-controller.js";
import { createDefaultListParams, stripDetailInput, withCustomerScope } from "./input-utils.js";
import { createProductListQueryKeys } from "./query-keys.js";
function createProductListHooks({ service, buildListParams, buildDetailParams, buildListKeyParams, buildDetailKeyParams, queryKeys, queryKeyNamespace = "storefront-data", cacheConfig, defaultPageSize = 20, cartQueryKeys, cartStorage, isActiveCartQueryKey }) {
const resolvedCacheConfig = cacheConfig ?? createCacheConfig();
const resolvedQueryKeys = queryKeys ?? createProductListQueryKeys(queryKeyNamespace);
const buildList = buildListParams ?? ((input)=>createDefaultListParams(input, defaultPageSize));
const buildDetail = buildDetailParams ?? ((input)=>stripDetailInput(input));
const buildListKey = buildListKeyParams ?? ((input, params)=>withCustomerScope(params, input));
const buildDetailKey = buildDetailKeyParams ?? ((input, params)=>withCustomerScope(params, input));
const getListQueryOptions = (input, options)=>{
const listParams = buildList(input);
return {
queryKey: resolvedQueryKeys.list(buildListKey(input, listParams)),
queryFn: ({ signal })=>service.listProductLists(listParams, signal),
...resolvedCacheConfig.userData,
...options?.queryOptions ?? {}
};
};
const getDetailQueryOptions = (input, options)=>{
const detailParams = buildDetail(input);
return {
queryKey: resolvedQueryKeys.detail(buildDetailKey(input, detailParams)),
queryFn: ({ signal })=>{
if (!input.id) throw new Error("Product list id is required");
return service.getProductList(detailParams, signal);
},
...resolvedCacheConfig.userData,
...options?.queryOptions ?? {}
};
};
const createProductListsPrefetchQueryOptions = (input, options)=>{
const listParams = buildList(input);
const prefetchCacheOptions = getPrefetchCacheOptions(resolvedCacheConfig, options?.cacheStrategy ?? "userData");
return {
queryKey: resolvedQueryKeys.list(buildListKey(input, listParams)),
queryFn: ({ signal })=>service.listProductLists(listParams, signal),
...prefetchCacheOptions,
meta: options?.prefetchedBy ? {
prefetchedBy: options.prefetchedBy
} : void 0
};
};
const createProductListPrefetchQueryOptions = (input, options)=>{
const detailParams = buildDetail(input);
const prefetchCacheOptions = getPrefetchCacheOptions(resolvedCacheConfig, options?.cacheStrategy ?? "userData");
return {
queryKey: resolvedQueryKeys.detail(buildDetailKey(input, detailParams)),
queryFn: ({ signal })=>service.getProductList(detailParams, signal),
...prefetchCacheOptions,
meta: options?.prefetchedBy ? {
prefetchedBy: options.prefetchedBy
} : void 0
};
};
const invalidateProductLists = (queryClient)=>queryClient.invalidateQueries({
queryKey: resolvedQueryKeys.all()
});
function useProductLists(input = {}, options) {
const enabled = input.enabled ?? true;
const query = useQuery({
...getListQueryOptions(input, options),
enabled
});
const { data, isLoading, isFetching, isSuccess, error } = query;
return {
productLists: data?.productLists ?? [],
count: data?.count ?? 0,
limit: data?.limit ?? input.limit ?? defaultPageSize,
offset: data?.offset ?? input.offset ?? 0,
isLoading,
isFetching,
isSuccess,
error: toErrorMessage(error),
query
};
}
function useSuspenseProductLists(input = {}, options) {
const query = useSuspenseQuery({
...getListQueryOptions(input, {
queryOptions: options?.queryOptions
})
});
const { data, isFetching } = query;
return {
productLists: data.productLists,
count: data.count,
limit: data.limit,
offset: data.offset,
isLoading: false,
isFetching,
isSuccess: true,
error: null,
query
};
}
function useProductList(input, options) {
const enabled = Boolean(input.id) && (input.enabled ?? true);
const query = useQuery({
...getDetailQueryOptions(input, options),
enabled
});
const { data, isLoading, isFetching, isSuccess, error } = query;
return {
productList: data ?? null,
isLoading,
isFetching,
isSuccess,
error: toErrorMessage(error),
query
};
}
function useSuspenseProductList(input, options) {
if (!input.id) throw new Error("Product list id is required");
const query = useSuspenseQuery({
...getDetailQueryOptions(input, {
queryOptions: options?.queryOptions
})
});
const { data, isFetching } = query;
return {
productList: data ?? null,
isLoading: false,
isFetching,
isSuccess: true,
error: null,
query
};
}
function useProductListDetails(inputs, options) {
const enabled = options?.enabled ?? true;
return useQueries({
queries: inputs.map((input)=>({
...getDetailQueryOptions(input, {
queryOptions: options?.queryOptions
}),
enabled: enabled && Boolean(input.id)
}))
});
}
function usePrefetchProductLists(options) {
const queryClient = useQueryClient();
const { schedulePrefetch, cancelPrefetch } = useDelayedPrefetchController();
const cacheStrategy = options?.cacheStrategy ?? "userData";
const defaultDelay = options?.defaultDelay ?? 800;
const skipIfCached = options?.skipIfCached ?? true;
const skipMode = options?.skipMode ?? "fresh";
const prefetchProductLists = async (input = {}, prefetchOptions)=>{
const cacheStrategyResolved = prefetchOptions?.cacheStrategy ?? cacheStrategy;
const skipIfCachedResolved = prefetchOptions?.skipIfCached ?? skipIfCached;
const skipModeResolved = prefetchOptions?.skipMode ?? skipMode;
const queryOptions = createProductListsPrefetchQueryOptions(input, {
cacheStrategy: cacheStrategyResolved,
prefetchedBy: prefetchOptions?.prefetchedBy
});
const prefetchCacheOptions = getPrefetchCacheOptions(resolvedCacheConfig, cacheStrategyResolved);
if (shouldSkipPrefetch({
queryClient,
queryKey: queryOptions.queryKey,
cacheOptions: prefetchCacheOptions,
skipIfCached: skipIfCachedResolved,
skipMode: skipModeResolved
})) return;
await queryClient.prefetchQuery(queryOptions);
};
const delayedPrefetch = (input = {}, delay = defaultDelay, prefetchId)=>{
const queryOptions = createProductListsPrefetchQueryOptions(input, {
cacheStrategy
});
const id = prefetchId ?? JSON.stringify(queryOptions.queryKey);
return schedulePrefetch(()=>{
prefetchProductLists(input);
}, id, delay);
};
return {
prefetchProductLists,
delayedPrefetch,
cancelPrefetch
};
}
function usePrefetchProductList(options) {
const queryClient = useQueryClient();
const { schedulePrefetch, cancelPrefetch } = useDelayedPrefetchController();
const cacheStrategy = options?.cacheStrategy ?? "userData";
const defaultDelay = options?.defaultDelay ?? 400;
const skipIfCached = options?.skipIfCached ?? true;
const skipMode = options?.skipMode ?? "fresh";
const prefetchProductList = async (input, prefetchOptions)=>{
if (!input.id) return;
const cacheStrategyResolved = prefetchOptions?.cacheStrategy ?? cacheStrategy;
const skipIfCachedResolved = prefetchOptions?.skipIfCached ?? skipIfCached;
const skipModeResolved = prefetchOptions?.skipMode ?? skipMode;
const queryOptions = createProductListPrefetchQueryOptions(input, {
cacheStrategy: cacheStrategyResolved,
prefetchedBy: prefetchOptions?.prefetchedBy
});
const prefetchCacheOptions = getPrefetchCacheOptions(resolvedCacheConfig, cacheStrategyResolved);
if (shouldSkipPrefetch({
queryClient,
queryKey: queryOptions.queryKey,
cacheOptions: prefetchCacheOptions,
skipIfCached: skipIfCachedResolved,
skipMode: skipModeResolved
})) return;
await queryClient.prefetchQuery(queryOptions);
};
const delayedPrefetch = (input, delay = defaultDelay, prefetchId)=>{
const queryOptions = createProductListPrefetchQueryOptions(input, {
cacheStrategy
});
const id = prefetchId ?? JSON.stringify(queryOptions.queryKey);
return schedulePrefetch(()=>{
prefetchProductList(input);
}, id, delay);
};
return {
prefetchProductList,
delayedPrefetch,
cancelPrefetch
};
}
function useCreateFavoriteProductList(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.createFavoriteProductList,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useCreateCustomProductList(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.createCustomProductList,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useUpdateProductList(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.updateProductList,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useDeleteProductList(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.deleteProductList,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useAddProductListItem(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.addProductListItem,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useAddFavoriteProductListItem(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.addFavoriteProductListItem,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useCreateProductListCart(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.createProductListCart,
onMutate: options?.onMutate,
onSuccess: (cart, variables, context)=>{
if (cartQueryKeys) {
syncCartCaches(queryClient, cartQueryKeys, cart, {
isActiveCartQueryKey
});
queryClient.invalidateQueries({
queryKey: cartQueryKeys.all()
});
}
cartStorage?.set(cart.id);
options?.onSuccess?.(cart, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useUpdateProductListItem(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.updateProductListItem,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useChangeProductListItemQuantity(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.changeProductListItemQuantity,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useIncrementProductListItem(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.incrementProductListItem,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
function useDeleteProductListItem(options) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: service.deleteProductListItem,
onMutate: options?.onMutate,
onSuccess: (data, variables, context)=>{
invalidateProductLists(queryClient);
options?.onSuccess?.(data, variables, context);
},
onError: options?.onError,
onSettled: options?.onSettled
});
}
return {
getListQueryOptions,
getDetailQueryOptions,
useProductLists,
useSuspenseProductLists,
useProductList,
useSuspenseProductList,
useProductListDetails,
usePrefetchProductLists,
usePrefetchProductList,
useCreateFavoriteProductList,
useCreateCustomProductList,
useUpdateProductList,
useDeleteProductList,
useAddProductListItem,
useAddFavoriteProductListItem,
useCreateProductListCart,
useUpdateProductListItem,
useChangeProductListItemQuantity,
useIncrementProductListItem,
useDeleteProductListItem
};
}
export { createProductListHooks };