UNPKG

@rocketmakers/api-swr

Version:

Rocketmakers front-end library for parsing a generated Typescript API client into a set of configurable React hooks for fetching and mutating data.

117 lines (116 loc) 7.73 kB
import * as React from 'react'; import { useClientFetch } from '../hooks/useClientFetch'; import { useQuery } from '../hooks/useQuery'; import { cacheKeyConcat } from '../utils/caching'; import { useInfiniteQuery } from '../hooks/useInfiniteQuery'; import { combineConfigs } from '../utils/config'; /** * Creates a factory of state management tools from a generic API controller object. * * @param {TConfig} globalFetchConfig - Optional custom fetch config to pass to all API calls. Can be overridden at endpoint and fetch level. * @param {boolean} options.enableMocking - Will use mock endpoint definitions instead of calling out to the real API. * @param {APIProcessingHook} options.useApiProcessing - Optional processing hook for all client side fetches. * @param {GlobalFetchWrapperHook<TConfig>} options.useGlobalFetchWrapper - Optional fetch wrapper hook for all client side fetches. * @param {SWRConfiguration<any | undefined>} options.swrConfig - Additional config to send to SWR for all queries. * @param {SWRInfiniteConfiguration<any | undefined>} options.swrInfiniteConfig - Additional config to send to SWR for all infinite loader queries. * @returns {IApiControllerFactory} A library of controller factory methods that create state management tools for a generic controller. */ export const genericApiControllerFactory = ({ globalFetchConfig, enableMocking, useApiProcessing, useGlobalFetchWrapper, swrConfig, swrInfiniteConfig, } = {}) => { /** * Creates a set of state management tools from an OpenAPI controller * * @param controllerKey A name to use as the first part of the cache key for this controller, must be unique amongst all controllers * @param controller The controller object * @param controllerConfig Optional custom fetch config to pass to all API calls. Inherits global config and can be overridden at fetch level. * @returns A set of state management tools for an OpenAPI controller using Axios */ const createGenericApiController = (controllerKey, controller, controllerConfig) => { // Record of mock endpoints let registeredMockEndpoints = {}; /** * Merges the provided mock endpoints with the already registered mock endpoints. * @param mockEndpoints - An object containing mock endpoint functions. */ const registerMockEndpoints = (mockEndpoints) => { registeredMockEndpoints = Object.assign(Object.assign({}, registeredMockEndpoints), mockEndpoints); }; /** * Retrieves a mock endpoint function for a given endpoint key. * @param endpointKey - The key of the endpoint to retrieve the mock function for. * @returns The mock function for the given endpoint key. * @throws Will throw an error if a mock function for the given endpoint key has not been registered. */ const getMockEndpointFunction = (endpointKey) => { const mockFunc = registeredMockEndpoints[endpointKey]; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this is nonsense, it's always defined if (!mockFunc) { throw new Error(`Mock endpoint not defined for: ${controllerKey}.${endpointKey}`); } return mockFunc; }; // iterate the controller object const endpoints = Object.keys(controller).reduce((memo, endpointKey) => { /** * Fetch function for server/client side use, calls the fetcher * @param args Whatever args have been passed to the fetch, this function doesn't need to know what they are * @returns The response data */ const fetchFactory = (enableMockingViaConfig) => async (...args) => { if (enableMocking || enableMockingViaConfig) { const mockFunc = getMockEndpointFunction(endpointKey); return mockFunc(...args); } const func = controller[endpointKey]; return func(...args); }; /** * Retrieves the cache key for this specific endpoint * @param additionalCacheKey Any further cache key parts to add on to the default `controllerKey.endpointKey` * @returns The final cache key string for the endpoint */ const cacheKeyGetter = (additionalCacheKey) => { let finalKeys = Array.isArray(additionalCacheKey) ? additionalCacheKey : [additionalCacheKey]; finalKeys = finalKeys.filter((key) => key !== undefined && key !== null); return cacheKeyConcat(controllerKey, endpointKey, ...finalKeys); }; /** * Returns a `swr` mutate matcher function which will invalidate on the basis of "starts with" on the root cache key * @param additionalCacheKey Any further cache key parts to add on to the default `controllerKey.endpointKey` * @returns A `swr` mutate matcher function */ const startsWithInvalidator = (additionalCacheKey) => { return (key) => { return typeof key === 'string' && key.startsWith(cacheKeyGetter(additionalCacheKey)); }; }; const endpointId = cacheKeyGetter(); /** * The combined state management tools for this endpoint */ const endpointTools = { controllerKey, endpointKey, endpointId: cacheKeyConcat(controllerKey, endpointKey), fetch: (params, config) => { var _a; return fetchFactory()(params, (_a = combineConfigs({ fetchConfig: config }, globalFetchConfig, controllerConfig)) === null || _a === void 0 ? void 0 : _a.fetchConfig); }, cacheKey: cacheKeyGetter, startsWithInvalidator, useQuery: (config) => { const fetchOverride = React.useCallback((...args) => fetchFactory(config === null || config === void 0 ? void 0 : config.enableMocking)(...args), [fetchFactory]); return useQuery(endpointId, fetchOverride, combineConfigs(config, globalFetchConfig, controllerConfig), useApiProcessing, useGlobalFetchWrapper, swrConfig); }, useMutation: (config) => { var _a; const fetchOverride = React.useCallback((...args) => fetchFactory(config === null || config === void 0 ? void 0 : config.enableMocking)(...args), [fetchFactory]); return useClientFetch(endpointId, 'mutation', (_a = combineConfigs(config, globalFetchConfig, controllerConfig)) === null || _a === void 0 ? void 0 : _a.fetchConfig, fetchOverride, config === null || config === void 0 ? void 0 : config.params, useApiProcessing, useGlobalFetchWrapper, config === null || config === void 0 ? void 0 : config.fetchWrapper); }, useInfiniteQuery: (config) => { const fetchOverride = React.useCallback((...args) => fetchFactory(config === null || config === void 0 ? void 0 : config.enableMocking)(...args), [fetchFactory]); return useInfiniteQuery(endpointId, fetchOverride, combineConfigs(config, globalFetchConfig, controllerConfig), useApiProcessing, useGlobalFetchWrapper, swrInfiniteConfig); }, }; return Object.assign(Object.assign({}, memo), { [endpointKey]: endpointTools }); }, {}); return Object.assign(Object.assign({}, endpoints), { registerMockEndpoints }); }; return { createGenericApiController }; };