{"version":3,"file":"provider.cjs","sources":["../../src/api/provider.ts"],"sourcesContent":["import { createContext, useState, useContext, Context } from \"react\";\nimport h from \"@macrostrat/hyper\";\nimport { memoize } from \"underscore\";\nimport axios, {\n  AxiosPromise,\n  AxiosInstance,\n  AxiosRequestConfig,\n  AxiosResponse,\n} from \"axios\";\nimport { useAsyncEffect } from \"use-async-effect\";\nimport { debounce } from \"underscore\";\nimport { APIConfig, ResponseUnwrapper, APIConfigOptions } from \"./types\";\nimport { QueryParams } from \"../util/query-string\";\nimport { useMemoizedValue } from \"../util\";\n\n/*\nThe baseURL is used to prefix paths if they are not already absolute\nhttps://github.com/axios/axios/blob/master/lib/core/buildFullPath.js\n*/\ntype APIBase = { baseURL: string; axiosInstance: AxiosInstance };\ntype APIContextValue = APIBase & {\n  config: APIConfig;\n};\n\ntype APIProviderCoreProps = APIConfigOptions &\n  Partial<AxiosRequestConfig> & {\n    config?: Partial<APIConfigOptions>;\n  };\n\ntype APIProviderProps = APIProviderCoreProps & {\n  context?: Context<APIContextValue>;\n  children?: React.ReactChild;\n};\n\ntype APIContextType = Context<APIContextValue>;\nexport type APIContext = APIContextType;\n\nconst apiDefaults: APIConfig = {\n  fullResponse: false,\n  handleError: true,\n  memoize: false,\n  onError(err, opts) {\n    let error = opts.error ?? opts;\n    throw error;\n  },\n  onResponse(_) {},\n  unwrapResponse(d) {\n    return d;\n  },\n};\n\nfunction removeUndefined(o1: object) {\n  let obj = { ...o1 };\n  Object.keys(obj).forEach((key) => {\n    if (obj[key] === undefined) {\n      delete obj[key];\n    }\n  });\n  return obj;\n}\n\nfunction splitConfig(\n  props: APIProviderCoreProps,\n): [AxiosRequestConfig, APIConfig] {\n  const {\n    config = {},\n    // These should maybe be reworked into a legacy options set...\n    fullResponse,\n    handleError,\n    memoize,\n    onError,\n    onResponse,\n    unwrapResponse,\n    ...axiosConfig\n  } = props;\n\n  let legacyConfig = removeUndefined({\n    fullResponse,\n    handleError,\n    memoize,\n    onError,\n    onResponse,\n    unwrapResponse,\n  });\n\n  const newConfig = { ...apiDefaults, ...legacyConfig, ...config };\n  return [axiosConfig, newConfig];\n}\n\nconst defaultAxios = axios.create();\n\nfunction createAPIContext(\n  defaultProps: APIProviderCoreProps = {},\n): APIContextType {\n  const [axiosConfig, config] = splitConfig(defaultProps);\n\n  const axiosInstance = axios.create(axiosConfig);\n\n  const defaultValue = {\n    axiosInstance,\n    baseURL: axiosInstance.defaults.baseURL ?? \"\",\n    config,\n  };\n\n  return createContext<APIContextValue>(defaultValue);\n}\n\nenum APIMethod {\n  POST = \"POST\",\n  GET = \"GET\",\n}\n\nconst APIContext = createAPIContext();\n\ninterface APIRequestInfo {\n  route: string;\n  params: QueryParams;\n  method: APIMethod;\n  opts?: APIConfigOptions;\n}\n\nasync function handleResult(\n  ctx: APIContextValue,\n  request: AxiosPromise,\n  info: APIRequestInfo,\n) {\n  const { processOptions, buildURL } = APIHelpers(ctx);\n  const { opts, method, params, route } = info;\n  const cfg = processOptions(opts);\n\n  let res: AxiosResponse | null = null;\n  let error: Error | null = null;\n  try {\n    res = await request;\n    cfg.onResponse(res);\n    if (res.data != null) return cfg.unwrapResponse(res.data);\n  } catch (err) {\n    error = err;\n  }\n  error = error ?? Error(res.statusText || \"No data!\");\n  if (cfg.handleError) {\n    // Log errors if we're not throwing\n    console.error(error);\n    cfg.onError(error, {\n      error: error,\n      response: res,\n      endpoint: buildURL(route, params),\n      method,\n    });\n  } else {\n    throw error;\n  }\n}\n\nconst APIHelpers = (ctx: APIContextValue) => ({\n  buildURL(route: string = \"\", params = {}) {\n    // axios's getUri doesn't return baseURL for some inexplicable reason,\n    // as of spring 2021.\n    // this behavior could change sometime in the future...\n    const uriPath = ctx.axiosInstance.getUri({\n      url: route,\n      params,\n    });\n    return ctx.baseURL + uriPath;\n  },\n  processOptions(opts: APIConfigOptions = {}): APIConfig {\n    let o1: APIConfig = { ...ctx.config, ...opts };\n    if (o1.fullResponse) o1.unwrapResponse = apiDefaults.unwrapResponse;\n    return o1;\n  },\n});\n\ninterface APIActions {\n  post(\n    route: string,\n    params: QueryParams,\n    payload: any,\n    opts: APIConfigOptions,\n  ): Promise<any | null>;\n  post(\n    route: string,\n    payload: any,\n    opts?: APIConfigOptions,\n  ): Promise<any | null>;\n  get(\n    route: string,\n    params: QueryParams,\n    opts: APIConfigOptions,\n  ): Promise<any | null>;\n  get(route: string, opts?: APIConfigOptions): Promise<any | null>;\n}\n\nconst APIActions = (ctx: APIContextValue): APIActions => {\n  const { axiosInstance } = ctx;\n  return {\n    post(route: string, ...args) {\n      let opts: APIConfigOptions, params: QueryParams, payload: any;\n      if (args.length === 3) {\n        [params, payload, opts] = args;\n      } else if (args.length === 2) {\n        [payload, opts] = args;\n      } else if (args.length === 1) {\n        [payload] = args;\n      } else {\n        throw \"No data to post\";\n      }\n      params ??= {};\n      opts ??= {};\n\n      const [axiosConfig, _] = splitConfig(opts);\n\n      const req = axiosInstance.post(route, payload, {\n        ...axiosConfig,\n        params,\n      });\n      const info = { route, params, method: APIMethod.POST, opts };\n      return handleResult(ctx, req, info);\n    },\n    get(route: string, ...args) {\n      let params: QueryParams, opts: APIConfigOptions;\n      if (args.length == 1) {\n        [opts] = args;\n      } else if (args.length == 2) {\n        [params, opts] = args;\n      }\n      params ??= {};\n      opts ??= {};\n\n      const [axiosConfig, cfg] = splitConfig(opts);\n      const fn = cfg.memoize ? memoize(axiosInstance.get) : axiosInstance.get;\n\n      const request = fn(route, { ...axiosConfig, params });\n      const info = { route, params, method: APIMethod.GET, opts };\n      return handleResult(ctx, request, info);\n    },\n  };\n};\n\nfunction APIProvider(props: APIProviderProps) {\n  /** Provider for APIContext\n\n  can pass an alternative API context using \"context\" param\n  */\n  const { context = APIContext, children, ...rest } = props;\n  const [axiosConfig, config] = splitConfig(rest);\n\n  const axiosInstance = axios.create(axiosConfig);\n\n  const value = {\n    axiosInstance,\n    baseURL: axiosInstance.defaults.baseURL ?? \"\",\n    config,\n  };\n  return h(context.Provider, { value }, children);\n}\n\nconst useAPIActions = (ctx: APIContextType = APIContext) => {\n  return APIActions(useContext(ctx));\n};\n\nconst useAPIHelpers = (ctx: APIContextType = APIContext) => {\n  return APIHelpers(useContext(ctx));\n};\n\ntype APIHookOpts = Partial<\n  APIConfig & {\n    debounce?: number;\n    context?: APIContextType;\n  }\n>;\n\nfunction useAxiosInstance(context: APIContextType = APIContext) {\n  return useContext(context).axiosInstance;\n}\n\nfunction useAPIResult<T>(\n  route: string | null,\n  params: QueryParams = null,\n  opts: APIHookOpts | ResponseUnwrapper<any, T> = {},\n): T {\n  /* React hook for API results */\n\n  const paramsDep = useMemoizedValue(params);\n  const deps = [route, paramsDep]; //...Object.values(params ?? {})];\n\n  const [result, setResult] = useState<T | null>(null);\n\n  if (typeof opts === \"function\") {\n    opts = { unwrapResponse: opts };\n  }\n\n  const { debounce: _debounce, context, ...rest } = opts ?? {};\n  let { get } = useAPIActions(context);\n\n  const _getAPIData = async function () {\n    if (route == null) {\n      return setResult(null);\n    }\n    const res = await get(route, params, rest);\n    return setResult(res);\n  };\n\n  const getAPIData =\n    _debounce != null ? debounce(_getAPIData, _debounce) : _getAPIData;\n\n  useAsyncEffect(getAPIData, deps);\n  return result;\n}\n\nexport {\n  createAPIContext,\n  useAxiosInstance,\n  APIContext,\n  APIProvider,\n  APIActions,\n  APIHelpers,\n  useAPIActions,\n  useAPIResult,\n  useAPIHelpers,\n};\n"],"names":["memoize","axios","createContext","h","useContext","useMemoizedValue","useState","debounce","useAsyncEffect"],"mappings":"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"}