UNPKG

@techsio/storefront-data

Version:

Medusa-first storefront data layer built on TanStack Query.

402 lines (401 loc) • 16.9 kB
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 };