{"version":3,"file":"components/Manifest/api.mjs","sources":["../../../src/components/Manifest/api.tsx"],"sourcesContent":["import { Constants, Manifest, SDK } from '@rsdoctor/types';\nimport { Skeleton, Spin } from 'antd';\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { isEqual } from 'es-toolkit/compat';\nimport { fetchManifest, useDataLoader } from '../../utils';\nimport { ComponentState } from '../../constants';\nimport { FailedStatus } from '../Status';\nimport { BaseDataLoader } from '../../utils/data/base';\nimport { Lodash } from '@rsdoctor/utils/common';\nimport { Client } from '@rsdoctor/types';\n\n/**\n * Checks if the current route is uploader route\n */\nfunction isUploaderRoute(): boolean {\n  if (typeof window === 'undefined') {\n    return false;\n  }\n  return window.location.hash.includes(Client.RsdoctorClientRoutes.Uploader);\n}\n\nexport type InferServerAPIBody<T> =\n  SDK.ServerAPI.InferRequestBodyType<T> extends void\n    ? {\n        // use `any` to avoid ts check when need not to define the body in this component.\n        body?: any;\n      }\n    : {\n        body: SDK.ServerAPI.InferRequestBodyType<T>;\n      };\n\ntype ServerAPIProviderProps<\n  T extends SDK.ServerAPI.API | SDK.ServerAPI.APIExtends,\n> = {\n  manifestLoader?: () => Promise<Manifest.RsdoctorManifestWithShardingFiles>;\n  api: T;\n  children: (response: SDK.ServerAPI.InferResponseType<T>) => React.JSX.Element;\n  fallbackComponent?: React.FC;\n  showSkeleton?: boolean;\n} & InferServerAPIBody<T>;\n\n/**\n * this component will request server api to the sdk of Rsdoctor in local development.\n * otherwise it will fallback to load full manifest for the data.\n *\n * @example usage\n * <ServerAPIProvider {...props}>\n *   {(response) => {\n *     <Component {...}></Component>\n *   }}\n * </ServerAPIProvider>\n */\nexport const ServerAPIProvider = <\n  T extends SDK.ServerAPI.API | SDK.ServerAPI.APIExtends,\n>(\n  props: ServerAPIProviderProps<T>,\n): React.JSX.Element | null => {\n  const {\n    manifestLoader = fetchManifest,\n    api,\n    children,\n    // default value of \"body\" must be \"null\" to avoid the response type not match when the socket received message.\n    body = null,\n    showSkeleton = true,\n    fallbackComponent,\n  } = props;\n  let promise: Promise<Manifest.RsdoctorManifestWithShardingFiles>;\n\n  const [manifest, setManifest] =\n    useState<Manifest.RsdoctorManifestWithShardingFiles>();\n  const [state, setState] = useState(ComponentState.Pending);\n  const [res, setRes] = useState({} as SDK.ServerAPI.InferResponseType<T>);\n\n  const { loader } = useDataLoader(manifest);\n\n  function init(loader: BaseDataLoader | void) {\n    if (window[Constants.WINDOW_RSDOCTOR_TAG]) {\n      executeLoader(loader);\n    } else {\n      promise = manifestLoader();\n      ensureManifest(promise).then(() => {\n        executeLoader(loader);\n      });\n    }\n  }\n\n  const update = useCallback(\n    ({ req, res: response }: SDK.ServerAPI.SocketResponseType<T>) => {\n      if (req.api === api) {\n        // body is equal.\n        // both two body are null or undefined.\n        if (\n          isEqual(req.body, body) ||\n          (Lodash.isNil(req.body) && Lodash.isNil(body))\n        ) {\n          if (!isEqual(res, response)) {\n            setRes(response);\n          }\n        }\n      }\n      // make sure update successful.\n      setState(ComponentState.Success);\n    },\n    [res, api, body],\n  );\n\n  useEffect(() => {\n    init(loader);\n  }, [loader, api, body]);\n\n  useEffect(() => {\n    if (!loader) return;\n    // add update event listener\n    loader.onDataUpdate(api, update);\n    return () => {\n      // remove update event when the component unmount.\n      loader.removeOnDataUpdate(api, update);\n    };\n  }, [loader, api, body]);\n\n  function ensureManifest(\n    pro: Promise<Manifest.RsdoctorManifestWithShardingFiles>,\n  ) {\n    return pro\n      .then((manifest) => {\n        setManifest(manifest);\n      })\n      .catch((err) => {\n        // If route is uploader, set empty manifest and continue instead of failing\n        if (isUploaderRoute()) {\n          console.warn(\n            '[ServerAPIProvider] Failed to load manifest for uploader route, continuing with empty manifest:',\n            err,\n          );\n          setState(ComponentState.Success);\n          return;\n        }\n        setState(ComponentState.Fail);\n        throw err;\n      });\n  }\n\n  function executeLoader(loader: BaseDataLoader | void) {\n    if (!loader) {\n      return;\n    }\n\n    const exts = Object.values(SDK.ServerAPI.APIExtends);\n\n    // extends api will wait for update only.\n    if (exts.includes(api as SDK.ServerAPI.APIExtends)) {\n      // extends api need to handle \"undefined\" response inside component.\n      setState(ComponentState.Success);\n      return;\n    }\n\n    loader\n      .loadAPI(api as SDK.ServerAPI.API, body)\n      .then((e) => {\n        // if (process.env.NODE_ENV === 'development') {\n        //   console.log('[ServerAPIProvider] props: ', e, api, loader);\n        // }\n        // maybe the data not prepared.\n        if (Lodash.isNil(e)) {\n          return;\n        }\n        setRes(e as SDK.ServerAPI.InferResponseType<T>);\n        setState(ComponentState.Success);\n      })\n      .catch((err) => {\n        console.error(err);\n        setState(ComponentState.Fail);\n      });\n  }\n\n  if (state === ComponentState.Pending) {\n    return showSkeleton ? <Skeleton active /> : null;\n  }\n\n  if (state === ComponentState.Fail) {\n    if (fallbackComponent)\n      return fallbackComponent as unknown as React.ReactElement;\n\n    return (\n      <FailedStatus\n        retry={() => {\n          setState(ComponentState.Pending);\n          promise = manifestLoader();\n          init();\n        }}\n      />\n    );\n  }\n\n  if (state === ComponentState.Updating) {\n    return <Spin spinning>{children(res)}</Spin>;\n  }\n\n  return children(res);\n};\n\nexport function withServerAPI<\n  T,\n  P extends keyof T,\n  A extends SDK.ServerAPI.API | SDK.ServerAPI.APIExtends,\n>({\n  Component,\n  api,\n  responsePropName,\n  body: bodyInRoot,\n  fallbackComponent,\n  showSkeleton,\n}: {\n  Component: React.FC<T>;\n  api: A;\n  responsePropName: P;\n  fallbackComponent?: React.FC;\n  showSkeleton?: boolean;\n} & Partial<Partial<InferServerAPIBody<A>>>): React.FC<\n  Omit<T, P> & Partial<InferServerAPIBody<A>>\n> {\n  return function _(props) {\n    const { body = bodyInRoot, ...rest } = props;\n\n    return (\n      // @ts-ignore\n      <ServerAPIProvider\n        api={api}\n        body={body}\n        showSkeleton={showSkeleton}\n        fallbackComponent={fallbackComponent}\n      >\n        {(res) => {\n          const _props = {\n            ...rest,\n            [responsePropName]: res,\n          } as T & React.JSX.IntrinsicAttributes;\n\n          return <Component {..._props} />;\n        }}\n      </ServerAPIProvider>\n    );\n  };\n}\n"],"names":["isUploaderRoute","window","Client","ServerAPIProvider","props","manifestLoader","fetchManifest","api","children","body","showSkeleton","fallbackComponent","promise","manifest","setManifest","useState","state","setState","ComponentState","res","setRes","loader","useDataLoader","init","Constants","executeLoader","ensureManifest","update","useCallback","req","response","isEqual","Lodash","useEffect","pro","err","console","exts","Object","SDK","e","Skeleton","FailedStatus","Spin","withServerAPI","Component","responsePropName","bodyInRoot","rest","_props"],"mappings":";;;;;;;;;AAcA,SAASA;IACP,IAAI,AAAkB,MAAlB,OAAOC,QACT,OAAO;IAET,OAAOA,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAACC,OAAO,oBAAoB,CAAC,QAAQ;AAC3E;AAiCO,MAAMC,oBAAoB,CAG/BC;IAEA,MAAM,EACJC,iBAAiBC,aAAa,EAC9BC,GAAG,EACHC,QAAQ,EAERC,OAAO,IAAI,EACXC,eAAe,IAAI,EACnBC,iBAAiB,EAClB,GAAGP;IACJ,IAAIQ;IAEJ,MAAM,CAACC,UAAUC,YAAY,GAC3BC;IACF,MAAM,CAACC,OAAOC,SAAS,GAAGF,SAASG,eAAe,OAAO;IACzD,MAAM,CAACC,KAAKC,OAAO,GAAGL,SAAS,CAAC;IAEhC,MAAM,EAAEM,MAAM,EAAE,GAAGC,cAAcT;IAEjC,SAASU,KAAKF,MAA6B;QACzC,IAAIpB,MAAM,CAACuB,UAAU,mBAAmB,CAAC,EACvCC,cAAcJ;aACT;YACLT,UAAUP;YACVqB,eAAed,SAAS,IAAI,CAAC;gBAC3Ba,cAAcJ;YAChB;QACF;IACF;IAEA,MAAMM,SAASC,YACb,CAAC,EAAEC,GAAG,EAAE,KAAKC,QAAQ,EAAuC;QAC1D,IAAID,IAAI,GAAG,KAAKtB,KAGd;YAAA,IACEwB,QAAQF,IAAI,IAAI,EAAEpB,SACjBuB,OAAO,KAAK,CAACH,IAAI,IAAI,KAAKG,OAAO,KAAK,CAACvB,OAExC;gBAAA,IAAI,CAACsB,QAAQZ,KAAKW,WAChBV,OAAOU;YACT;QACF;QAGFb,SAASC,eAAe,OAAO;IACjC,GACA;QAACC;QAAKZ;QAAKE;KAAK;IAGlBwB,UAAU;QACRV,KAAKF;IACP,GAAG;QAACA;QAAQd;QAAKE;KAAK;IAEtBwB,UAAU;QACR,IAAI,CAACZ,QAAQ;QAEbA,OAAO,YAAY,CAACd,KAAKoB;QACzB,OAAO;YAELN,OAAO,kBAAkB,CAACd,KAAKoB;QACjC;IACF,GAAG;QAACN;QAAQd;QAAKE;KAAK;IAEtB,SAASiB,eACPQ,GAAwD;QAExD,OAAOA,IACJ,IAAI,CAAC,CAACrB;YACLC,YAAYD;QACd,GACC,KAAK,CAAC,CAACsB;YAEN,IAAInC,mBAAmB;gBACrBoC,QAAQ,IAAI,CACV,mGACAD;gBAEFlB,SAASC,eAAe,OAAO;gBAC/B;YACF;YACAD,SAASC,eAAe,IAAI;YAC5B,MAAMiB;QACR;IACJ;IAEA,SAASV,cAAcJ,MAA6B;QAClD,IAAI,CAACA,QACH;QAGF,MAAMgB,OAAOC,OAAO,MAAM,CAACC,IAAI,SAAS,CAAC,UAAU;QAGnD,IAAIF,KAAK,QAAQ,CAAC9B,MAAkC,YAElDU,SAASC,eAAe,OAAO;QAIjCG,OACG,OAAO,CAACd,KAA0BE,MAClC,IAAI,CAAC,CAAC+B;YAKL,IAAIR,OAAO,KAAK,CAACQ,IACf;YAEFpB,OAAOoB;YACPvB,SAASC,eAAe,OAAO;QACjC,GACC,KAAK,CAAC,CAACiB;YACNC,QAAQ,KAAK,CAACD;YACdlB,SAASC,eAAe,IAAI;QAC9B;IACJ;IAEA,IAAIF,UAAUE,eAAe,OAAO,EAClC,OAAOR,eAAe,WAAfA,GAAe,IAAC+B,UAAQA;QAAC,QAAM;SAAM;IAG9C,IAAIzB,UAAUE,eAAe,IAAI,EAAE;QACjC,IAAIP,mBACF,OAAOA;QAET,OAAO,WAAP,GACE,IAAC+B,cAAYA;YACX,OAAO;gBACLzB,SAASC,eAAe,OAAO;gBAC/BN,UAAUP;gBACVkB;YACF;;IAGN;IAEA,IAAIP,UAAUE,eAAe,QAAQ,EACnC,OAAO,WAAP,GAAO,IAACyB,MAAIA;QAAC,UAAQ;kBAAEnC,SAASW;;IAGlC,OAAOX,SAASW;AAClB;AAEO,SAASyB,cAId,EACAC,SAAS,EACTtC,GAAG,EACHuC,gBAAgB,EAChB,MAAMC,UAAU,EAChBpC,iBAAiB,EACjBD,YAAY,EAO6B;IAGzC,OAAO,SAAWN,KAAK;QACrB,MAAM,EAAEK,OAAOsC,UAAU,EAAE,GAAGC,MAAM,GAAG5C;QAEvC,O,cAEE,IAACD,mBAAiBA;YAChB,KAAKI;YACL,MAAME;YACN,cAAcC;YACd,mBAAmBC;sBAElB,CAACQ;gBACA,MAAM8B,SAAS;oBACb,GAAGD,IAAI;oBACP,CAACF,iBAAiB,EAAE3B;gBACtB;gBAEA,OAAO,WAAP,GAAO,IAAC0B,WAAAA;oBAAW,GAAGI,MAAM;;YAC9B;;IAGN;AACF"}