@betterinternship/components 1.5.22 → 1.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,7 +33,7 @@ export interface ProcessResponse {
33
33
  *
34
34
  * @context
35
35
  */
36
- export declare const ClientProcessesProvider: ({ children }: {
36
+ export declare const ClientProcessesProvider: ({ children, }: {
37
37
  children: React.ReactNode;
38
38
  }) => import("react/jsx-runtime").JSX.Element;
39
39
  /**
@@ -42,8 +42,9 @@ export declare const ClientProcessesProvider: ({ children }: {
42
42
  * @param caller
43
43
  * @returns
44
44
  */
45
- export declare function useClientProcess<T>({ caller, filterKey, onLoading, onSuccess, onFailure, }: {
45
+ export declare function useClientProcess<T, R>({ caller, invalidator, filterKey, onLoading, onSuccess, onFailure, }: {
46
46
  caller?: (inputs: T) => Promise<ProcessResponse>;
47
+ invalidator?: (result: R) => boolean;
47
48
  filterKey: string;
48
49
  onLoading?: (processId: string, processName: string) => void;
49
50
  onSuccess?: <R>(processId: string, processName: string, result: R) => void;
@@ -63,6 +64,6 @@ export declare function useClientProcess<T>({ caller, filterKey, onLoading, onSu
63
64
  getAllPending: () => ClientProcess<unknown>[];
64
65
  getAllHandled: () => ClientProcess<unknown>[];
65
66
  getAllFailed: () => ClientProcess<unknown>[];
66
- refreshCache: () => Promise<void>;
67
+ refreshCache: () => void;
67
68
  };
68
69
  export {};
@@ -46,7 +46,7 @@ const clientProcessesContext = createContext({});
46
46
  *
47
47
  * @context
48
48
  */
49
- const ClientProcessesProvider = ({ children }) => {
49
+ const ClientProcessesProvider = ({ children, }) => {
50
50
  const queryClient = useQueryClient();
51
51
  const clientProcessAttempts = useRef({});
52
52
  const [clientProcessDtos, setClientProcessDtos] = useState({});
@@ -174,17 +174,28 @@ const ClientProcessesProvider = ({ children }) => {
174
174
  /**
175
175
  * Refreshes the cache and invalidates all related processes.
176
176
  */
177
- const refreshCache = async (filter) => {
178
- await queryClient.invalidateQueries({
179
- queryKey: ['.__process', filter],
180
- exact: false,
181
- });
177
+ const invalidateBasedOnResult = (filter, invalidator) => {
178
+ const cachedClientProcesses = queryClient
179
+ .getQueryCache()
180
+ .findAll({ queryKey: ['.__process', filter], exact: false });
181
+ for (const query of cachedClientProcesses) {
182
+ const processId = query.queryKey[2];
183
+ if (typeof processId !== 'string')
184
+ continue;
185
+ const data = query.state.data;
186
+ if (!data.result)
187
+ continue;
188
+ // If invalidator says we gotta clear the cache, we clear it
189
+ if (invalidator && invalidator?.(data.result)) {
190
+ queryClient.removeQueries({ queryKey: query.queryKey });
191
+ }
192
+ }
182
193
  };
183
194
  return (jsxRuntimeExports.jsx(clientProcessesContext.Provider, { value: {
184
195
  registerProcess,
185
196
  getProcess,
186
197
  filterProcesses,
187
- refreshCache,
198
+ invalidateBasedOnResult,
188
199
  }, children: children }));
189
200
  };
190
201
  /**
@@ -193,7 +204,7 @@ const ClientProcessesProvider = ({ children }) => {
193
204
  * @param caller
194
205
  * @returns
195
206
  */
196
- function useClientProcess({ caller, filterKey, onLoading, onSuccess, onFailure, }) {
207
+ function useClientProcess({ caller, invalidator, filterKey, onLoading, onSuccess, onFailure, }) {
197
208
  const clientProcessesApi = useContext(clientProcessesContext);
198
209
  // Note that the exposed function doesn't return process ids
199
210
  // Client should be oblivious to those
@@ -231,7 +242,7 @@ function useClientProcess({ caller, filterKey, onLoading, onSuccess, onFailure,
231
242
  getAllFailed: () => clientProcessesApi
232
243
  .filterProcesses(filterKey)
233
244
  .filter((process) => !process.success),
234
- refreshCache: () => clientProcessesApi.refreshCache(filterKey),
245
+ refreshCache: () => clientProcessesApi.invalidateBasedOnResult(filterKey, invalidator),
235
246
  };
236
247
  }
237
248
 
@@ -1 +1 @@
1
- {"version":3,"file":"useClientProcess.js","sources":["../../src/hooks/useClientProcess.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useState, useRef, createContext, useContext, useEffect } from 'react';\r\nimport { Query, useQueries, useQueryClient } from '@tanstack/react-query';\r\nimport { config } from '../config';\r\n\r\ntype ProcessStatus = 'pending' | 'handled' | 'failed';\r\n\r\ninterface Process<T> {\r\n id: string;\r\n name: string;\r\n status: ProcessStatus;\r\n result?: T;\r\n error?: string;\r\n timestamp: string;\r\n}\r\n\r\ninterface ClientProcess<T> {\r\n dto?: ProcessCallbackDto;\r\n metadata?: ClientProcessMetadata;\r\n options?: ClientProcessOptions;\r\n result?: T;\r\n success?: boolean;\r\n pending?: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface ProcessCallbackDto {\r\n processId: string;\r\n processName: string;\r\n processCallbackUrl: string;\r\n}\r\n\r\nexport interface ClientProcessOptions {\r\n onLoading?: (processId: string, processName: string) => void;\r\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\r\n onFailure?: (processId: string, processName: string, error?: string) => void;\r\n}\r\n\r\nexport interface ClientProcessMetadata {\r\n filterKey: string;\r\n metadata?: Record<string, string>;\r\n}\r\n\r\nexport interface ProcessResponse {\r\n processId: string;\r\n processName: string;\r\n processCallbackUrl: string;\r\n success: boolean;\r\n message?: string;\r\n}\r\n\r\n/**\r\n * The orchestrator service to get the process from.\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\nconst processStatusChecker = <T,>(processId: string) => {\r\n if (!processId) return { process: null };\r\n return fetch(`${config.orchestratorApi}/${encodeURIComponent(processId)}`, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n }).then((response) => response.json()) as unknown as Promise<{\r\n process: Omit<Process<T>, 'result'>;\r\n }>;\r\n};\r\n\r\n/**\r\n * This fetches from the callback endpoint.\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\nconst processCallbackResultFetcher = <T,>(\r\n processId: string,\r\n processName: string,\r\n processCallbackUrl?: string,\r\n) =>\r\n fetch(`${processCallbackUrl}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n processId,\r\n processName,\r\n }),\r\n }).then((response) => response.json()) as unknown as Promise<T>;\r\n\r\ninterface ClientProcesses {\r\n registerProcess: (\r\n processId: string,\r\n dto: ProcessCallbackDto,\r\n metadata: ClientProcessMetadata,\r\n options?: ClientProcessOptions,\r\n ) => void;\r\n getProcess: <T>(processId: string) => ClientProcess<T> | null;\r\n filterProcesses: <T>(filter: string) => ClientProcess<T>[];\r\n refreshCache: (filter: string) => Promise<void>;\r\n}\r\n\r\n/**\r\n * We need a context to keep track of all client processes.\r\n * This helps us manage the state of the toasts as well.\r\n */\r\nconst clientProcessesContext = createContext<ClientProcesses>(\r\n {} as ClientProcesses,\r\n);\r\n\r\n/**\r\n * This should be at the root layout.\r\n *\r\n * @context\r\n */\r\nexport const ClientProcessesProvider = ({ children }: { children: React.ReactNode }) => {\r\n const queryClient = useQueryClient();\r\n const clientProcessAttempts = useRef<Record<string, number>>({});\r\n const [clientProcessDtos, setClientProcessDtos] = useState<\r\n Record<\r\n string,\r\n {\r\n dto: ProcessCallbackDto;\r\n metadata: ClientProcessMetadata;\r\n options?: ClientProcessOptions;\r\n }\r\n >\r\n >({});\r\n\r\n // Populates the state object with cached data on mount\r\n useEffect(() => {\r\n setClientProcessDtos((oldData) => {\r\n if (Object.keys(oldData).length > 0) return oldData;\r\n\r\n const cachedClientProcesses = queryClient\r\n .getQueryCache()\r\n .findAll({ queryKey: ['.__process'], exact: false });\r\n const seededClientProcesses: typeof oldData = {};\r\n\r\n for (const query of cachedClientProcesses) {\r\n const processId = query.queryKey[2];\r\n if (typeof processId !== 'string') continue;\r\n\r\n const data = query.state.data as ClientProcess<any>;\r\n if (!data?.dto || !data?.metadata) continue;\r\n\r\n seededClientProcesses[processId] = {\r\n dto: data.dto,\r\n metadata: data.metadata,\r\n options: data.options,\r\n };\r\n }\r\n\r\n return { ...oldData, ...seededClientProcesses };\r\n });\r\n }, [queryClient]);\r\n\r\n // The query for all the processes, pending and sht\r\n const clientProcesses = useQueries({\r\n queries: Object.entries(clientProcessDtos).map(\r\n ([processId, { dto, metadata, options }]) => ({\r\n staleTime: Infinity,\r\n gcTime: 3 * 60 * 1000,\r\n refetchOnWindowFocus: true,\r\n refetchOnMount: true,\r\n refetchOnReconnect: true,\r\n queryKey: ['.__process', metadata.filterKey, processId],\r\n queryFn: async () => {\r\n if (!processId) throw new Error('No processId');\r\n const { process } = await processStatusChecker(processId);\r\n\r\n // Handled! Return the proper response too\r\n if (process?.status === 'handled') {\r\n const result = await processCallbackResultFetcher(\r\n process.id,\r\n process.name,\r\n dto.processCallbackUrl,\r\n );\r\n\r\n // Log and call runner\r\n console.warn(`Handled ${process.name} (${process.id})!`);\r\n options?.onSuccess?.(process.id, process.name, result);\r\n\r\n return {\r\n dto,\r\n metadata,\r\n options,\r\n success: true,\r\n pending: false,\r\n result,\r\n };\r\n }\r\n\r\n // Pending or failed\r\n if (process?.status === 'pending') {\r\n console.log(`Running process ${process.name} (${process.id})...`);\r\n options?.onLoading?.(process.id, process.name);\r\n return { dto, metadata, options, pending: true };\r\n } else if (process?.status === 'failed') {\r\n console.error(\r\n `Failed ${process.name} (${process.id}) - ${process.error}.`,\r\n );\r\n\r\n return {\r\n dto,\r\n metadata,\r\n options,\r\n success: false,\r\n pending: false,\r\n error: process.error,\r\n };\r\n }\r\n\r\n // Return null since no response\r\n return { dto, metadata, options, pending: true };\r\n },\r\n\r\n // Returns the next interval length to use to wait for fetch\r\n refetchInterval: <T,>(query: Query) => {\r\n const data = query.state.data as ClientProcess<T>;\r\n if (data?.success !== undefined) return false;\r\n\r\n // If not defined, init to 0\r\n if (!clientProcessAttempts.current[processId])\r\n clientProcessAttempts.current[processId] = 0;\r\n\r\n const interval = Math.min(\r\n 500 * 2 ** clientProcessAttempts.current[processId],\r\n 10000,\r\n );\r\n\r\n clientProcessAttempts.current[processId] += 1;\r\n return interval;\r\n },\r\n }),\r\n ),\r\n });\r\n\r\n /**\r\n * Registers a new process to handle\r\n *\r\n * @param processId\r\n * @param processCallbackDto\r\n * @param options\r\n */\r\n const registerProcess = (\r\n processId: string,\r\n processCallbackDto: ProcessCallbackDto,\r\n metadata: ClientProcessMetadata,\r\n options?: ClientProcessOptions,\r\n ) => {\r\n setClientProcessDtos({\r\n ...clientProcessDtos,\r\n [processId]: { dto: processCallbackDto, metadata, options },\r\n });\r\n };\r\n\r\n /**\r\n * Retrieves a process\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\n const getProcess = <T,>(processId: string) => {\r\n const clientProcess =\r\n clientProcesses.find(\r\n (clientProcess) => clientProcess.data?.dto?.processId === processId,\r\n )?.data ?? null;\r\n\r\n return (clientProcess as ClientProcess<T>) ?? null;\r\n };\r\n\r\n /**\r\n * Filters processes by the provided attribute.\r\n *\r\n * @param filterKey\r\n */\r\n const filterProcesses = <T,>(filterKey: string) => {\r\n return clientProcesses\r\n .filter((clientProcess) => !!clientProcess.data?.dto)\r\n .map((clientProcess) => clientProcess.data)\r\n .filter(\r\n (clientProcessData) =>\r\n clientProcessData?.metadata.filterKey === filterKey,\r\n ) as ClientProcess<T>[];\r\n };\r\n\r\n /**\r\n * Refreshes the cache and invalidates all related processes.\r\n */\r\n const refreshCache = async (filter: string) => {\r\n await queryClient.invalidateQueries({\r\n queryKey: ['.__process', filter],\r\n exact: false,\r\n });\r\n };\r\n\r\n return (\r\n <clientProcessesContext.Provider\r\n value={{\r\n registerProcess,\r\n getProcess,\r\n filterProcesses,\r\n refreshCache,\r\n }}\r\n >\r\n {children}\r\n </clientProcessesContext.Provider>\r\n );\r\n};;;\r\n\r\n/**\r\n * A simpler version, where u just pass the async function that returns process data.\r\n *\r\n * @param caller\r\n * @returns\r\n */\r\nexport function useClientProcess<T>({\r\n caller,\r\n filterKey,\r\n onLoading,\r\n onSuccess,\r\n onFailure,\r\n}: {\r\n caller?: (inputs: T) => Promise<ProcessResponse>;\r\n filterKey: string;\r\n onLoading?: (processId: string, processName: string) => void;\r\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\r\n onFailure?: (processId: string, processName: string, error?: string) => void;\r\n}) {\r\n const clientProcessesApi = useContext(clientProcessesContext);\r\n\r\n // Note that the exposed function doesn't return process ids\r\n // Client should be oblivious to those\r\n // Abstraction BABY!\r\n const wrappedCaller = async (\r\n inputs: T,\r\n metadata?: Record<string, string>,\r\n ) => {\r\n const response = await caller?.(inputs);\r\n if (response) {\r\n clientProcessesApi.registerProcess(\r\n response.processId,\r\n response,\r\n { filterKey, metadata },\r\n {\r\n onLoading,\r\n onSuccess,\r\n onFailure,\r\n },\r\n );\r\n\r\n return {\r\n id: response.processId,\r\n success: response.success,\r\n message: response.message,\r\n };\r\n }\r\n\r\n return {\r\n id: null,\r\n success: false,\r\n message: 'No `caller` specified for useClientProcess().',\r\n };\r\n };\r\n\r\n return {\r\n run: wrappedCaller,\r\n get: (id: string) => clientProcessesApi.getProcess(id),\r\n getAll: () => clientProcessesApi.filterProcesses(filterKey),\r\n getAllPending: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => process.pending),\r\n getAllHandled: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => process.success),\r\n getAllFailed: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => !process.success),\r\n refreshCache: () => clientProcessesApi.refreshCache(filterKey),\r\n };\r\n}"],"names":[],"mappings":";;;;;;AAoDA;;;;;AAKG;AACH;AACE;AAAgB;AAChB;AACE;AACA;AACE;AACD;AACF;AAGH;AAEA;;;;;AAKG;AACH;AAMI;AACA;AACE;AACD;AACD;;;;AAID;AAcH;;;AAGG;AACH;AAIA;;;;AAIG;;AAED;AACA;;;;AAcE;;AACuC;;AAGlC;AACA;;AAGH;;;;AAIE;;;;;;;;;AAUF;AACF;AACF;;;;AAMM;AACA;AACA;AACA;AACA;;;AAGE;AAAgB;;;AAIhB;AACE;;AAOA;AACA;;;;;AAME;AACA;;;;;AAMJ;AACE;AACA;;;AAEK;AACL;;;;;AAQE;AACA;;;;;;;;AAUN;AACE;AACA;AAAiC;;AAGjC;AACE;AAEF;AAKA;AACA;;AAEH;AAEJ;AAED;;;;;;AAMG;;AAOD;AACE;;AAED;AACH;AAEA;;;;;AAKG;AACH;;;AAOA;AAEA;;;;AAIG;AACH;AACE;AACG;;AAEA;AAIL;AAEA;;AAEG;AACH;;AAEI;AACA;AACD;AACH;AAEA;;;;;;AAYF;AAEA;;;;;AAKG;AACG;AAaJ;;;;;;;AAWI;;;;AAQG;;;;;;;;AAWH;AACA;AACA;;AAEJ;;AAGE;;;AAGA;;;AAIA;;;AAIA;;;;;AAMJ;;"}
1
+ {"version":3,"file":"useClientProcess.js","sources":["../../src/hooks/useClientProcess.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useState, useRef, createContext, useContext, useEffect } from 'react';\r\nimport { Query, useQueries, useQueryClient } from '@tanstack/react-query';\r\nimport { config } from '../config';\r\n\r\ntype ProcessStatus = 'pending' | 'handled' | 'failed';\r\n\r\ninterface Process<T> {\r\n id: string;\r\n name: string;\r\n status: ProcessStatus;\r\n result?: T;\r\n error?: string;\r\n timestamp: string;\r\n}\r\n\r\ninterface ClientProcess<T> {\r\n dto?: ProcessCallbackDto;\r\n metadata?: ClientProcessMetadata;\r\n options?: ClientProcessOptions;\r\n result?: T;\r\n success?: boolean;\r\n pending?: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface ProcessCallbackDto {\r\n processId: string;\r\n processName: string;\r\n processCallbackUrl: string;\r\n}\r\n\r\nexport interface ClientProcessOptions {\r\n onLoading?: (processId: string, processName: string) => void;\r\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\r\n onFailure?: (processId: string, processName: string, error?: string) => void;\r\n}\r\n\r\nexport interface ClientProcessMetadata {\r\n filterKey: string;\r\n metadata?: Record<string, string>;\r\n}\r\n\r\nexport interface ProcessResponse {\r\n processId: string;\r\n processName: string;\r\n processCallbackUrl: string;\r\n success: boolean;\r\n message?: string;\r\n}\r\n\r\n/**\r\n * The orchestrator service to get the process from.\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\nconst processStatusChecker = <T,>(processId: string) => {\r\n if (!processId) return { process: null };\r\n return fetch(`${config.orchestratorApi}/${encodeURIComponent(processId)}`, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n }).then((response) => response.json()) as unknown as Promise<{\r\n process: Omit<Process<T>, 'result'>;\r\n }>;\r\n};\r\n\r\n/**\r\n * This fetches from the callback endpoint.\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\nconst processCallbackResultFetcher = <T,>(\r\n processId: string,\r\n processName: string,\r\n processCallbackUrl?: string,\r\n) =>\r\n fetch(`${processCallbackUrl}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n processId,\r\n processName,\r\n }),\r\n }).then((response) => response.json()) as unknown as Promise<T>;\r\n\r\ninterface ClientProcesses {\r\n registerProcess: (\r\n processId: string,\r\n dto: ProcessCallbackDto,\r\n metadata: ClientProcessMetadata,\r\n options?: ClientProcessOptions,\r\n ) => void;\r\n getProcess: <T>(processId: string) => ClientProcess<T> | null;\r\n filterProcesses: <T>(filter: string) => ClientProcess<T>[];\r\n invalidateBasedOnResult: <R>(\r\n filter: string,\r\n invalidator?: (result: R) => boolean,\r\n ) => void;\r\n}\r\n\r\n/**\r\n * We need a context to keep track of all client processes.\r\n * This helps us manage the state of the toasts as well.\r\n */\r\nconst clientProcessesContext = createContext<ClientProcesses>(\r\n {} as ClientProcesses,\r\n);\r\n\r\n/**\r\n * This should be at the root layout.\r\n *\r\n * @context\r\n */\r\nexport const ClientProcessesProvider = ({\r\n children,\r\n}: {\r\n children: React.ReactNode;\r\n}) => {\r\n const queryClient = useQueryClient();\r\n const clientProcessAttempts = useRef<Record<string, number>>({});\r\n const [clientProcessDtos, setClientProcessDtos] = useState<\r\n Record<\r\n string,\r\n {\r\n dto: ProcessCallbackDto;\r\n metadata: ClientProcessMetadata;\r\n options?: ClientProcessOptions;\r\n }\r\n >\r\n >({});\r\n\r\n // Populates the state object with cached data on mount\r\n useEffect(() => {\r\n setClientProcessDtos((oldData) => {\r\n if (Object.keys(oldData).length > 0) return oldData;\r\n\r\n const cachedClientProcesses = queryClient\r\n .getQueryCache()\r\n .findAll({ queryKey: ['.__process'], exact: false });\r\n const seededClientProcesses: typeof oldData = {};\r\n\r\n for (const query of cachedClientProcesses) {\r\n const processId = query.queryKey[2];\r\n if (typeof processId !== 'string') continue;\r\n\r\n const data = query.state.data as ClientProcess<any>;\r\n if (!data?.dto || !data?.metadata) continue;\r\n\r\n seededClientProcesses[processId] = {\r\n dto: data.dto,\r\n metadata: data.metadata,\r\n options: data.options,\r\n };\r\n }\r\n\r\n return { ...oldData, ...seededClientProcesses };\r\n });\r\n }, [queryClient]);\r\n\r\n // The query for all the processes, pending and sht\r\n const clientProcesses = useQueries({\r\n queries: Object.entries(clientProcessDtos).map(\r\n ([processId, { dto, metadata, options }]) => ({\r\n staleTime: Infinity,\r\n gcTime: 3 * 60 * 1000,\r\n refetchOnWindowFocus: true,\r\n refetchOnMount: true,\r\n refetchOnReconnect: true,\r\n queryKey: ['.__process', metadata.filterKey, processId],\r\n queryFn: async () => {\r\n if (!processId) throw new Error('No processId');\r\n const { process } = await processStatusChecker(processId);\r\n\r\n // Handled! Return the proper response too\r\n if (process?.status === 'handled') {\r\n const result = await processCallbackResultFetcher(\r\n process.id,\r\n process.name,\r\n dto.processCallbackUrl,\r\n );\r\n\r\n // Log and call runner\r\n console.warn(`Handled ${process.name} (${process.id})!`);\r\n options?.onSuccess?.(process.id, process.name, result);\r\n\r\n return {\r\n dto,\r\n metadata,\r\n options,\r\n success: true,\r\n pending: false,\r\n result,\r\n };\r\n }\r\n\r\n // Pending or failed\r\n if (process?.status === 'pending') {\r\n console.log(`Running process ${process.name} (${process.id})...`);\r\n options?.onLoading?.(process.id, process.name);\r\n return { dto, metadata, options, pending: true };\r\n } else if (process?.status === 'failed') {\r\n console.error(\r\n `Failed ${process.name} (${process.id}) - ${process.error}.`,\r\n );\r\n\r\n return {\r\n dto,\r\n metadata,\r\n options,\r\n success: false,\r\n pending: false,\r\n error: process.error,\r\n };\r\n }\r\n\r\n // Return null since no response\r\n return { dto, metadata, options, pending: true };\r\n },\r\n\r\n // Returns the next interval length to use to wait for fetch\r\n refetchInterval: <T,>(query: Query) => {\r\n const data = query.state.data as ClientProcess<T>;\r\n if (data?.success !== undefined) return false;\r\n\r\n // If not defined, init to 0\r\n if (!clientProcessAttempts.current[processId])\r\n clientProcessAttempts.current[processId] = 0;\r\n\r\n const interval = Math.min(\r\n 500 * 2 ** clientProcessAttempts.current[processId],\r\n 10000,\r\n );\r\n\r\n clientProcessAttempts.current[processId] += 1;\r\n return interval;\r\n },\r\n }),\r\n ),\r\n });\r\n\r\n /**\r\n * Registers a new process to handle\r\n *\r\n * @param processId\r\n * @param processCallbackDto\r\n * @param options\r\n */\r\n const registerProcess = (\r\n processId: string,\r\n processCallbackDto: ProcessCallbackDto,\r\n metadata: ClientProcessMetadata,\r\n options?: ClientProcessOptions,\r\n ) => {\r\n setClientProcessDtos({\r\n ...clientProcessDtos,\r\n [processId]: { dto: processCallbackDto, metadata, options },\r\n });\r\n };\r\n\r\n /**\r\n * Retrieves a process\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\n const getProcess = <T,>(processId: string) => {\r\n const clientProcess =\r\n clientProcesses.find(\r\n (clientProcess) => clientProcess.data?.dto?.processId === processId,\r\n )?.data ?? null;\r\n\r\n return (clientProcess as ClientProcess<T>) ?? null;\r\n };\r\n\r\n /**\r\n * Filters processes by the provided attribute.\r\n *\r\n * @param filterKey\r\n */\r\n const filterProcesses = <T,>(filterKey: string) => {\r\n return clientProcesses\r\n .filter((clientProcess) => !!clientProcess.data?.dto)\r\n .map((clientProcess) => clientProcess.data)\r\n .filter(\r\n (clientProcessData) =>\r\n clientProcessData?.metadata.filterKey === filterKey,\r\n ) as ClientProcess<T>[];\r\n };\r\n\r\n /**\r\n * Refreshes the cache and invalidates all related processes.\r\n */\r\n const invalidateBasedOnResult = <R,>(\r\n filter: string,\r\n invalidator?: (result: R) => boolean,\r\n ) => {\r\n const cachedClientProcesses = queryClient\r\n .getQueryCache()\r\n .findAll({ queryKey: ['.__process', filter], exact: false });\r\n\r\n for (const query of cachedClientProcesses) {\r\n const processId = query.queryKey[2];\r\n if (typeof processId !== 'string') continue;\r\n\r\n const data = query.state.data as ClientProcess<any>;\r\n if (!data.result) continue;\r\n\r\n // If invalidator says we gotta clear the cache, we clear it\r\n if (invalidator && invalidator?.(data.result as R)) {\r\n queryClient.removeQueries({ queryKey: query.queryKey });\r\n }\r\n }\r\n };\r\n\r\n return (\r\n <clientProcessesContext.Provider\r\n value={{\r\n registerProcess,\r\n getProcess,\r\n filterProcesses,\r\n invalidateBasedOnResult,\r\n }}\r\n >\r\n {children}\r\n </clientProcessesContext.Provider>\r\n );\r\n};\r\n\r\n/**\r\n * A simpler version, where u just pass the async function that returns process data.\r\n *\r\n * @param caller\r\n * @returns\r\n */\r\nexport function useClientProcess<T, R>({\r\n caller,\r\n invalidator,\r\n filterKey,\r\n onLoading,\r\n onSuccess,\r\n onFailure,\r\n}: {\r\n caller?: (inputs: T) => Promise<ProcessResponse>;\r\n invalidator?: (result: R) => boolean;\r\n filterKey: string;\r\n onLoading?: (processId: string, processName: string) => void;\r\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\r\n onFailure?: (processId: string, processName: string, error?: string) => void;\r\n}) {\r\n const clientProcessesApi = useContext(clientProcessesContext);\r\n\r\n // Note that the exposed function doesn't return process ids\r\n // Client should be oblivious to those\r\n // Abstraction BABY!\r\n const wrappedCaller = async (\r\n inputs: T,\r\n metadata?: Record<string, string>,\r\n ) => {\r\n const response = await caller?.(inputs);\r\n if (response) {\r\n clientProcessesApi.registerProcess(\r\n response.processId,\r\n response,\r\n { filterKey, metadata },\r\n {\r\n onLoading,\r\n onSuccess,\r\n onFailure,\r\n },\r\n );\r\n\r\n return {\r\n id: response.processId,\r\n success: response.success,\r\n message: response.message,\r\n };\r\n }\r\n\r\n return {\r\n id: null,\r\n success: false,\r\n message: 'No `caller` specified for useClientProcess().',\r\n };\r\n };\r\n\r\n return {\r\n run: wrappedCaller,\r\n get: (id: string) => clientProcessesApi.getProcess(id),\r\n getAll: () => clientProcessesApi.filterProcesses(filterKey),\r\n getAllPending: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => process.pending),\r\n getAllHandled: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => process.success),\r\n getAllFailed: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => !process.success),\r\n refreshCache: () =>\r\n clientProcessesApi.invalidateBasedOnResult(filterKey, invalidator),\r\n };\r\n}"],"names":[],"mappings":";;;;;;AAoDA;;;;;AAKG;AACH;AACE;AAAgB;AAChB;AACE;AACA;AACE;AACD;AACF;AAGH;AAEA;;;;;AAKG;AACH;AAMI;AACA;AACE;AACD;AACD;;;;AAID;AAiBH;;;AAGG;AACH;AAIA;;;;AAIG;;AAMD;AACA;;;;AAcE;;AACuC;;AAGlC;AACA;;AAGH;;;;AAIE;;;;;;;;;AAUF;AACF;AACF;;;;AAMM;AACA;AACA;AACA;AACA;;;AAGE;AAAgB;;;AAIhB;AACE;;AAOA;AACA;;;;;AAME;AACA;;;;;AAMJ;AACE;AACA;;;AAEK;AACL;;;;;AAQE;AACA;;;;;;;;AAUN;AACE;AACA;AAAiC;;AAGjC;AACE;AAEF;AAKA;AACA;;AAEH;AAEJ;AAED;;;;;;AAMG;;AAOD;AACE;;AAED;AACH;AAEA;;;;;AAKG;AACH;;;AAOA;AAEA;;;;AAIG;AACH;AACE;AACG;;AAEA;AAIL;AAEA;;AAEG;AACH;;AAKK;AACA;AAEH;;;;AAIE;;;;;;;;AAQJ;AAEA;;;;;;AAYF;AAEA;;;;;AAKG;AACG;AAeJ;;;;;;;AAWI;;;;AAQG;;;;;;;;AAWH;AACA;AACA;;AAEJ;;AAGE;;;AAGA;;;AAIA;;;AAIA;;;;;AAOJ;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterinternship/components",
3
- "version": "1.5.22",
3
+ "version": "1.5.23",
4
4
  "main": "dist/index.esm.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",