@betterinternship/components 1.5.22 → 1.5.24
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;
|
|
@@ -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,32 @@ const ClientProcessesProvider = ({ children }) => {
|
|
|
174
174
|
/**
|
|
175
175
|
* Refreshes the cache and invalidates all related processes.
|
|
176
176
|
*/
|
|
177
|
-
const
|
|
177
|
+
const invalidateBasedOnResult = async (filter, invalidator) => {
|
|
178
178
|
await queryClient.invalidateQueries({
|
|
179
179
|
queryKey: ['.__process', filter],
|
|
180
180
|
exact: false,
|
|
181
181
|
});
|
|
182
|
+
const cachedClientProcesses = queryClient
|
|
183
|
+
.getQueryCache()
|
|
184
|
+
.findAll({ queryKey: ['.__process', filter], exact: false });
|
|
185
|
+
for (const query of cachedClientProcesses) {
|
|
186
|
+
const processId = query.queryKey[2];
|
|
187
|
+
if (typeof processId !== 'string')
|
|
188
|
+
continue;
|
|
189
|
+
const data = query.state.data;
|
|
190
|
+
if (!data.result)
|
|
191
|
+
continue;
|
|
192
|
+
// If invalidator says we gotta clear the cache, we clear it
|
|
193
|
+
if (invalidator && invalidator?.(data.result)) {
|
|
194
|
+
queryClient.removeQueries({ queryKey: query.queryKey });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
182
197
|
};
|
|
183
198
|
return (jsxRuntimeExports.jsx(clientProcessesContext.Provider, { value: {
|
|
184
199
|
registerProcess,
|
|
185
200
|
getProcess,
|
|
186
201
|
filterProcesses,
|
|
187
|
-
|
|
202
|
+
invalidateBasedOnResult,
|
|
188
203
|
}, children: children }));
|
|
189
204
|
};
|
|
190
205
|
/**
|
|
@@ -193,7 +208,7 @@ const ClientProcessesProvider = ({ children }) => {
|
|
|
193
208
|
* @param caller
|
|
194
209
|
* @returns
|
|
195
210
|
*/
|
|
196
|
-
function useClientProcess({ caller, filterKey, onLoading, onSuccess, onFailure, }) {
|
|
211
|
+
function useClientProcess({ caller, invalidator, filterKey, onLoading, onSuccess, onFailure, }) {
|
|
197
212
|
const clientProcessesApi = useContext(clientProcessesContext);
|
|
198
213
|
// Note that the exposed function doesn't return process ids
|
|
199
214
|
// Client should be oblivious to those
|
|
@@ -231,7 +246,7 @@ function useClientProcess({ caller, filterKey, onLoading, onSuccess, onFailure,
|
|
|
231
246
|
getAllFailed: () => clientProcessesApi
|
|
232
247
|
.filterProcesses(filterKey)
|
|
233
248
|
.filter((process) => !process.success),
|
|
234
|
-
refreshCache: () => clientProcessesApi.
|
|
249
|
+
refreshCache: () => clientProcessesApi.invalidateBasedOnResult(filterKey, invalidator),
|
|
235
250
|
};
|
|
236
251
|
}
|
|
237
252
|
|
|
@@ -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 ) => 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 = ({\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 = async <R,>(\r\n filter: string,\r\n invalidator?: (result: R) => boolean,\r\n ) => {\r\n await queryClient.invalidateQueries({\r\n queryKey: ['.__process', filter],\r\n exact: false,\r\n });\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;;;AAMC;AACA;AACD;;AAGE;AACA;AAEH;;;;AAIE;;;;;;;;AAQJ;AAEA;;;;;;AAYF;AAEA;;;;;AAKG;AACG;AAeJ;;;;;;;AAWI;;;;AAQG;;;;;;;;AAWH;AACA;AACA;;AAEJ;;AAGE;;;AAGA;;;AAIA;;;AAIA;;;;;AAOJ;;"}
|