@betterinternship/components 1.5.4 → 1.5.6
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.
|
@@ -4,6 +4,7 @@ export interface ProcessCallbackDto {
|
|
|
4
4
|
processCallbackUrl: string;
|
|
5
5
|
}
|
|
6
6
|
export interface ClientProcessOptions {
|
|
7
|
+
filter?: string;
|
|
7
8
|
onLoading?: (processId: string, processName: string) => void;
|
|
8
9
|
onSuccess?: <R>(processId: string, processName: string, result: R) => void;
|
|
9
10
|
onFailure?: (processId: string, processName: string, error?: string) => void;
|
|
@@ -29,11 +30,12 @@ export declare const ClientProcessesProvider: ({ children, }: {
|
|
|
29
30
|
* @param caller
|
|
30
31
|
* @returns
|
|
31
32
|
*/
|
|
32
|
-
export declare function useClientProcess<T
|
|
33
|
+
export declare function useClientProcess<T>({ caller, filterKey, onLoading, onSuccess, onFailure, }: {
|
|
33
34
|
caller: (inputs: T) => Promise<ProcessResponse>;
|
|
35
|
+
filterKey: string;
|
|
34
36
|
onLoading?: (processId: string, processName: string) => void;
|
|
35
|
-
onSuccess?: (processId: string, processName: string) => void;
|
|
36
|
-
onFailure?: (processId: string, processName: string) => void;
|
|
37
|
+
onSuccess?: <R>(processId: string, processName: string, result: R) => void;
|
|
38
|
+
onFailure?: (processId: string, processName: string, error?: string) => void;
|
|
37
39
|
}): {
|
|
38
40
|
run: (inputs: T) => Promise<{
|
|
39
41
|
id: string;
|
|
@@ -46,4 +48,11 @@ export declare function useClientProcess<T, R>({ caller, onLoading, onSuccess, o
|
|
|
46
48
|
result?: unknown;
|
|
47
49
|
success: boolean;
|
|
48
50
|
} | null;
|
|
51
|
+
getAll: () => {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
result?: unknown;
|
|
55
|
+
success: boolean;
|
|
56
|
+
}[];
|
|
57
|
+
changed: object;
|
|
49
58
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { j as jsxRuntimeExports } from '../_virtual/jsx-runtime.js';
|
|
3
|
-
import { createContext, useState, useRef, useContext } from 'react';
|
|
3
|
+
import { createContext, useState, useRef, useEffect, useContext } from 'react';
|
|
4
4
|
import { useQueries } from '@tanstack/react-query';
|
|
5
5
|
import { config } from '../config.js';
|
|
6
6
|
|
|
@@ -43,11 +43,12 @@ const clientProcessesContext = createContext({});
|
|
|
43
43
|
* @context
|
|
44
44
|
*/
|
|
45
45
|
const ClientProcessesProvider = ({ children, }) => {
|
|
46
|
+
const [changed, setChanged] = useState({});
|
|
46
47
|
const [clientProcessDtos, setClientProcessDtos] = useState({});
|
|
47
48
|
const clientProcessAttempts = useRef({});
|
|
48
49
|
const clientProcesses = useQueries({
|
|
49
50
|
queries: Object.entries(clientProcessDtos).map(([processId, { dto, options }]) => ({
|
|
50
|
-
queryKey: ['__process', processId],
|
|
51
|
+
queryKey: ['.__process', processId],
|
|
51
52
|
queryFn: async () => {
|
|
52
53
|
if (!processId)
|
|
53
54
|
throw new Error('No processId');
|
|
@@ -114,9 +115,26 @@ const ClientProcessesProvider = ({ children, }) => {
|
|
|
114
115
|
const getProcess = (processId) => {
|
|
115
116
|
return (clientProcesses.find((clientProcess) => clientProcess.data?.id === processId)?.data ?? null);
|
|
116
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Filters processes by the provided attribute.
|
|
120
|
+
*
|
|
121
|
+
* @param filter
|
|
122
|
+
*/
|
|
123
|
+
const filterProcesses = (filter) => {
|
|
124
|
+
return clientProcesses
|
|
125
|
+
.filter((clientProcess) => clientProcess.data?.id &&
|
|
126
|
+
clientProcessDtos[clientProcess.data.id].options?.filter === filter)
|
|
127
|
+
.map((clientProcess) => clientProcess.data)
|
|
128
|
+
.filter((clientProcess) => !!clientProcess);
|
|
129
|
+
};
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
setChanged({ ...changed });
|
|
132
|
+
}, [clientProcesses]);
|
|
117
133
|
return (jsxRuntimeExports.jsx(clientProcessesContext.Provider, { value: {
|
|
118
134
|
registerProcess,
|
|
119
135
|
getProcess,
|
|
136
|
+
filterProcesses,
|
|
137
|
+
changed,
|
|
120
138
|
}, children: children }));
|
|
121
139
|
};
|
|
122
140
|
/**
|
|
@@ -125,7 +143,7 @@ const ClientProcessesProvider = ({ children, }) => {
|
|
|
125
143
|
* @param caller
|
|
126
144
|
* @returns
|
|
127
145
|
*/
|
|
128
|
-
function useClientProcess({ caller, onLoading, onSuccess, onFailure, }) {
|
|
146
|
+
function useClientProcess({ caller, filterKey, onLoading, onSuccess, onFailure, }) {
|
|
129
147
|
const clientProcessesApi = useContext(clientProcessesContext);
|
|
130
148
|
// Note that the exposed function doesn't return process ids
|
|
131
149
|
// Client should be oblivious to those
|
|
@@ -146,6 +164,8 @@ function useClientProcess({ caller, onLoading, onSuccess, onFailure, }) {
|
|
|
146
164
|
return {
|
|
147
165
|
run: wrappedCaller,
|
|
148
166
|
get: (id) => clientProcessesApi.getProcess(id),
|
|
167
|
+
getAll: () => clientProcessesApi.filterProcesses(filterKey),
|
|
168
|
+
changed: clientProcessesApi.changed,
|
|
149
169
|
};
|
|
150
170
|
}
|
|
151
171
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClientProcess.js","sources":["../../src/hooks/useClientProcess.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useState, useRef, createContext, useContext } from 'react';\r\nimport { Query, useQueries } from '@tanstack/react-query';\r\nimport { config } from '../config';\r\
|
|
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 } 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\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 filter?: 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\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 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 * 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 options?: ClientProcessOptions,\r\n ) => void;\r\n getProcess: (\r\n processId: string,\r\n ) => { id: string; name: string; result?: unknown; success: boolean } | null;\r\n filterProcesses: (\r\n filter: string,\r\n ) => { id: string; name: string; result?: unknown; success: boolean }[];\r\n changed: object;\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 [changed, setChanged] = useState<object>({});\r\n const [clientProcessDtos, setClientProcessDtos] = useState<\r\n Record<string, { dto: ProcessCallbackDto; options?: ClientProcessOptions }>\r\n >({});\r\n const clientProcessAttempts = useRef<Record<string, number>>({});\r\n const clientProcesses = useQueries({\r\n queries: Object.entries(clientProcessDtos).map(\r\n ([processId, { dto, options }]) => ({\r\n queryKey: ['.__process', 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 id: process.id,\r\n name: process.name,\r\n success: true,\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 null;\r\n } else if (process.status === 'failed') {\r\n console.error(\r\n `Failed ${process.name} (${process.id}) - ${process.error}.`,\r\n );\r\n return { id: process.id, name: process.name, success: false };\r\n }\r\n\r\n // Return null since no response\r\n return null;\r\n },\r\n\r\n // Returns the next interval length to use to wait for fetch\r\n refetchInterval: (query: Query) => {\r\n const data = query.state.data;\r\n if (data !== null) 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 options?: ClientProcessOptions,\r\n ) => {\r\n setClientProcessDtos({\r\n ...clientProcessDtos,\r\n [processId]: { dto: processCallbackDto, 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 = (processId: string) => {\r\n return (\r\n clientProcesses.find(\r\n (clientProcess) => clientProcess.data?.id === processId,\r\n )?.data ?? null\r\n );\r\n };\r\n\r\n /**\r\n * Filters processes by the provided attribute.\r\n *\r\n * @param filter\r\n */\r\n const filterProcesses = (filter: string) => {\r\n return clientProcesses\r\n .filter(\r\n (clientProcess) =>\r\n clientProcess.data?.id &&\r\n clientProcessDtos[clientProcess.data.id].options?.filter === filter,\r\n )\r\n .map((clientProcess) => clientProcess.data)\r\n .filter((clientProcess) => !!clientProcess);\r\n };\r\n\r\n useEffect(() => {\r\n setChanged({ ...changed });\r\n }, [clientProcesses]);\r\n\r\n return (\r\n <clientProcessesContext.Provider\r\n value={{\r\n registerProcess,\r\n getProcess,\r\n filterProcesses,\r\n changed,\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 (inputs: T) => {\r\n const response = await caller(inputs);\r\n clientProcessesApi.registerProcess(response.processId, response, {\r\n onLoading,\r\n onSuccess,\r\n onFailure,\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 run: wrappedCaller,\r\n get: (id: string) => clientProcessesApi.getProcess(id),\r\n getAll: () => clientProcessesApi.filterProcesses(filterKey),\r\n changed: clientProcessesApi.changed,\r\n };\r\n}"],"names":[],"mappings":";;;;;;AAsCA;;;;;AAKG;AACH;AAEI;AACA;AACE;AACD;AACF;AAIH;;;;;AAKG;AACH;AAMI;AACA;AACE;AACD;AACD;;;;AAID;AAiBH;;;AAGG;AACH;AAIA;;;;AAIG;;;;AAUD;;;AAIM;;AAEE;AAAgB;;;AAIhB;AACE;;AAOA;AACA;;;;AAKE;;;;;AAMJ;AACE;AACA;AACA;;AACK;AACL;AAGA;;;AAIF;;;AAIF;AACE;;AACmB;;AAGnB;AACE;AAEF;AAKA;AACA;;AAEH;AAEJ;AAED;;;;;;AAMG;;AAMD;AACE;;AAED;AACH;AAEA;;;;;AAKG;AACH;;AAMA;AAEA;;;;AAIG;AACH;AACE;;AAIM;;;AAIR;;AAGE;AACF;AAEA;;;;;;AAYF;AAEA;;;;;AAKG;AACG;AAaJ;;;;AAKA;AACE;;;;;AAKC;;;;;;AAOH;;AAGE;;;;;AAKJ;;"}
|