@ventlio/tanstack-query 0.2.63 → 0.2.65
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.mjs +157 -29
- package/dist/index.mjs.map +1 -1
- package/dist/model/index.d.ts +1 -0
- package/dist/model/model.interface.d.ts +9 -0
- package/dist/model/useQueryModel.d.ts +2 -2
- package/dist/model/useQueryModel.js +102 -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/index.ts +1 -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
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { QueryClient } from '@tanstack/react-query';
|
|
2
|
-
import type {
|
|
3
|
-
export declare const bootstrapQueryRequest: (queryClient: QueryClient, options?:
|
|
2
|
+
import type { BootstrapConfig } from '../types';
|
|
3
|
+
export declare const bootstrapQueryRequest: (queryClient: QueryClient, options?: BootstrapConfig) => void;
|
|
@@ -2,8 +2,8 @@ import { useReactNativeEnv } from './useReactNativeEnv.js';
|
|
|
2
2
|
|
|
3
3
|
const useEnvironmentVariables = () => {
|
|
4
4
|
const { appTimeout, appUrl } = useReactNativeEnv();
|
|
5
|
-
const url = process.env.REACT_APP_API_URL
|
|
6
|
-
const timeout = process.env.REACT_APP_API_TIMEOUT
|
|
5
|
+
const url = process.env.REACT_APP_API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? appUrl;
|
|
6
|
+
const timeout = process.env.REACT_APP_API_TIMEOUT ?? process.env.NEXT_PUBLIC_API_TIMEOUT ?? appTimeout;
|
|
7
7
|
return {
|
|
8
8
|
API_URL: url,
|
|
9
9
|
TIMEOUT: Number(timeout),
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
2
|
|
|
4
3
|
const useReactNativeEnv = () => {
|
|
5
4
|
const queryClient = useQueryClient();
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (config?.options?.context === 'app') {
|
|
12
|
-
const API_URL = config.options.environments?.appBaseUrl;
|
|
13
|
-
const API_TIMEOUT = config.options.environments?.appTimeout;
|
|
14
|
-
setAppUrl(API_URL);
|
|
15
|
-
setAppTimeout(API_TIMEOUT);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
loadReactNativeEnvIfNeeded();
|
|
19
|
-
}, [queryClient]);
|
|
20
|
-
return { appUrl, appTimeout };
|
|
5
|
+
const config = queryClient.getQueryData(['config']);
|
|
6
|
+
const appUrl = config?.options?.environments?.appBaseUrl;
|
|
7
|
+
const appTimeout = config?.options?.environments?.appTimeout;
|
|
8
|
+
const isApp = config?.options?.context === 'app';
|
|
9
|
+
return { appUrl, appTimeout, isApp };
|
|
21
10
|
};
|
|
22
11
|
|
|
23
12
|
export { useReactNativeEnv };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactNativeEnv.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useReactNativeEnv.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
|
|
2
|
-
import
|
|
2
|
+
import result from 'lodash.result';
|
|
3
|
+
import set from 'lodash.set';
|
|
4
|
+
import { useState, useMemo, useEffect, startTransition } from 'react';
|
|
3
5
|
import axios from 'axios';
|
|
4
6
|
|
|
5
7
|
const bootstrapQueryRequest = (queryClient, options) => {
|
|
@@ -8,7 +10,7 @@ const bootstrapQueryRequest = (queryClient, options) => {
|
|
|
8
10
|
staleTime: Infinity,
|
|
9
11
|
cacheTime: Infinity,
|
|
10
12
|
});
|
|
11
|
-
// set default query
|
|
13
|
+
// set default query config
|
|
12
14
|
queryClient.setQueryData(['config'], {
|
|
13
15
|
headers: {
|
|
14
16
|
Authorization: ``,
|
|
@@ -19,27 +21,17 @@ const bootstrapQueryRequest = (queryClient, options) => {
|
|
|
19
21
|
|
|
20
22
|
const useReactNativeEnv = () => {
|
|
21
23
|
const queryClient = useQueryClient();
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (config?.options?.context === 'app') {
|
|
28
|
-
const API_URL = config.options.environments?.appBaseUrl;
|
|
29
|
-
const API_TIMEOUT = config.options.environments?.appTimeout;
|
|
30
|
-
setAppUrl(API_URL);
|
|
31
|
-
setAppTimeout(API_TIMEOUT);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
loadReactNativeEnvIfNeeded();
|
|
35
|
-
}, [queryClient]);
|
|
36
|
-
return { appUrl, appTimeout };
|
|
24
|
+
const config = queryClient.getQueryData(['config']);
|
|
25
|
+
const appUrl = config?.options?.environments?.appBaseUrl;
|
|
26
|
+
const appTimeout = config?.options?.environments?.appTimeout;
|
|
27
|
+
const isApp = config?.options?.context === 'app';
|
|
28
|
+
return { appUrl, appTimeout, isApp };
|
|
37
29
|
};
|
|
38
30
|
|
|
39
31
|
const useEnvironmentVariables = () => {
|
|
40
32
|
const { appTimeout, appUrl } = useReactNativeEnv();
|
|
41
|
-
const url = process.env.REACT_APP_API_URL
|
|
42
|
-
const timeout = process.env.REACT_APP_API_TIMEOUT
|
|
33
|
+
const url = process.env.REACT_APP_API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? appUrl;
|
|
34
|
+
const timeout = process.env.REACT_APP_API_TIMEOUT ?? process.env.NEXT_PUBLIC_API_TIMEOUT ?? appTimeout;
|
|
43
35
|
return {
|
|
44
36
|
API_URL: url,
|
|
45
37
|
TIMEOUT: Number(timeout),
|
|
@@ -107,9 +99,106 @@ const useKeyTrackerModel = (keyTracker) => {
|
|
|
107
99
|
return { refetchQuery, getQueryKey };
|
|
108
100
|
};
|
|
109
101
|
|
|
110
|
-
const useQueryModel = (
|
|
102
|
+
const useQueryModel = (keyTracker, exact = true) => {
|
|
111
103
|
const queryClient = useQueryClient();
|
|
112
|
-
|
|
104
|
+
const { getQueryKey } = useKeyTrackerModel(keyTracker);
|
|
105
|
+
const queryKey = getQueryKey();
|
|
106
|
+
const add = (data, position, path) => {
|
|
107
|
+
let records = findAll(path) ?? [];
|
|
108
|
+
if (!position || position === 'end') {
|
|
109
|
+
records = [...records, data];
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
111
|
+
}
|
|
112
|
+
else if (position === 'start') {
|
|
113
|
+
records = [data, ...records];
|
|
114
|
+
}
|
|
115
|
+
if (!path) {
|
|
116
|
+
queryClient.setQueryData(queryKey, records);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
120
|
+
queryClient.setQueryData(queryKey, set(queryData, path, records));
|
|
121
|
+
}
|
|
122
|
+
return data;
|
|
123
|
+
};
|
|
124
|
+
const findAll = (path) => {
|
|
125
|
+
const data = queryClient.getQueryData(queryKey, { exact });
|
|
126
|
+
if (!data) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
if (!path) {
|
|
130
|
+
return Array.isArray(data) ? data : [data];
|
|
131
|
+
}
|
|
132
|
+
return result(data, path, []);
|
|
133
|
+
};
|
|
134
|
+
const findMany = (selector, path) => {
|
|
135
|
+
const data = findAll(path) ?? [];
|
|
136
|
+
return data.filter(selector);
|
|
137
|
+
};
|
|
138
|
+
const find = (id, path) => {
|
|
139
|
+
const modelConfig = getModelConfig();
|
|
140
|
+
if (!modelConfig?.idColumn) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
const data = findAll(path) ?? [];
|
|
144
|
+
return data.find((record) => record[modelConfig.idColumn] === id);
|
|
145
|
+
};
|
|
146
|
+
const getModelConfig = () => {
|
|
147
|
+
const { options } = queryClient.getQueryData(['config']) ?? {};
|
|
148
|
+
const { modelConfig } = options ?? {};
|
|
149
|
+
return modelConfig;
|
|
150
|
+
};
|
|
151
|
+
const update = (id, data, path) => {
|
|
152
|
+
const oldData = findAll(path) ?? [];
|
|
153
|
+
const modelConfig = getModelConfig();
|
|
154
|
+
if (!modelConfig?.idColumn) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
const idColumn = modelConfig.idColumn;
|
|
158
|
+
let updatedRecord = undefined;
|
|
159
|
+
const newData = oldData.map((record) => {
|
|
160
|
+
let dataRecord = record;
|
|
161
|
+
if (dataRecord[idColumn] === id) {
|
|
162
|
+
dataRecord = { ...dataRecord, ...data };
|
|
163
|
+
updatedRecord = dataRecord;
|
|
164
|
+
}
|
|
165
|
+
return dataRecord;
|
|
166
|
+
});
|
|
167
|
+
if (!path) {
|
|
168
|
+
queryClient.setQueryData(queryKey, newData);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
172
|
+
queryClient.setQueryData(queryKey, set(queryData, path, newData));
|
|
173
|
+
}
|
|
174
|
+
return updatedRecord;
|
|
175
|
+
};
|
|
176
|
+
const remove = (id, path) => {
|
|
177
|
+
const oldData = findAll(path) ?? [];
|
|
178
|
+
const modelConfig = getModelConfig();
|
|
179
|
+
if (!modelConfig?.idColumn) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const idColumn = modelConfig.idColumn;
|
|
183
|
+
let updated = false;
|
|
184
|
+
const newData = oldData.filter((record) => {
|
|
185
|
+
const dataRecord = record;
|
|
186
|
+
if (dataRecord[idColumn] === id) {
|
|
187
|
+
updated = true;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
});
|
|
192
|
+
if (!path) {
|
|
193
|
+
queryClient.setQueryData(queryKey, newData);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
197
|
+
queryClient.setQueryData(queryKey, set(queryData, path, newData));
|
|
198
|
+
}
|
|
199
|
+
return updated;
|
|
200
|
+
};
|
|
201
|
+
return { find, findAll, findMany, remove, update, add };
|
|
113
202
|
};
|
|
114
203
|
|
|
115
204
|
const useRefetchQuery = async (queryKey) => {
|
|
@@ -209,20 +298,41 @@ const successTransformer = (data) => {
|
|
|
209
298
|
};
|
|
210
299
|
};
|
|
211
300
|
|
|
212
|
-
async function makeRequest({ body, method = HttpMethod.GET, path, isFormData, headers = {}, baseURL, timeout, }) {
|
|
301
|
+
async function makeRequest({ body, method = HttpMethod.GET, path, isFormData, headers = {}, baseURL, timeout, appFileConfig, }) {
|
|
302
|
+
// check if file is included in mobile app environment and extract all file input to avoid
|
|
303
|
+
// it being formatted to object using axios formData builder
|
|
304
|
+
const isApp = appFileConfig?.isApp;
|
|
305
|
+
const appFiles = isApp ? getAppFiles(body, appFileConfig.fileSelectors) : {};
|
|
213
306
|
// configure body
|
|
214
|
-
body = isFormData ?
|
|
215
|
-
// configure request
|
|
307
|
+
body = (isFormData ? axios.toFormData(body) : body);
|
|
308
|
+
// configure request header1
|
|
216
309
|
if (!isFormData) {
|
|
217
310
|
headers['Content-Type'] = ContentType.APPLICATION_JSON;
|
|
218
311
|
}
|
|
219
312
|
else {
|
|
220
|
-
|
|
313
|
+
if (isApp) {
|
|
314
|
+
headers['Content-Type'] = ContentType.MULTIPART_FORM_DATA;
|
|
315
|
+
// add the app files
|
|
316
|
+
for (const fileKey in appFiles) {
|
|
317
|
+
const currentFile = appFiles[fileKey];
|
|
318
|
+
if (Array.isArray(currentFile)) {
|
|
319
|
+
for (const innerFile of currentFile) {
|
|
320
|
+
body.append(fileKey, innerFile);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
body.append(fileKey, currentFile);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
delete headers['Content-Type'];
|
|
330
|
+
}
|
|
221
331
|
}
|
|
222
332
|
try {
|
|
223
|
-
const
|
|
333
|
+
const axiosRequest = axiosInstance({ baseURL, headers, timeout });
|
|
224
334
|
// send request
|
|
225
|
-
const resp = await
|
|
335
|
+
const resp = await axiosRequest({
|
|
226
336
|
url: path,
|
|
227
337
|
method,
|
|
228
338
|
data: body,
|
|
@@ -250,6 +360,19 @@ async function makeRequest({ body, method = HttpMethod.GET, path, isFormData, he
|
|
|
250
360
|
...errorData,
|
|
251
361
|
});
|
|
252
362
|
}
|
|
363
|
+
}
|
|
364
|
+
function getAppFiles(body, fileSelectors = []) {
|
|
365
|
+
const files = {};
|
|
366
|
+
if (body) {
|
|
367
|
+
if (fileSelectors.length > 0) {
|
|
368
|
+
//
|
|
369
|
+
for (const fileKey of fileSelectors) {
|
|
370
|
+
files[fileKey] = body[fileKey];
|
|
371
|
+
delete body[fileKey];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return files;
|
|
253
376
|
}
|
|
254
377
|
|
|
255
378
|
const useDeleteRequest = (deleteOptions) => {
|
|
@@ -287,7 +410,7 @@ const useDeleteRequest = (deleteOptions) => {
|
|
|
287
410
|
internalDeleteOptions = internalDeleteOptions ?? {};
|
|
288
411
|
internalDeleteOptions.enabled = true;
|
|
289
412
|
await updatedPathAsync(link);
|
|
290
|
-
await setOptionsAsync(
|
|
413
|
+
await setOptionsAsync(internalDeleteOptions);
|
|
291
414
|
return query.data;
|
|
292
415
|
};
|
|
293
416
|
return { destroy, ...query };
|
|
@@ -430,10 +553,11 @@ const usePatchRequest = ({ path, baseUrl, headers }) => {
|
|
|
430
553
|
return { patch, ...mutation };
|
|
431
554
|
};
|
|
432
555
|
|
|
433
|
-
const usePostRequest = ({ path, isFormData = false, baseUrl, headers, }) => {
|
|
556
|
+
const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelectors, }) => {
|
|
434
557
|
const { API_URL, TIMEOUT } = useEnvironmentVariables();
|
|
435
558
|
const queryClient = useQueryClient();
|
|
436
559
|
const { getHeaders } = useQueryHeaders();
|
|
560
|
+
const { isApp } = useReactNativeEnv();
|
|
437
561
|
const sendRequest = async (res, rej, postData) => {
|
|
438
562
|
// get request headers
|
|
439
563
|
const globalHeaders = getHeaders();
|
|
@@ -446,6 +570,10 @@ const usePostRequest = ({ path, isFormData = false, baseUrl, headers, }) => {
|
|
|
446
570
|
headers: { ...globalHeaders, ...headers },
|
|
447
571
|
baseURL: baseUrl ?? API_URL,
|
|
448
572
|
timeout: TIMEOUT,
|
|
573
|
+
appFileConfig: {
|
|
574
|
+
isApp,
|
|
575
|
+
fileSelectors,
|
|
576
|
+
},
|
|
449
577
|
});
|
|
450
578
|
if (postResponse.status) {
|
|
451
579
|
// scroll to top after success
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/model/index.d.ts
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export type QueryModelAddPosition = 'start' | 'end';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const useQueryModel: (
|
|
1
|
+
import type { QueryModelBuilder } from './model.interface';
|
|
2
|
+
export declare const useQueryModel: <T>(keyTracker: string, exact?: boolean) => QueryModelBuilder<T>;
|
|
@@ -1,8 +1,108 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import result from 'lodash.result';
|
|
3
|
+
import set from 'lodash.set';
|
|
4
|
+
import { useKeyTrackerModel } from './useKeyTrackerModel.js';
|
|
2
5
|
|
|
3
|
-
const useQueryModel = (
|
|
6
|
+
const useQueryModel = (keyTracker, exact = true) => {
|
|
4
7
|
const queryClient = useQueryClient();
|
|
5
|
-
|
|
8
|
+
const { getQueryKey } = useKeyTrackerModel(keyTracker);
|
|
9
|
+
const queryKey = getQueryKey();
|
|
10
|
+
const add = (data, position, path) => {
|
|
11
|
+
let records = findAll(path) ?? [];
|
|
12
|
+
if (!position || position === 'end') {
|
|
13
|
+
records = [...records, data];
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
15
|
+
}
|
|
16
|
+
else if (position === 'start') {
|
|
17
|
+
records = [data, ...records];
|
|
18
|
+
}
|
|
19
|
+
if (!path) {
|
|
20
|
+
queryClient.setQueryData(queryKey, records);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
24
|
+
queryClient.setQueryData(queryKey, set(queryData, path, records));
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
};
|
|
28
|
+
const findAll = (path) => {
|
|
29
|
+
const data = queryClient.getQueryData(queryKey, { exact });
|
|
30
|
+
if (!data) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
if (!path) {
|
|
34
|
+
return Array.isArray(data) ? data : [data];
|
|
35
|
+
}
|
|
36
|
+
return result(data, path, []);
|
|
37
|
+
};
|
|
38
|
+
const findMany = (selector, path) => {
|
|
39
|
+
const data = findAll(path) ?? [];
|
|
40
|
+
return data.filter(selector);
|
|
41
|
+
};
|
|
42
|
+
const find = (id, path) => {
|
|
43
|
+
const modelConfig = getModelConfig();
|
|
44
|
+
if (!modelConfig?.idColumn) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const data = findAll(path) ?? [];
|
|
48
|
+
return data.find((record) => record[modelConfig.idColumn] === id);
|
|
49
|
+
};
|
|
50
|
+
const getModelConfig = () => {
|
|
51
|
+
const { options } = queryClient.getQueryData(['config']) ?? {};
|
|
52
|
+
const { modelConfig } = options ?? {};
|
|
53
|
+
return modelConfig;
|
|
54
|
+
};
|
|
55
|
+
const update = (id, data, path) => {
|
|
56
|
+
const oldData = findAll(path) ?? [];
|
|
57
|
+
const modelConfig = getModelConfig();
|
|
58
|
+
if (!modelConfig?.idColumn) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const idColumn = modelConfig.idColumn;
|
|
62
|
+
let updatedRecord = undefined;
|
|
63
|
+
const newData = oldData.map((record) => {
|
|
64
|
+
let dataRecord = record;
|
|
65
|
+
if (dataRecord[idColumn] === id) {
|
|
66
|
+
dataRecord = { ...dataRecord, ...data };
|
|
67
|
+
updatedRecord = dataRecord;
|
|
68
|
+
}
|
|
69
|
+
return dataRecord;
|
|
70
|
+
});
|
|
71
|
+
if (!path) {
|
|
72
|
+
queryClient.setQueryData(queryKey, newData);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
76
|
+
queryClient.setQueryData(queryKey, set(queryData, path, newData));
|
|
77
|
+
}
|
|
78
|
+
return updatedRecord;
|
|
79
|
+
};
|
|
80
|
+
const remove = (id, path) => {
|
|
81
|
+
const oldData = findAll(path) ?? [];
|
|
82
|
+
const modelConfig = getModelConfig();
|
|
83
|
+
if (!modelConfig?.idColumn) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const idColumn = modelConfig.idColumn;
|
|
87
|
+
let updated = false;
|
|
88
|
+
const newData = oldData.filter((record) => {
|
|
89
|
+
const dataRecord = record;
|
|
90
|
+
if (dataRecord[idColumn] === id) {
|
|
91
|
+
updated = true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
if (!path) {
|
|
97
|
+
queryClient.setQueryData(queryKey, newData);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const queryData = queryClient.getQueryData(queryKey, { exact }) ?? {};
|
|
101
|
+
queryClient.setQueryData(queryKey, set(queryData, path, newData));
|
|
102
|
+
}
|
|
103
|
+
return updated;
|
|
104
|
+
};
|
|
105
|
+
return { find, findAll, findMany, remove, update, add };
|
|
6
106
|
};
|
|
7
107
|
|
|
8
108
|
export { useQueryModel };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQueryModel.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useQueryModel.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -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.65",
|
|
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
|
};
|
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
|
+
}
|
|
@@ -9,8 +9,13 @@ export interface IMakeRequest {
|
|
|
9
9
|
method?: HttpMethod;
|
|
10
10
|
isFormData?: boolean;
|
|
11
11
|
headers: RawAxiosRequestHeaders;
|
|
12
|
+
appFileConfig?: AppFileConfig;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
export interface AppFileConfig {
|
|
16
|
+
fileSelectors?: string[];
|
|
17
|
+
isApp: boolean;
|
|
18
|
+
}
|
|
14
19
|
export interface AxiosInstanceOption {
|
|
15
20
|
bearerToken?: string;
|
|
16
21
|
contentType?: string;
|
package/src/types/index.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import type { RawAxiosRequestHeaders } from 'axios';
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface BootstrapConfig {
|
|
4
4
|
environments?: {
|
|
5
5
|
appBaseUrl: string;
|
|
6
6
|
appTimeout: number;
|
|
7
7
|
};
|
|
8
|
-
context?:
|
|
8
|
+
context?: ContextType;
|
|
9
|
+
modelConfig?: BootstrapModelConfig;
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
export interface BootstrapModelConfig {
|
|
13
|
+
idColumn: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ContextType = 'app' | 'web' | 'electronjs';
|
|
11
17
|
export interface TanstackQueryConfig {
|
|
12
18
|
headers: RawAxiosRequestHeaders;
|
|
13
|
-
options?:
|
|
19
|
+
options?: BootstrapConfig;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface IUseQueryHeaders {
|