@ventlio/tanstack-query 0.2.63 → 0.2.64
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.
- package/dist/config/bootstrapQueryRequest.d.ts +2 -2
- package/dist/config/bootstrapQueryRequest.js +1 -1
- package/dist/config/useEnvironmentVariables.js +2 -2
- package/dist/config/useReactNativeEnv.d.ts +1 -0
- package/dist/config/useReactNativeEnv.js +5 -16
- package/dist/config/useReactNativeEnv.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -30
- package/dist/index.mjs.map +1 -1
- package/dist/model/Model.d.ts +14 -0
- package/dist/model/Model.js +93 -0
- package/dist/model/Model.js.map +1 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/model.interface.d.ts +7 -0
- package/dist/model/useQueryModel.d.ts +2 -2
- package/dist/model/useQueryModel.js +84 -2
- package/dist/model/useQueryModel.js.map +1 -1
- package/dist/queries/useDeleteRequest.js +1 -1
- package/dist/queries/usePatchRequest.js +0 -1
- package/dist/queries/usePatchRequest.js.map +1 -1
- package/dist/queries/usePostRequest.d.ts +2 -1
- package/dist/queries/usePostRequest.js +7 -2
- package/dist/queries/usePostRequest.js.map +1 -1
- package/dist/request/make-request.d.ts +1 -1
- package/dist/request/make-request.js +41 -7
- package/dist/request/make-request.js.map +1 -1
- package/dist/request/request.interface.d.ts +5 -0
- package/dist/types/index.d.ts +8 -3
- package/package.json +8 -2
- package/src/config/bootstrapQueryRequest.ts +3 -3
- package/src/config/useEnvironmentVariables.ts +2 -2
- package/src/config/useReactNativeEnv.ts +5 -20
- package/src/model/Model.ts +107 -0
- package/src/model/index.ts +2 -0
- package/src/model/model.interface.ts +10 -0
- package/src/model/useQueryModel.ts +123 -3
- package/src/queries/useDeleteRequest.ts +1 -1
- package/src/queries/usePostRequest.ts +8 -2
- package/src/request/make-request.ts +42 -6
- package/src/request/request.interface.ts +5 -0
- package/src/types/index.ts +9 -3
|
@@ -41,7 +41,7 @@ const useDeleteRequest = (deleteOptions) => {
|
|
|
41
41
|
internalDeleteOptions = internalDeleteOptions ?? {};
|
|
42
42
|
internalDeleteOptions.enabled = true;
|
|
43
43
|
await updatedPathAsync(link);
|
|
44
|
-
await setOptionsAsync(
|
|
44
|
+
await setOptionsAsync(internalDeleteOptions);
|
|
45
45
|
return query.data;
|
|
46
46
|
};
|
|
47
47
|
return { destroy, ...query };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useMutation } from '@tanstack/react-query';
|
|
2
2
|
import { useEnvironmentVariables } from '../config/useEnvironmentVariables.js';
|
|
3
3
|
import { useQueryHeaders } from '../config/useQueryHeaders.js';
|
|
4
|
-
import 'react';
|
|
5
4
|
import { scrollToTop } from '../helpers/scrollToTop.js';
|
|
6
5
|
import 'axios';
|
|
7
6
|
import { makeRequest } from '../request/make-request.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePatchRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePatchRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { MutateOptions } from '@tanstack/react-query';
|
|
2
2
|
import type { IRequestError, IRequestSuccess } from '../request';
|
|
3
3
|
import type { DefaultRequestOptions } from './queries.interface';
|
|
4
|
-
export declare const usePostRequest: <TResponse>({ path, isFormData, baseUrl, headers, }: {
|
|
4
|
+
export declare const usePostRequest: <TResponse>({ path, isFormData, baseUrl, headers, fileSelectors, }: {
|
|
5
5
|
path: string;
|
|
6
6
|
isFormData?: boolean | undefined;
|
|
7
|
+
fileSelectors?: string[] | undefined;
|
|
7
8
|
} & DefaultRequestOptions) => {
|
|
8
9
|
data: undefined;
|
|
9
10
|
error: null;
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { useQueryClient, useMutation } from '@tanstack/react-query';
|
|
2
2
|
import { useEnvironmentVariables } from '../config/useEnvironmentVariables.js';
|
|
3
3
|
import { useQueryHeaders } from '../config/useQueryHeaders.js';
|
|
4
|
-
import '
|
|
4
|
+
import { useReactNativeEnv } from '../config/useReactNativeEnv.js';
|
|
5
5
|
import { scrollToTop } from '../helpers/scrollToTop.js';
|
|
6
6
|
import 'axios';
|
|
7
7
|
import { makeRequest } from '../request/make-request.js';
|
|
8
8
|
import { HttpMethod } from '../request/request.enum.js';
|
|
9
9
|
|
|
10
|
-
const usePostRequest = ({ path, isFormData = false, baseUrl, headers, }) => {
|
|
10
|
+
const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelectors, }) => {
|
|
11
11
|
const { API_URL, TIMEOUT } = useEnvironmentVariables();
|
|
12
12
|
const queryClient = useQueryClient();
|
|
13
13
|
const { getHeaders } = useQueryHeaders();
|
|
14
|
+
const { isApp } = useReactNativeEnv();
|
|
14
15
|
const sendRequest = async (res, rej, postData) => {
|
|
15
16
|
// get request headers
|
|
16
17
|
const globalHeaders = getHeaders();
|
|
@@ -23,6 +24,10 @@ const usePostRequest = ({ path, isFormData = false, baseUrl, headers, }) => {
|
|
|
23
24
|
headers: { ...globalHeaders, ...headers },
|
|
24
25
|
baseURL: baseUrl ?? API_URL,
|
|
25
26
|
timeout: TIMEOUT,
|
|
27
|
+
appFileConfig: {
|
|
28
|
+
isApp,
|
|
29
|
+
fileSelectors,
|
|
30
|
+
},
|
|
26
31
|
});
|
|
27
32
|
if (postResponse.status) {
|
|
28
33
|
// scroll to top after success
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePostRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePostRequest.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { IMakeRequest } from './request.interface';
|
|
2
|
-
export declare function makeRequest<TResponse>({ body, method, path, isFormData, headers, baseURL, timeout, }: IMakeRequest): Promise<import("./request.interface").IRequestError>;
|
|
2
|
+
export declare function makeRequest<TResponse>({ body, method, path, isFormData, headers, baseURL, timeout, appFileConfig, }: IMakeRequest): Promise<import("./request.interface").IRequestError>;
|
|
@@ -1,22 +1,43 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
1
2
|
import { axiosInstance } from './axios-instance.js';
|
|
2
|
-
import { buildFormData } from './buildFormData.js';
|
|
3
3
|
import { ContentType, HttpMethod } from './request.enum.js';
|
|
4
4
|
import { errorTransformer, successTransformer } from './transformer.js';
|
|
5
5
|
|
|
6
|
-
async function makeRequest({ body, method = HttpMethod.GET, path, isFormData, headers = {}, baseURL, timeout, }) {
|
|
6
|
+
async function makeRequest({ body, method = HttpMethod.GET, path, isFormData, headers = {}, baseURL, timeout, appFileConfig, }) {
|
|
7
|
+
// check if file is included in mobile app environment and extract all file input to avoid
|
|
8
|
+
// it being formatted to object using axios formData builder
|
|
9
|
+
const isApp = appFileConfig?.isApp;
|
|
10
|
+
const appFiles = isApp ? getAppFiles(body, appFileConfig.fileSelectors) : {};
|
|
7
11
|
// configure body
|
|
8
|
-
body = isFormData ?
|
|
9
|
-
// configure request
|
|
12
|
+
body = (isFormData ? axios.toFormData(body) : body);
|
|
13
|
+
// configure request header1
|
|
10
14
|
if (!isFormData) {
|
|
11
15
|
headers['Content-Type'] = ContentType.APPLICATION_JSON;
|
|
12
16
|
}
|
|
13
17
|
else {
|
|
14
|
-
|
|
18
|
+
if (isApp) {
|
|
19
|
+
headers['Content-Type'] = ContentType.MULTIPART_FORM_DATA;
|
|
20
|
+
// add the app files
|
|
21
|
+
for (const fileKey in appFiles) {
|
|
22
|
+
const currentFile = appFiles[fileKey];
|
|
23
|
+
if (Array.isArray(currentFile)) {
|
|
24
|
+
for (const innerFile of currentFile) {
|
|
25
|
+
body.append(fileKey, innerFile);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
body.append(fileKey, currentFile);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
delete headers['Content-Type'];
|
|
35
|
+
}
|
|
15
36
|
}
|
|
16
37
|
try {
|
|
17
|
-
const
|
|
38
|
+
const axiosRequest = axiosInstance({ baseURL, headers, timeout });
|
|
18
39
|
// send request
|
|
19
|
-
const resp = await
|
|
40
|
+
const resp = await axiosRequest({
|
|
20
41
|
url: path,
|
|
21
42
|
method,
|
|
22
43
|
data: body,
|
|
@@ -44,6 +65,19 @@ async function makeRequest({ body, method = HttpMethod.GET, path, isFormData, he
|
|
|
44
65
|
...errorData,
|
|
45
66
|
});
|
|
46
67
|
}
|
|
68
|
+
}
|
|
69
|
+
function getAppFiles(body, fileSelectors = []) {
|
|
70
|
+
const files = {};
|
|
71
|
+
if (body) {
|
|
72
|
+
if (fileSelectors.length > 0) {
|
|
73
|
+
//
|
|
74
|
+
for (const fileKey of fileSelectors) {
|
|
75
|
+
files[fileKey] = body[fileKey];
|
|
76
|
+
delete body[fileKey];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return files;
|
|
47
81
|
}
|
|
48
82
|
|
|
49
83
|
export { makeRequest };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-request.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"make-request.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -8,6 +8,11 @@ export interface IMakeRequest {
|
|
|
8
8
|
method?: HttpMethod;
|
|
9
9
|
isFormData?: boolean;
|
|
10
10
|
headers: RawAxiosRequestHeaders;
|
|
11
|
+
appFileConfig?: AppFileConfig;
|
|
12
|
+
}
|
|
13
|
+
export interface AppFileConfig {
|
|
14
|
+
fileSelectors?: string[];
|
|
15
|
+
isApp: boolean;
|
|
11
16
|
}
|
|
12
17
|
export interface AxiosInstanceOption {
|
|
13
18
|
bearerToken?: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { RawAxiosRequestHeaders } from 'axios';
|
|
2
|
-
export interface
|
|
2
|
+
export interface BootstrapConfig {
|
|
3
3
|
environments?: {
|
|
4
4
|
appBaseUrl: string;
|
|
5
5
|
appTimeout: number;
|
|
6
6
|
};
|
|
7
|
-
context?:
|
|
7
|
+
context?: ContextType;
|
|
8
|
+
modelConfig?: BootstrapModelConfig;
|
|
8
9
|
}
|
|
10
|
+
export interface BootstrapModelConfig {
|
|
11
|
+
idColumn: string;
|
|
12
|
+
}
|
|
13
|
+
export type ContextType = 'app' | 'web' | 'electronjs';
|
|
9
14
|
export interface TanstackQueryConfig {
|
|
10
15
|
headers: RawAxiosRequestHeaders;
|
|
11
|
-
options?:
|
|
16
|
+
options?: BootstrapConfig;
|
|
12
17
|
}
|
|
13
18
|
export interface IUseQueryHeaders {
|
|
14
19
|
getHeaders: () => TanstackQueryConfig['headers'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ventlio/tanstack-query",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.64",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"contributors": [
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"@testing-library/react-hooks": "^8.0.1",
|
|
52
52
|
"@types/axios": "^0.14.0",
|
|
53
53
|
"@types/jest": "^29.5.1",
|
|
54
|
+
"@types/lodash.result": "^4.5.7",
|
|
55
|
+
"@types/lodash.set": "^4.3.7",
|
|
54
56
|
"@types/node": "*",
|
|
55
57
|
"@types/react": "^18.2.0",
|
|
56
58
|
"@types/react-dom": "^18.2.0",
|
|
@@ -82,5 +84,9 @@
|
|
|
82
84
|
"files": [
|
|
83
85
|
"dist/**/*",
|
|
84
86
|
"src"
|
|
85
|
-
]
|
|
87
|
+
],
|
|
88
|
+
"dependencies": {
|
|
89
|
+
"lodash.result": "^4.5.2",
|
|
90
|
+
"lodash.set": "^4.3.2"
|
|
91
|
+
}
|
|
86
92
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { QueryClient } from '@tanstack/react-query';
|
|
2
|
-
import type {
|
|
2
|
+
import type { BootstrapConfig, TanstackQueryConfig } from '../types';
|
|
3
3
|
|
|
4
|
-
export const bootstrapQueryRequest = (queryClient: QueryClient, options?:
|
|
4
|
+
export const bootstrapQueryRequest = (queryClient: QueryClient, options?: BootstrapConfig): void => {
|
|
5
5
|
// make query config doesn't expire
|
|
6
6
|
queryClient.setQueryDefaults(['config'], {
|
|
7
7
|
staleTime: Infinity,
|
|
8
8
|
cacheTime: Infinity,
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
// set default query
|
|
11
|
+
// set default query config
|
|
12
12
|
queryClient.setQueryData<TanstackQueryConfig>(['config'], {
|
|
13
13
|
headers: {
|
|
14
14
|
Authorization: ``,
|
|
@@ -3,8 +3,8 @@ import { useReactNativeEnv } from './useReactNativeEnv';
|
|
|
3
3
|
|
|
4
4
|
export const useEnvironmentVariables = (): IConfig => {
|
|
5
5
|
const { appTimeout, appUrl } = useReactNativeEnv();
|
|
6
|
-
const url = process.env.REACT_APP_API_URL
|
|
7
|
-
const timeout = process.env.REACT_APP_API_TIMEOUT
|
|
6
|
+
const url = process.env.REACT_APP_API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? appUrl;
|
|
7
|
+
const timeout = process.env.REACT_APP_API_TIMEOUT ?? process.env.NEXT_PUBLIC_API_TIMEOUT ?? appTimeout;
|
|
8
8
|
|
|
9
9
|
return {
|
|
10
10
|
API_URL: url as string,
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
2
|
import type { TanstackQueryConfig } from '../types';
|
|
4
3
|
|
|
5
4
|
export const useReactNativeEnv = () => {
|
|
6
5
|
const queryClient = useQueryClient();
|
|
6
|
+
const config = queryClient.getQueryData<TanstackQueryConfig>(['config']);
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const appUrl: string | undefined = config?.options?.environments?.appBaseUrl;
|
|
9
|
+
const appTimeout: number | undefined = config?.options?.environments?.appTimeout;
|
|
10
|
+
const isApp = config?.options?.context === 'app';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
const config = queryClient.getQueryData<TanstackQueryConfig>(['config']);
|
|
13
|
-
|
|
14
|
-
const loadReactNativeEnvIfNeeded = async () => {
|
|
15
|
-
if (config?.options?.context === 'app') {
|
|
16
|
-
const API_URL = config.options.environments?.appBaseUrl;
|
|
17
|
-
const API_TIMEOUT = config.options.environments?.appTimeout;
|
|
18
|
-
|
|
19
|
-
setAppUrl(API_URL);
|
|
20
|
-
setAppTimeout(API_TIMEOUT);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
loadReactNativeEnvIfNeeded();
|
|
25
|
-
}, [queryClient]);
|
|
26
|
-
|
|
27
|
-
return { appUrl, appTimeout };
|
|
12
|
+
return { appUrl, appTimeout, isApp };
|
|
28
13
|
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import result from 'lodash.result';
|
|
3
|
+
import set from 'lodash.set';
|
|
4
|
+
import type { TanstackQueryConfig } from '../types';
|
|
5
|
+
import type { QueryModelBuilder } from './model.interface';
|
|
6
|
+
|
|
7
|
+
export class QueryModel<T> implements QueryModelBuilder<T> {
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly queryKey: any[],
|
|
10
|
+
private readonly queryClient: QueryClient,
|
|
11
|
+
private readonly exact: boolean = true
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
public findAll(path?: string): T[] | undefined {
|
|
15
|
+
const data = this.queryClient.getQueryData(this.queryKey, { exact: this.exact });
|
|
16
|
+
|
|
17
|
+
if (!data) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!path) {
|
|
22
|
+
return Array.isArray(data) ? data : [data];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result<T[]>(data, path, []);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public findMany(selector: (record: T) => boolean, path?: string): T[] {
|
|
29
|
+
const data = this.findAll(path) ?? [];
|
|
30
|
+
return data.filter(selector);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
find(id: string | number, path?: string): T | undefined {
|
|
34
|
+
const modelConfig = this.getModelConfig();
|
|
35
|
+
|
|
36
|
+
if (!modelConfig?.idColumn) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const data = this.findAll(path) ?? [];
|
|
40
|
+
|
|
41
|
+
return data.find((record) => (record as Record<string, any>)[modelConfig.idColumn] === id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
update(id: string | number, data: Partial<T>, path?: string): T | undefined {
|
|
45
|
+
const oldData = this.findAll(path) ?? [];
|
|
46
|
+
const modelConfig = this.getModelConfig();
|
|
47
|
+
|
|
48
|
+
if (!modelConfig?.idColumn) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const idColumn = modelConfig.idColumn;
|
|
52
|
+
|
|
53
|
+
let updatedRecord: T | undefined = undefined;
|
|
54
|
+
const newData = oldData.map((record) => {
|
|
55
|
+
let dataRecord = record as Record<string, any>;
|
|
56
|
+
|
|
57
|
+
if (dataRecord[idColumn] === id) {
|
|
58
|
+
dataRecord = { ...dataRecord, ...data };
|
|
59
|
+
updatedRecord = dataRecord as T;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return dataRecord;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!path) {
|
|
66
|
+
this.queryClient.setQueryData(this.queryKey, newData);
|
|
67
|
+
} else {
|
|
68
|
+
const queryData = this.queryClient.getQueryData(this.queryKey, { exact: this.exact }) ?? {};
|
|
69
|
+
this.queryClient.setQueryData(this.queryKey, set(queryData, path, newData));
|
|
70
|
+
}
|
|
71
|
+
return updatedRecord;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
remove(id: number | string, path?: string): boolean {
|
|
75
|
+
const oldData = this.findAll(path) ?? [];
|
|
76
|
+
const modelConfig = this.getModelConfig();
|
|
77
|
+
|
|
78
|
+
if (!modelConfig?.idColumn) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const idColumn = modelConfig.idColumn;
|
|
82
|
+
let updated = false;
|
|
83
|
+
const newData = oldData.filter((record) => {
|
|
84
|
+
const dataRecord = record as Record<string, any>;
|
|
85
|
+
if (dataRecord[idColumn] === id) {
|
|
86
|
+
updated = true;
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!path) {
|
|
93
|
+
this.queryClient.setQueryData(this.queryKey, newData);
|
|
94
|
+
} else {
|
|
95
|
+
const queryData = this.queryClient.getQueryData(this.queryKey, { exact: this.exact }) ?? {};
|
|
96
|
+
this.queryClient.setQueryData(this.queryKey, set(queryData, path, newData));
|
|
97
|
+
}
|
|
98
|
+
return updated;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private getModelConfig() {
|
|
102
|
+
const { options } = this.queryClient.getQueryData<TanstackQueryConfig>(['config']) ?? {};
|
|
103
|
+
const { modelConfig } = options ?? {};
|
|
104
|
+
|
|
105
|
+
return modelConfig;
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/model/index.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface QueryModelBuilder<T> {
|
|
2
|
+
add: (data: T, position?: QueryModelAddPosition, path?: string) => T | undefined;
|
|
3
|
+
findAll: (path?: string) => T[] | undefined;
|
|
4
|
+
findMany: (selector: (record: T) => boolean, path?: string) => T[];
|
|
5
|
+
find: (id: number | string, path?: string) => T | undefined;
|
|
6
|
+
update: (id: number | string, data: Partial<T>, path?: string) => T | undefined;
|
|
7
|
+
remove: (id: number, path?: string) => boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type QueryModelAddPosition = 'start' | 'end';
|
|
@@ -1,8 +1,128 @@
|
|
|
1
|
-
import type { QueryFilters } from '@tanstack/react-query';
|
|
2
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import result from 'lodash.result';
|
|
3
|
+
import set from 'lodash.set';
|
|
4
|
+
import type { TanstackQueryConfig } from '../types';
|
|
5
|
+
import type { QueryModelAddPosition, QueryModelBuilder } from './model.interface';
|
|
6
|
+
import { useKeyTrackerModel } from './useKeyTrackerModel';
|
|
3
7
|
|
|
4
|
-
export const useQueryModel = (
|
|
8
|
+
export const useQueryModel = <T>(keyTracker: string, exact: boolean = true): QueryModelBuilder<T> => {
|
|
5
9
|
const queryClient = useQueryClient();
|
|
10
|
+
const { getQueryKey } = useKeyTrackerModel(keyTracker);
|
|
11
|
+
const queryKey = getQueryKey() as any[];
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
const add = (data: T, position?: QueryModelAddPosition, path?: string): T | undefined => {
|
|
14
|
+
let records = findAll(path) ?? [];
|
|
15
|
+
|
|
16
|
+
if (!position || position === 'end') {
|
|
17
|
+
records = [...records, data];
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
19
|
+
} else if (position === 'start') {
|
|
20
|
+
records = [data, ...records];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!path) {
|
|
24
|
+
queryClient.setQueryData(queryKey, records);
|
|
25
|
+
} else {
|
|
26
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
27
|
+
queryClient.setQueryData(queryKey, set(queryData, path, records));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return data;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const findAll = (path?: string): T[] | undefined => {
|
|
34
|
+
const data = queryClient.getQueryData(queryKey, { exact });
|
|
35
|
+
|
|
36
|
+
if (!data) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!path) {
|
|
41
|
+
return Array.isArray(data) ? data : [data];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return result<T[]>(data, path, []);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const findMany = (selector: (record: T) => boolean, path?: string): T[] => {
|
|
48
|
+
const data = findAll(path) ?? [];
|
|
49
|
+
return data.filter(selector);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const find = (id: string | number, path?: string): T | undefined => {
|
|
53
|
+
const modelConfig = getModelConfig();
|
|
54
|
+
|
|
55
|
+
if (!modelConfig?.idColumn) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const data = findAll(path) ?? [];
|
|
59
|
+
|
|
60
|
+
return data.find((record) => (record as Record<string, any>)[modelConfig.idColumn] === id);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getModelConfig = () => {
|
|
64
|
+
const { options } = queryClient.getQueryData<TanstackQueryConfig>(['config']) ?? {};
|
|
65
|
+
const { modelConfig } = options ?? {};
|
|
66
|
+
|
|
67
|
+
return modelConfig;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const update = (id: string | number, data: Partial<T>, path?: string): T | undefined => {
|
|
71
|
+
const oldData = findAll(path) ?? [];
|
|
72
|
+
const modelConfig = getModelConfig();
|
|
73
|
+
|
|
74
|
+
if (!modelConfig?.idColumn) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const idColumn = modelConfig.idColumn;
|
|
78
|
+
|
|
79
|
+
let updatedRecord: T | undefined = undefined;
|
|
80
|
+
const newData = oldData.map((record) => {
|
|
81
|
+
let dataRecord = record as Record<string, any>;
|
|
82
|
+
|
|
83
|
+
if (dataRecord[idColumn] === id) {
|
|
84
|
+
dataRecord = { ...dataRecord, ...data };
|
|
85
|
+
updatedRecord = dataRecord as T;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return dataRecord;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!path) {
|
|
92
|
+
queryClient.setQueryData(queryKey, newData);
|
|
93
|
+
} else {
|
|
94
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
95
|
+
queryClient.setQueryData(queryKey, set(queryData, path, newData));
|
|
96
|
+
}
|
|
97
|
+
return updatedRecord;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const remove = (id: number | string, path?: string): boolean => {
|
|
101
|
+
const oldData = findAll(path) ?? [];
|
|
102
|
+
const modelConfig = getModelConfig();
|
|
103
|
+
|
|
104
|
+
if (!modelConfig?.idColumn) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const idColumn = modelConfig.idColumn;
|
|
108
|
+
let updated = false;
|
|
109
|
+
const newData = oldData.filter((record) => {
|
|
110
|
+
const dataRecord = record as Record<string, any>;
|
|
111
|
+
if (dataRecord[idColumn] === id) {
|
|
112
|
+
updated = true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!path) {
|
|
119
|
+
queryClient.setQueryData(queryKey, newData);
|
|
120
|
+
} else {
|
|
121
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
122
|
+
queryClient.setQueryData(queryKey, set(queryData, path, newData));
|
|
123
|
+
}
|
|
124
|
+
return updated;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return { find, findAll, findMany, remove, update, add };
|
|
8
128
|
};
|
|
@@ -63,7 +63,7 @@ export const useDeleteRequest = <TResponse>(deleteOptions?: DefaultRequestOption
|
|
|
63
63
|
internalDeleteOptions.enabled = true;
|
|
64
64
|
|
|
65
65
|
await updatedPathAsync(link);
|
|
66
|
-
await setOptionsAsync(
|
|
66
|
+
await setOptionsAsync(internalDeleteOptions);
|
|
67
67
|
|
|
68
68
|
return query.data;
|
|
69
69
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MutateOptions } from '@tanstack/react-query';
|
|
2
2
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
3
|
-
import { useEnvironmentVariables, useQueryHeaders } from '../config';
|
|
3
|
+
import { useEnvironmentVariables, useQueryHeaders, useReactNativeEnv } from '../config';
|
|
4
4
|
|
|
5
5
|
import type { RawAxiosRequestHeaders } from 'axios';
|
|
6
6
|
import { scrollToTop } from '../helpers';
|
|
@@ -14,14 +14,16 @@ export const usePostRequest = <TResponse>({
|
|
|
14
14
|
isFormData = false,
|
|
15
15
|
baseUrl,
|
|
16
16
|
headers,
|
|
17
|
+
fileSelectors,
|
|
17
18
|
}: {
|
|
18
19
|
path: string;
|
|
19
20
|
isFormData?: boolean;
|
|
21
|
+
fileSelectors?: string[];
|
|
20
22
|
} & DefaultRequestOptions) => {
|
|
21
23
|
const { API_URL, TIMEOUT } = useEnvironmentVariables();
|
|
22
24
|
const queryClient = useQueryClient();
|
|
23
25
|
const { getHeaders } = useQueryHeaders();
|
|
24
|
-
|
|
26
|
+
const { isApp } = useReactNativeEnv();
|
|
25
27
|
const sendRequest = async (res: (value: any) => void, rej: (reason?: any) => void, postData: any) => {
|
|
26
28
|
// get request headers
|
|
27
29
|
const globalHeaders: RawAxiosRequestHeaders = getHeaders();
|
|
@@ -35,6 +37,10 @@ export const usePostRequest = <TResponse>({
|
|
|
35
37
|
headers: { ...globalHeaders, ...headers },
|
|
36
38
|
baseURL: baseUrl ?? API_URL,
|
|
37
39
|
timeout: TIMEOUT,
|
|
40
|
+
appFileConfig: {
|
|
41
|
+
isApp,
|
|
42
|
+
fileSelectors,
|
|
43
|
+
},
|
|
38
44
|
});
|
|
39
45
|
|
|
40
46
|
if (postResponse.status) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
1
2
|
import { axiosInstance } from './axios-instance';
|
|
2
3
|
|
|
3
|
-
import { buildFormData } from './buildFormData';
|
|
4
4
|
import { ContentType, HttpMethod } from './request.enum';
|
|
5
5
|
import type { IMakeRequest } from './request.interface';
|
|
6
6
|
import { errorTransformer, successTransformer } from './transformer';
|
|
@@ -13,22 +13,43 @@ export async function makeRequest<TResponse>({
|
|
|
13
13
|
headers = {},
|
|
14
14
|
baseURL,
|
|
15
15
|
timeout,
|
|
16
|
+
appFileConfig,
|
|
16
17
|
}: IMakeRequest) {
|
|
18
|
+
// check if file is included in mobile app environment and extract all file input to avoid
|
|
19
|
+
// it being formatted to object using axios formData builder
|
|
20
|
+
const isApp = appFileConfig?.isApp;
|
|
21
|
+
const appFiles: Record<string, string> = isApp ? getAppFiles(body, appFileConfig.fileSelectors) : {};
|
|
22
|
+
|
|
17
23
|
// configure body
|
|
18
|
-
body = isFormData ?
|
|
24
|
+
body = (isFormData ? axios.toFormData(body as FormData) : body) as FormData;
|
|
19
25
|
|
|
20
|
-
// configure request
|
|
26
|
+
// configure request header1
|
|
21
27
|
if (!isFormData) {
|
|
22
28
|
headers['Content-Type'] = ContentType.APPLICATION_JSON;
|
|
23
29
|
} else {
|
|
24
|
-
|
|
30
|
+
if (isApp) {
|
|
31
|
+
headers['Content-Type'] = ContentType.MULTIPART_FORM_DATA;
|
|
32
|
+
// add the app files
|
|
33
|
+
for (const fileKey in appFiles) {
|
|
34
|
+
const currentFile = appFiles[fileKey];
|
|
35
|
+
if (Array.isArray(currentFile)) {
|
|
36
|
+
for (const innerFile of currentFile) {
|
|
37
|
+
body.append(fileKey, innerFile);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
body.append(fileKey, currentFile);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
delete headers['Content-Type'];
|
|
45
|
+
}
|
|
25
46
|
}
|
|
26
47
|
|
|
27
48
|
try {
|
|
28
|
-
const
|
|
49
|
+
const axiosRequest = axiosInstance({ baseURL, headers, timeout });
|
|
29
50
|
|
|
30
51
|
// send request
|
|
31
|
-
const resp = await
|
|
52
|
+
const resp = await axiosRequest({
|
|
32
53
|
url: path,
|
|
33
54
|
method,
|
|
34
55
|
data: body,
|
|
@@ -60,3 +81,18 @@ export async function makeRequest<TResponse>({
|
|
|
60
81
|
});
|
|
61
82
|
}
|
|
62
83
|
}
|
|
84
|
+
function getAppFiles(body: any, fileSelectors: string[] = []) {
|
|
85
|
+
const files: Record<string, string> = {};
|
|
86
|
+
|
|
87
|
+
if (body) {
|
|
88
|
+
if (fileSelectors.length > 0) {
|
|
89
|
+
//
|
|
90
|
+
for (const fileKey of fileSelectors) {
|
|
91
|
+
files[fileKey] = body[fileKey];
|
|
92
|
+
delete body[fileKey];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return files;
|
|
98
|
+
}
|