@carbonorm/carbonnode 3.0.0 → 3.0.2
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/api/builders/sqlBuilder.d.ts +3 -0
- package/dist/api/convertForRequestBody.d.ts +1 -1
- package/dist/api/executors/Executor.d.ts +16 -0
- package/dist/api/executors/HttpExecutor.d.ts +13 -0
- package/dist/api/executors/SqlExecutor.d.ts +19 -0
- package/dist/api/restRequest.d.ts +9 -166
- package/dist/api/types/dynamicFetching.d.ts +10 -0
- package/dist/api/types/modifyTypes.d.ts +9 -0
- package/dist/api/types/mysqlTypes.d.ts +4 -0
- package/dist/api/types/ormInterfaces.d.ts +219 -0
- package/dist/api/utils/apiHelpers.d.ts +9 -0
- package/dist/api/utils/cacheManager.d.ts +10 -0
- package/dist/api/utils/determineRuntimeJsType.d.ts +5 -0
- package/dist/api/utils/logger.d.ts +7 -0
- package/dist/api/utils/sortAndSerializeQueryObject.d.ts +1 -0
- package/dist/api/utils/testHelpers.d.ts +1 -0
- package/dist/api/utils/toastNotifier.d.ts +2 -0
- package/dist/index.cjs.js +665 -614
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.esm.js +655 -618
- package/dist/index.esm.js.map +1 -1
- package/package.json +22 -6
- package/scripts/assets/handlebars/C6.ts.handlebars +13 -5
- package/scripts/assets/handlebars/Table.ts.handlebars +44 -12
- package/scripts/generateRestBindings.cjs +1 -1
- package/scripts/generateRestBindings.ts +1 -1
- package/src/api/builders/sqlBuilder.ts +173 -0
- package/src/api/convertForRequestBody.ts +2 -3
- package/src/api/executors/Executor.ts +28 -0
- package/src/api/executors/HttpExecutor.ts +794 -0
- package/src/api/executors/SqlExecutor.ts +104 -0
- package/src/api/restRequest.ts +50 -1287
- package/src/api/types/dynamicFetching.ts +10 -0
- package/src/api/types/modifyTypes.ts +25 -0
- package/src/api/types/mysqlTypes.ts +33 -0
- package/src/api/types/ormInterfaces.ts +310 -0
- package/src/api/utils/apiHelpers.ts +82 -0
- package/src/api/utils/cacheManager.ts +67 -0
- package/src/api/utils/determineRuntimeJsType.ts +46 -0
- package/src/api/utils/logger.ts +24 -0
- package/src/api/utils/sortAndSerializeQueryObject.ts +12 -0
- package/src/api/utils/testHelpers.ts +24 -0
- package/src/api/utils/toastNotifier.ts +11 -0
- package/src/index.ts +15 -2
- package/src/api/carbonSqlExecutor.ts +0 -279
- package/src/api/interfaces/ormInterfaces.ts +0 -87
package/src/api/restRequest.ts
CHANGED
|
@@ -1,1292 +1,55 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {AxiosInstance, AxiosPromise, AxiosResponse} from "axios";
|
|
5
|
-
import {Pool} from "mysql2/promise";
|
|
6
|
-
|
|
7
|
-
import {toast} from "react-toastify";
|
|
8
|
-
import isNode from 'variables/isNode'; // simple check: `typeof window==='undefined'`
|
|
9
|
-
import isLocal from "variables/isLocal";
|
|
10
|
-
import isTest from "variables/isTest";
|
|
11
|
-
import isVerbose from "variables/isVerbose";
|
|
12
|
-
import {toastOptions, toastOptionsDevs} from "variables/toastOptions";
|
|
13
|
-
|
|
14
|
-
// When we capture DropExceptions and display them as a custom page, this will change.
|
|
15
|
-
export function TestRestfulResponse(response: AxiosResponse | any, success: ((r: AxiosResponse) => (string | void)) | string | undefined, error: ((r: AxiosResponse) => (string | void)) | string | undefined): string | boolean | number {
|
|
16
|
-
|
|
17
|
-
if (undefined === response.data?.['ERROR TYPE']
|
|
18
|
-
&& (undefined !== response?.data?.rest
|
|
19
|
-
|| undefined !== response.data?.created
|
|
20
|
-
|| undefined !== response.data?.updated
|
|
21
|
-
|| undefined !== response.data?.deleted)) {
|
|
22
|
-
|
|
23
|
-
let successReturn: string | undefined | void = 'function' === typeof success ? success?.(response) : success;
|
|
24
|
-
|
|
25
|
-
if (typeof successReturn === 'string') {
|
|
26
|
-
|
|
27
|
-
toast.success(successReturn, toastOptions);
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// this could end up with bad results for deleting id's === 0
|
|
32
|
-
return response.data.created ?? response.data.updated ?? response.data.deleted ?? true;
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let errorReturn: string | undefined | void = 'function' === typeof error ? error?.(response) : error;
|
|
37
|
-
|
|
38
|
-
if (typeof errorReturn === 'string') {
|
|
39
|
-
|
|
40
|
-
toast.error(errorReturn, toastOptions);
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return false;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function removeInvalidKeys<iRestObject>(request: any, c6Tables: {
|
|
50
|
-
[key: string]: (iC6RestfulModel & { [key: string]: any })
|
|
51
|
-
}): iRestObject {
|
|
52
|
-
|
|
53
|
-
let intersection: iRestObject = {} as iRestObject
|
|
54
|
-
|
|
55
|
-
let restfulObjectKeys: string[] = [];
|
|
56
|
-
|
|
57
|
-
const tableList = Object.values(c6Tables)
|
|
58
|
-
|
|
59
|
-
tableList.forEach(table => Object.values(table.COLUMNS).forEach(column => {
|
|
60
|
-
|
|
61
|
-
if (false === restfulObjectKeys.includes(column)) {
|
|
62
|
-
|
|
63
|
-
restfulObjectKeys.push(column)
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
}))
|
|
68
|
-
|
|
69
|
-
Object.keys(request).forEach(key => {
|
|
70
|
-
|
|
71
|
-
if (restfulObjectKeys.includes(key)) {
|
|
72
|
-
|
|
73
|
-
intersection[key] = request[key]
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
isTest || console.log('intersection', intersection)
|
|
80
|
-
|
|
81
|
-
return intersection
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// if you can get away with modify over modifyDeep, use modify. The editor will be happier.
|
|
86
|
-
export type Modify<T, R> = Omit<T, keyof R> & R;
|
|
87
|
-
|
|
88
|
-
// @link https://stackoverflow.com/questions/41285211/overriding-interface-property-type-defined-in-typescript-d-ts-file/55032655#55032655
|
|
89
|
-
export type ModifyDeep<A, B extends DeepPartialAny<A>> = {
|
|
90
|
-
[K in keyof A | keyof B]?: // For all keys in A and B:
|
|
91
|
-
K extends keyof A // ───┐
|
|
92
|
-
? K extends keyof B // ───┼─ key K exists in both A and B
|
|
93
|
-
? A[K] extends AnyObject // │ ┴──┐
|
|
94
|
-
? B[K] extends AnyObject // │ ───┼─ both A and B are objects
|
|
95
|
-
? ModifyDeep<A[K], B[K]> // │ │ └─── We need to go deeper (recursively)
|
|
96
|
-
: B[K] // │ ├─ B is a primitive 🠆 use B as the final type (new type)
|
|
97
|
-
: B[K] // │ └─ A is a primitive 🠆 use B as the final type (new type)
|
|
98
|
-
: A[K] // ├─ key only exists in A 🠆 use A as the final type (original type)
|
|
99
|
-
: B[K] // └─ key only exists in B 🠆 use B as the final type (new type)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
type AnyObject = Record<string, any>
|
|
103
|
-
|
|
104
|
-
// This type is here only for some intellisense for the overrides object
|
|
105
|
-
type DeepPartialAny<T> = {
|
|
106
|
-
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
|
|
107
|
-
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export enum eFetchDependencies {
|
|
111
|
-
NONE = 0,
|
|
112
|
-
REFERENCED = 0b1,
|
|
113
|
-
CHILDREN = 0b1,
|
|
114
|
-
REFERENCES = 0b10,
|
|
115
|
-
PARENTS = 0b10,
|
|
116
|
-
ALL = 0b11,
|
|
117
|
-
C6ENTITY = 0b100,
|
|
118
|
-
RECURSIVE = 0b1000,
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// todo - I don't like that these essentially become reserved words.
|
|
122
|
-
export type iAPI<RestTableInterfaces extends { [key: string]: any }> = RestTableInterfaces & {
|
|
123
|
-
dataInsertMultipleRows?: RestTableInterfaces[],
|
|
124
|
-
cacheResults?: boolean, // aka ignoreCache
|
|
125
|
-
// todo - this should really only be used for get requests - add this to the Get interface or throw error (im actually inclined to ts ignore the function and add to iGetC6 atm; back later)
|
|
126
|
-
fetchDependencies?: number | eFetchDependencies | Awaited<apiReturn<iGetC6RestResponse<any>>>[],
|
|
127
|
-
debug?: boolean,
|
|
128
|
-
success?: string | ((r: AxiosResponse) => (string | void)),
|
|
129
|
-
error?: string | ((r: AxiosResponse) => (string | void)),
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface iCacheAPI<ResponseDataType = any> {
|
|
133
|
-
requestArgumentsSerialized: string,
|
|
134
|
-
request: AxiosPromise<ResponseDataType>,
|
|
135
|
-
response?: AxiosResponse,
|
|
136
|
-
final?: boolean,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// do not remove entries from this array. It is used to track the progress of API requests.
|
|
141
|
-
// position in array is important. Do not sort. To not add to begging.
|
|
142
|
-
let apiRequestCache: iCacheAPI[] = [];
|
|
143
|
-
|
|
144
|
-
let userCustomClearCache: (() => void)[] = [];
|
|
145
|
-
|
|
146
|
-
export function checkAllRequestsComplete(): true | (string[]) {
|
|
147
|
-
|
|
148
|
-
const stillRunning = apiRequestCache.filter((cache) => undefined === cache.response)
|
|
149
|
-
|
|
150
|
-
if (stillRunning.length !== 0) {
|
|
151
|
-
|
|
152
|
-
if (document === null || document === undefined) {
|
|
153
|
-
|
|
154
|
-
throw new Error('document is undefined while waiting for API requests to complete (' + JSON.stringify(apiRequestCache) + ')')
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// when requests return emtpy sets in full renders, it may not be possible to track their progress.
|
|
159
|
-
console.warn('stillRunning...', stillRunning)
|
|
160
|
-
|
|
161
|
-
return stillRunning.map((cache) => cache.requestArgumentsSerialized)
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return true
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
interface iClearCache {
|
|
171
|
-
ignoreWarning: boolean
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
function checkCache<ResponseDataType = any, RestShortTableNames = string>(cacheResult: iCacheAPI<ResponseDataType>, requestMethod: string, tableName: RestShortTableNames | RestShortTableNames[], request: any): false | undefined | null | AxiosPromise<ResponseDataType> {
|
|
176
|
-
|
|
177
|
-
if (undefined === cacheResult?.response) {
|
|
178
|
-
|
|
179
|
-
console.groupCollapsed('%c API: The request on (' + tableName + ') is in cache and the response is undefined. The request has not finished. Returning the request Promise!', 'color: #0c0')
|
|
180
|
-
|
|
181
|
-
console.log('%c ' + requestMethod + ' ' + tableName, 'color: #0c0')
|
|
182
|
-
|
|
183
|
-
console.log('%c Request Data (note you may see the success and/or error prompt):', 'color: #0c0', request)
|
|
184
|
-
|
|
185
|
-
console.groupEnd()
|
|
186
|
-
|
|
187
|
-
return cacheResult.request;
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (true === cacheResult?.final) {
|
|
192
|
-
|
|
193
|
-
if (false === isTest || true === isVerbose) {
|
|
194
|
-
|
|
195
|
-
console.groupCollapsed('%c API: Rest api cache (' + requestMethod + ' ' + tableName + ') has reached the final result. Returning undefined!', 'color: #cc0')
|
|
196
|
-
|
|
197
|
-
console.log('%c ' + requestMethod + ' ' + tableName, 'color: #cc0')
|
|
198
|
-
|
|
199
|
-
console.log('%c Request Data (note you may see the success and/or error prompt):', 'color: #cc0', request)
|
|
200
|
-
|
|
201
|
-
console.log('%c Response Data:', 'color: #cc0', cacheResult?.response?.data?.rest || cacheResult?.response?.data || cacheResult?.response)
|
|
202
|
-
|
|
203
|
-
console.groupEnd()
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return undefined;
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function sortAndSerializeQueryObject(tables: String, query: Object) {
|
|
215
|
-
const orderedQuery = Object.keys(query).sort().reduce(
|
|
216
|
-
(obj, key) => {
|
|
217
|
-
obj[key] = query[key];
|
|
218
|
-
return obj;
|
|
219
|
-
},
|
|
220
|
-
{}
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
return tables + ' ' + JSON.stringify(orderedQuery);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
export function clearCache(props?: iClearCache) {
|
|
228
|
-
|
|
229
|
-
if (false === props?.ignoreWarning) {
|
|
230
|
-
|
|
231
|
-
console.warn('The rest api clearCache should only be used with extreme care! Avoid using this in favor of using `cacheResults : boolean`.')
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
userCustomClearCache.map((f) => 'function' === typeof f && f());
|
|
236
|
-
|
|
237
|
-
userCustomClearCache = apiRequestCache = []
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
export function removePrefixIfExists(tableName: string, prefix: string): string {
|
|
243
|
-
if (tableName.startsWith(prefix.toLowerCase())) {
|
|
244
|
-
return tableName.slice(prefix.length);
|
|
245
|
-
}
|
|
246
|
-
return tableName;
|
|
247
|
-
}
|
|
1
|
+
import isNode from '../variables/isNode';
|
|
2
|
+
import {Modify} from "./types/modifyTypes";
|
|
3
|
+
import {apiReturn, iAPI, iRest} from "./types/ormInterfaces";
|
|
248
4
|
|
|
249
5
|
/**
|
|
250
|
-
*
|
|
251
|
-
|
|
252
|
-
* Our api returns a zero argument function iff the method is get and the previous request reached the predefined limit.
|
|
253
|
-
* This function can be aliased as GetNextPageOfResults(). If the end is reached undefined will be returned.
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* For POST, PUT, and DELETE requests one can expect the primary key of the new or modified index, or a boolean success
|
|
257
|
-
* indication if no primary key exists.
|
|
258
|
-
**/
|
|
259
|
-
export const POST = 'POST';
|
|
260
|
-
|
|
261
|
-
export const PUT = 'PUT';
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
export const GET = 'GET';
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
export const DELETE = 'DELETE';
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
export type iRestMethods = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
271
|
-
|
|
272
|
-
// ========================
|
|
273
|
-
// 🔧 SQL Operator & Helpers
|
|
274
|
-
// ========================
|
|
275
|
-
|
|
276
|
-
export type SQLFunction =
|
|
277
|
-
| 'COUNT'
|
|
278
|
-
| 'GROUP_CONCAT'
|
|
279
|
-
| 'MAX'
|
|
280
|
-
| 'MIN'
|
|
281
|
-
| 'SUM'
|
|
282
|
-
| 'DISTINCT';
|
|
283
|
-
|
|
284
|
-
export type SQLComparisonOperator =
|
|
285
|
-
| '='
|
|
286
|
-
| '!='
|
|
287
|
-
| '<'
|
|
288
|
-
| '<='
|
|
289
|
-
| '>'
|
|
290
|
-
| '>='
|
|
291
|
-
| 'IN'
|
|
292
|
-
| 'NOT IN'
|
|
293
|
-
| 'LIKE'
|
|
294
|
-
| 'IS NULL'
|
|
295
|
-
| 'IS NOT NULL'
|
|
296
|
-
| 'BETWEEN'
|
|
297
|
-
| 'LESS_THAN'
|
|
298
|
-
| 'GREATER_THAN';
|
|
299
|
-
|
|
300
|
-
export type JoinType = 'INNER' | 'LEFT_OUTER' | 'RIGHT_OUTER';
|
|
301
|
-
|
|
302
|
-
export type OrderDirection = 'ASC' | 'DESC';
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// ========================
|
|
306
|
-
// 📦 SELECT
|
|
307
|
-
// ========================
|
|
308
|
-
|
|
309
|
-
export type SubSelect<T = any> = {
|
|
310
|
-
subSelect: true;
|
|
311
|
-
table: string; // could be enum’d to known table names
|
|
312
|
-
args: RequestGetPutDeleteBody<T>;
|
|
313
|
-
alias: string;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
export type SelectField<T = any> =
|
|
317
|
-
| keyof T
|
|
318
|
-
| [keyof T, 'AS', string]
|
|
319
|
-
| [SQLFunction, keyof T]
|
|
320
|
-
| [SQLFunction, keyof T, string] // With alias
|
|
321
|
-
| SubSelect<T>; // Fully nested sub-select
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// ========================
|
|
325
|
-
// 🧠 WHERE (Recursive)
|
|
326
|
-
// ========================
|
|
327
|
-
|
|
328
|
-
export type WhereClause<T = any> =
|
|
329
|
-
| Partial<T>
|
|
330
|
-
| LogicalGroup<T>
|
|
331
|
-
| ComparisonClause<T>;
|
|
332
|
-
|
|
333
|
-
export type LogicalGroup<T = any> = {
|
|
334
|
-
[logicalGroup: string]: Array<WhereClause<T>>;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
export type ComparisonClause<T = any> = [keyof T, SQLComparisonOperator, any];
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// ========================
|
|
341
|
-
// 🔗 JOIN
|
|
342
|
-
// ========================
|
|
343
|
-
|
|
344
|
-
export type JoinTableCondition<T = any> =
|
|
345
|
-
| Partial<T>
|
|
346
|
-
| WhereClause<T>[]
|
|
347
|
-
| ComparisonClause<T>[];
|
|
348
|
-
|
|
349
|
-
export type JoinClause<T = any> = {
|
|
350
|
-
[table: string]: JoinTableCondition<T>;
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
export type Join<T = any> = {
|
|
354
|
-
[K in JoinType]?: JoinClause<T>;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// ========================
|
|
359
|
-
// 📄 PAGINATION
|
|
360
|
-
// ========================
|
|
361
|
-
|
|
362
|
-
export type Pagination<T = any> = {
|
|
363
|
-
PAGE?: number;
|
|
364
|
-
LIMIT?: number | null;
|
|
365
|
-
ORDER?: Partial<Record<keyof T, OrderDirection>>;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// ========================
|
|
370
|
-
// 🌐 MAIN API TYPE
|
|
371
|
-
// ========================
|
|
372
|
-
|
|
373
|
-
export type RequestGetPutDeleteBody<T = any> = {
|
|
374
|
-
SELECT?: SelectField<T>[];
|
|
375
|
-
UPDATE?: Partial<T>;
|
|
376
|
-
DELETE?: boolean;
|
|
377
|
-
WHERE?: WhereClause<T>;
|
|
378
|
-
JOIN?: Join<T>;
|
|
379
|
-
PAGINATION?: Pagination<T>;
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
export type RequestQueryBody<RestTableInterfaces extends { [key: string]: any }> =
|
|
384
|
-
iAPI<RestTableInterfaces>
|
|
385
|
-
| RequestGetPutDeleteBody;
|
|
386
|
-
|
|
387
|
-
export function isPromise(x) {
|
|
388
|
-
return Object(x).constructor === Promise
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
interface iC6RestResponse<RestData> {
|
|
392
|
-
rest: RestData,
|
|
393
|
-
session?: any,
|
|
394
|
-
sql?: any
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
interface iChangeC6Data {
|
|
399
|
-
rowCount: number,
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export interface iDeleteC6RestResponse<RestData = any, RequestData = any> extends iChangeC6Data, iC6RestResponse<RestData> {
|
|
403
|
-
deleted: boolean | number | string | RequestData,
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export interface iPostC6RestResponse<RestData = any> extends iC6RestResponse<RestData> {
|
|
407
|
-
created: boolean | number | string,
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
export interface iPutC6RestResponse<RestData = any, RequestData = any> extends iChangeC6Data, iC6RestResponse<RestData> {
|
|
411
|
-
updated: boolean | number | string | RequestData,
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
export interface iC6Object {
|
|
415
|
-
C6VERSION: string,
|
|
416
|
-
TABLES: {
|
|
417
|
-
[key: string]: iC6RestfulModel &
|
|
418
|
-
{ [key: string]: string | number }
|
|
419
|
-
},
|
|
420
|
-
PREFIX: string,
|
|
421
|
-
IMPORT: (tableName: string) => Promise<iDynamicApiImport>,
|
|
422
|
-
|
|
423
|
-
[key: string]: any
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// todo - I'm not sure that Modify<ResponseDataType, ResponseDataOverrides>[]> is needed?
|
|
427
|
-
export type iGetC6RestResponse<ResponseDataType, ResponseDataOverrides = {}> = iC6RestResponse<Modify<ResponseDataType, ResponseDataOverrides> | Modify<ResponseDataType, ResponseDataOverrides>[]>
|
|
428
|
-
|
|
429
|
-
// returning undefined means no more results are available, thus we've queried everything possible
|
|
430
|
-
// null means the request is currently being executed
|
|
431
|
-
// https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
|
|
432
|
-
export type apiReturn<Response> =
|
|
433
|
-
null
|
|
434
|
-
| undefined
|
|
435
|
-
| AxiosPromise<Response>
|
|
436
|
-
| (Response extends iPutC6RestResponse | iDeleteC6RestResponse | iPostC6RestResponse ? null : (() => apiReturn<Response>))
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
interface iRest<CustomAndRequiredFields extends { [key: string]: any }, RestTableInterfaces extends {
|
|
440
|
-
[key: string]: any
|
|
441
|
-
}, RequestTableOverrides = {
|
|
442
|
-
[key in keyof RestTableInterfaces]: any
|
|
443
|
-
}, ResponseDataType = any,
|
|
444
|
-
RestShortTableNames extends string = any> {
|
|
445
|
-
C6: iC6Object,
|
|
446
|
-
axios?: AxiosInstance,
|
|
447
|
-
restURL?: string,
|
|
448
|
-
mysqlPool?: Pool;
|
|
449
|
-
withCredentials?: boolean,
|
|
450
|
-
tableName: RestShortTableNames | RestShortTableNames[],
|
|
451
|
-
requestMethod: iRestMethods,
|
|
452
|
-
clearCache?: () => void,
|
|
453
|
-
skipPrimaryCheck?: boolean,
|
|
454
|
-
queryCallback: RequestQueryBody<Modify<RestTableInterfaces, RequestTableOverrides>> | ((request: iAPI<Modify<RestTableInterfaces, RequestTableOverrides>> & CustomAndRequiredFields) => (null | undefined | RequestQueryBody<Modify<RestTableInterfaces, RequestTableOverrides>>)),
|
|
455
|
-
responseCallback?: (response: AxiosResponse<ResponseDataType>,
|
|
456
|
-
request: iAPI<Modify<RestTableInterfaces, RequestTableOverrides>> & CustomAndRequiredFields,
|
|
457
|
-
success: (ResponseDataType extends iPutC6RestResponse | iDeleteC6RestResponse ? RequestQueryBody<Modify<RestTableInterfaces, RequestTableOverrides>> : string) | string | number | boolean) => any // keep this set to any, it allows easy arrow functions and the results unused here
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export function extendedTypeHints<RestTableInterfaces extends {
|
|
461
|
-
[key: string]: any
|
|
462
|
-
}, RestShortTableNames extends string>() {
|
|
463
|
-
return <CustomAndRequiredFields extends {
|
|
464
|
-
[key: string]: any
|
|
465
|
-
} = any, RequestTableTypes extends RestTableInterfaces = any, RequestTableOverrides extends {
|
|
466
|
-
[key: string]: any
|
|
467
|
-
} = any, ResponseDataType extends {
|
|
468
|
-
[key: string]: any
|
|
469
|
-
} = any>(argv) => restRequest<CustomAndRequiredFields, RequestTableTypes, RequestTableOverrides, ResponseDataType, RestShortTableNames>(argv)
|
|
470
|
-
}
|
|
471
|
-
|
|
6
|
+
* Facade: routes API calls to SQL or HTTP executors based on runtime context.
|
|
7
|
+
*/
|
|
472
8
|
export default function restRequest<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
mysqlPool,
|
|
489
|
-
withCredentials = true,
|
|
490
|
-
tableName,
|
|
491
|
-
requestMethod = GET,
|
|
492
|
-
queryCallback = {},
|
|
493
|
-
responseCallback,
|
|
494
|
-
skipPrimaryCheck = false,
|
|
495
|
-
clearCache = undefined
|
|
496
|
-
}: iRest<CustomAndRequiredFields, RestTableInterfaces, RequestTableOverrides, ResponseDataType, RestShortTableNames>
|
|
9
|
+
RestShortTableName extends string = any,
|
|
10
|
+
RestTableInterface extends { [key: string]: any } = any,
|
|
11
|
+
PrimaryKey extends Extract<keyof RestTableInterface, string> = Extract<keyof RestTableInterface, string>,
|
|
12
|
+
CustomAndRequiredFields extends { [key: string]: any } = any,
|
|
13
|
+
RequestTableOverrides extends { [key: string]: any } = { [key in keyof RestTableInterface]: any },
|
|
14
|
+
ResponseDataType = any
|
|
15
|
+
>(
|
|
16
|
+
config: iRest<
|
|
17
|
+
RestShortTableName,
|
|
18
|
+
RestTableInterface,
|
|
19
|
+
PrimaryKey,
|
|
20
|
+
CustomAndRequiredFields,
|
|
21
|
+
RequestTableOverrides,
|
|
22
|
+
ResponseDataType
|
|
23
|
+
>
|
|
497
24
|
) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
console.groupEnd()
|
|
530
|
-
|
|
531
|
-
// an undefined query would indicate queryCallback returned undefined,
|
|
532
|
-
// thus the request shouldn't fire as is in custom cache
|
|
533
|
-
let query: RequestQueryBody<Modify<RestTableInterfaces, RequestTableOverrides>> | undefined | null;
|
|
534
|
-
|
|
535
|
-
if ('function' === typeof queryCallback) {
|
|
536
|
-
|
|
537
|
-
query = queryCallback(request); // obj or obj[]
|
|
538
|
-
|
|
539
|
-
} else {
|
|
540
|
-
|
|
541
|
-
query = queryCallback;
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
if (undefined === query || null === query) {
|
|
546
|
-
|
|
547
|
-
if (request.debug && isLocal) {
|
|
548
|
-
|
|
549
|
-
toast.warning("DEV: queryCallback returned undefined, signaling in Custom Cache. (returning null)", toastOptionsDevs)
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
console.groupCollapsed('%c API: (' + requestMethod + ') Request Query for (' + operatingTable + ') undefined, returning null (will not fire ajax)!', 'color: #c00')
|
|
554
|
-
|
|
555
|
-
console.log('%c Returning (undefined|null) for a query would indicate a custom cache hit (outside API.tsx), thus the request should not fire.', 'color: #c00')
|
|
556
|
-
|
|
557
|
-
console.trace();
|
|
558
|
-
|
|
559
|
-
console.groupEnd()
|
|
560
|
-
|
|
561
|
-
return null;
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (C6.GET === requestMethod) {
|
|
566
|
-
|
|
567
|
-
if (undefined === query[C6.PAGINATION]) {
|
|
568
|
-
|
|
569
|
-
query[C6.PAGINATION] = {}
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
query[C6.PAGINATION][C6.PAGE] = query[C6.PAGINATION][C6.PAGE] || 1;
|
|
574
|
-
|
|
575
|
-
query[C6.PAGINATION][C6.LIMIT] = query[C6.PAGINATION][C6.LIMIT] || 100;
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// this could return itself with a new page number, or undefined if the end is reached
|
|
580
|
-
async function apiRequest(): Promise<apiReturn<ResponseDataType>> {
|
|
581
|
-
|
|
582
|
-
request.cacheResults ??= (C6.GET === requestMethod)
|
|
583
|
-
|
|
584
|
-
if (C6.GET === requestMethod
|
|
585
|
-
&& undefined !== query?.[C6.PAGINATION]?.[C6.PAGE]
|
|
586
|
-
&& 1 !== query[C6.PAGINATION][C6.PAGE]) {
|
|
587
|
-
|
|
588
|
-
console.groupCollapsed('Request on table (' + tableName + ') is firing for page (' + query[C6.PAGINATION][C6.PAGE] + '), please wait!')
|
|
589
|
-
|
|
590
|
-
console.log('Request Data (note you may see the success and/or error prompt):', request)
|
|
591
|
-
|
|
592
|
-
console.trace();
|
|
593
|
-
|
|
594
|
-
console.groupEnd()
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// The problem with creating cache keys with a stringified object is the order of keys matters and it's possible for the same query to be stringified differently.
|
|
599
|
-
// Here we ensure the key order will be identical between two of the same requests. https://stackoverflow.com/questions/5467129/sort-javascript-object-by-key
|
|
600
|
-
|
|
601
|
-
// literally impossible for query to be undefined or null here but the editor is too busy licking windows to understand that
|
|
602
|
-
let querySerialized: string = sortAndSerializeQueryObject(tables, query ?? {});
|
|
603
|
-
|
|
604
|
-
let cacheResult: iCacheAPI | undefined = apiRequestCache.find(cache => cache.requestArgumentsSerialized === querySerialized);
|
|
605
|
-
|
|
606
|
-
let cachingConfirmed = false;
|
|
607
|
-
|
|
608
|
-
// determine if we need to paginate.
|
|
609
|
-
if (requestMethod === C6.GET) {
|
|
610
|
-
|
|
611
|
-
if (undefined === query?.[C6.PAGINATION]) {
|
|
612
|
-
|
|
613
|
-
if (undefined === query || null === query) {
|
|
614
|
-
|
|
615
|
-
query = {}
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
query[C6.PAGINATION] = {}
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
query[C6.PAGINATION][C6.PAGE] = query[C6.PAGINATION][C6.PAGE] || 1;
|
|
624
|
-
|
|
625
|
-
query[C6.PAGINATION][C6.LIMIT] = query[C6.PAGINATION][C6.LIMIT] || 100;
|
|
626
|
-
|
|
627
|
-
// this will evaluate true most the time
|
|
628
|
-
if (true === request.cacheResults) {
|
|
629
|
-
|
|
630
|
-
// just find the next, non-fetched, page and return a function to request it
|
|
631
|
-
if (undefined !== cacheResult) {
|
|
632
|
-
|
|
633
|
-
do {
|
|
634
|
-
|
|
635
|
-
const cacheCheck = checkCache<ResponseDataType>(cacheResult, requestMethod, tableName, request);
|
|
636
|
-
|
|
637
|
-
if (false !== cacheCheck) {
|
|
638
|
-
|
|
639
|
-
return cacheCheck;
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// this line incrementing page is why we return recursively
|
|
644
|
-
++query[C6.PAGINATION][C6.PAGE];
|
|
645
|
-
|
|
646
|
-
// this json stringify is to capture the new page number
|
|
647
|
-
querySerialized = sortAndSerializeQueryObject(tables, query ?? {});
|
|
648
|
-
|
|
649
|
-
cacheResult = apiRequestCache.find(cache => cache.requestArgumentsSerialized === querySerialized)
|
|
650
|
-
|
|
651
|
-
} while (undefined !== cacheResult)
|
|
652
|
-
|
|
653
|
-
if (request.debug && isLocal) {
|
|
654
|
-
|
|
655
|
-
toast.warning("DEVS: Request in cache. (" + apiRequestCache.findIndex(cache => cache.requestArgumentsSerialized === querySerialized) + "). Returning function to request page (" + query[C6.PAGINATION][C6.PAGE] + ")", toastOptionsDevs);
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// @ts-ignore - this is an incorrect warning on TS, it's well typed
|
|
660
|
-
return apiRequest;
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
cachingConfirmed = true;
|
|
665
|
-
|
|
666
|
-
} else {
|
|
667
|
-
|
|
668
|
-
if (request.debug && isLocal) {
|
|
669
|
-
|
|
670
|
-
toast.info("DEVS: Ignore cache was set to true.", toastOptionsDevs);
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (request.debug && isLocal) {
|
|
677
|
-
|
|
678
|
-
toast.success("DEVS: Request not in cache." + (requestMethod === C6.GET ? "Page (" + query[C6.PAGINATION][C6.PAGE] + ")." : '') + " Logging cache 2 console.", toastOptionsDevs);
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
} else if (request.cacheResults) { // if we are not getting, we are updating, deleting, or inserting
|
|
683
|
-
|
|
684
|
-
if (cacheResult) {
|
|
685
|
-
const cacheCheck = checkCache<ResponseDataType>(cacheResult, requestMethod, tableName, request);
|
|
686
|
-
|
|
687
|
-
if (false !== cacheCheck) {
|
|
688
|
-
|
|
689
|
-
return cacheCheck;
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
cachingConfirmed = true;
|
|
695
|
-
// push to cache so we do not repeat the request
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
let addBackPK: (() => void) | undefined;
|
|
700
|
-
|
|
701
|
-
let apiResponse: string | boolean | number | undefined;
|
|
702
|
-
|
|
703
|
-
let returnGetNextPageFunction = false;
|
|
704
|
-
|
|
705
|
-
let restRequestUri: string = restURL + operatingTable + '/';
|
|
706
|
-
|
|
707
|
-
const needsConditionOrPrimaryCheck = (PUT === requestMethod || DELETE === requestMethod)
|
|
708
|
-
&& false === skipPrimaryCheck;
|
|
709
|
-
|
|
710
|
-
const TABLES = C6.TABLES;
|
|
711
|
-
|
|
712
|
-
// todo - aggregate primary key check with condition check
|
|
713
|
-
// check if PK exists in query, clone so pop does not affect the real data
|
|
714
|
-
const primaryKey = structuredClone(TABLES[operatingTable]?.PRIMARY)?.pop()?.split('.')?.pop();
|
|
715
|
-
|
|
716
|
-
if (needsConditionOrPrimaryCheck) {
|
|
717
|
-
|
|
718
|
-
if (undefined === primaryKey) {
|
|
719
|
-
|
|
720
|
-
if (null === query
|
|
721
|
-
|| undefined === query
|
|
722
|
-
|| undefined === query?.[C6.WHERE]
|
|
723
|
-
|| (true === Array.isArray(query[C6.WHERE])
|
|
724
|
-
|| query[C6.WHERE].length === 0)
|
|
725
|
-
|| (Object.keys(query?.[C6.WHERE]).length === 0)
|
|
726
|
-
) {
|
|
727
|
-
|
|
728
|
-
console.error(query)
|
|
729
|
-
|
|
730
|
-
throw Error('Failed to parse primary key information. Query: (' + JSON.stringify(query) + ') Primary Key: (' + JSON.stringify(primaryKey) + ') TABLES[operatingTable]?.PRIMARY: (' + JSON.stringify(TABLES[operatingTable]?.PRIMARY) + ') for operatingTable (' + operatingTable + ').')
|
|
731
|
-
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
} else {
|
|
735
|
-
|
|
736
|
-
if (undefined === query
|
|
737
|
-
|| null === query
|
|
738
|
-
|| false === primaryKey in query) {
|
|
739
|
-
|
|
740
|
-
if (true === request.debug && isLocal) {
|
|
741
|
-
|
|
742
|
-
toast.error('DEVS: The primary key (' + primaryKey + ') was not provided!!')
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
throw Error('You must provide the primary key (' + primaryKey + ') for table (' + operatingTable + '). Request (' + JSON.stringify(request, undefined, 4) + ') Query (' + JSON.stringify(query) + ')');
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if (undefined === query?.[primaryKey]
|
|
751
|
-
|| null === query?.[primaryKey]) {
|
|
752
|
-
|
|
753
|
-
toast.error('The primary key (' + primaryKey + ') provided is undefined or null explicitly!!')
|
|
754
|
-
|
|
755
|
-
throw Error('The primary key (' + primaryKey + ') provided in the request was exactly equal to undefined.');
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// this is node check is for react bundlers to skip this import all together
|
|
764
|
-
if (isNode && mysqlPool) {
|
|
765
|
-
const { CarbonSqlExecutor } = await import('./carbonSqlExecutor');
|
|
766
|
-
const engine = new CarbonSqlExecutor(mysqlPool, C6);
|
|
767
|
-
switch (requestMethod) {
|
|
768
|
-
case GET:
|
|
769
|
-
return engine
|
|
770
|
-
.select(tableName, undefined, request)
|
|
771
|
-
.then((rows) => {
|
|
772
|
-
// mirror the front‐end shape
|
|
773
|
-
const serverResponse: iGetC6RestResponse<typeof rows> = {
|
|
774
|
-
rest: rows,
|
|
775
|
-
session: undefined,
|
|
776
|
-
sql: true,
|
|
777
|
-
};
|
|
778
|
-
return serverResponse;
|
|
779
|
-
}) as apiReturn<ResponseDataType>;
|
|
780
|
-
|
|
781
|
-
case POST:
|
|
782
|
-
return engine
|
|
783
|
-
.insert(tableName, request)
|
|
784
|
-
.then((created) => ({
|
|
785
|
-
rest: created,
|
|
786
|
-
})) as apiReturn<ResponseDataType>;
|
|
787
|
-
|
|
788
|
-
case PUT:
|
|
789
|
-
return engine
|
|
790
|
-
.update(tableName, undefined, request)
|
|
791
|
-
.then((updatedResult) => ({
|
|
792
|
-
rest: updatedResult,
|
|
793
|
-
rowCount: (updatedResult as any).affectedRows ?? 0,
|
|
794
|
-
})) as apiReturn<ResponseDataType>;
|
|
795
|
-
|
|
796
|
-
case DELETE:
|
|
797
|
-
return engine
|
|
798
|
-
.delete(tableName, undefined, request)
|
|
799
|
-
.then((deletedResult) => ({
|
|
800
|
-
rest: deletedResult,
|
|
801
|
-
rowCount: (deletedResult as any).affectedRows ?? 0,
|
|
802
|
-
deleted: true,
|
|
803
|
-
})) as apiReturn<ResponseDataType>;
|
|
804
|
-
|
|
805
|
-
default:
|
|
806
|
-
throw new Error(`Unsupported method: ${requestMethod}`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
// A part of me exists that wants to remove this, but it's a good feature
|
|
812
|
-
// this allows developers the ability to cache requests based on primary key
|
|
813
|
-
// for tables like `photos` this can be a huge performance boost
|
|
814
|
-
if (undefined !== query
|
|
815
|
-
&& null !== query
|
|
816
|
-
&& undefined !== primaryKey
|
|
817
|
-
&& primaryKey in query) {
|
|
818
|
-
|
|
819
|
-
restRequestUri += query[primaryKey] + '/'
|
|
820
|
-
|
|
821
|
-
const removedPkValue = query[primaryKey];
|
|
822
|
-
|
|
823
|
-
addBackPK = () => {
|
|
824
|
-
query ??= {}
|
|
825
|
-
query[primaryKey] = removedPkValue
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
delete query[primaryKey]
|
|
829
|
-
|
|
830
|
-
console.log('query', query, 'primaryKey', primaryKey, 'removedPkValue', removedPkValue)
|
|
831
|
-
|
|
832
|
-
} else {
|
|
833
|
-
|
|
834
|
-
console.log('query', query)
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
try {
|
|
839
|
-
|
|
840
|
-
console.groupCollapsed('%c API: (' + requestMethod + ') Request Query for (' + operatingTable + ') is about to fire, will return with promise!', 'color: #A020F0')
|
|
841
|
-
|
|
842
|
-
console.log(request)
|
|
843
|
-
|
|
844
|
-
console.log('%c If this is the first request for this datatype; thus the value being set is currently undefined, please remember to update the state to null.', 'color: #A020F0')
|
|
845
|
-
|
|
846
|
-
console.log('%c Remember undefined indicated the request has not fired, null indicates the request is firing, an empty array would signal no data was returned for the sql stmt.', 'color: #A020F0')
|
|
847
|
-
|
|
848
|
-
console.trace()
|
|
849
|
-
|
|
850
|
-
console.groupEnd()
|
|
851
|
-
|
|
852
|
-
const axiosActiveRequest: AxiosPromise<ResponseDataType> = axios[requestMethod.toLowerCase()]<ResponseDataType>(
|
|
853
|
-
restRequestUri,
|
|
854
|
-
...((() => {
|
|
855
|
-
|
|
856
|
-
// @link - https://axios-http.com/docs/instance
|
|
857
|
-
// How configuration vs data is passed is variable, use documentation above for reference
|
|
858
|
-
if (requestMethod === GET) {
|
|
859
|
-
|
|
860
|
-
return [{
|
|
861
|
-
withCredentials: withCredentials,
|
|
862
|
-
params: query
|
|
863
|
-
}]
|
|
864
|
-
|
|
865
|
-
} else if (requestMethod === POST) {
|
|
866
|
-
|
|
867
|
-
if (undefined !== request?.dataInsertMultipleRows) {
|
|
868
|
-
|
|
869
|
-
return [
|
|
870
|
-
request.dataInsertMultipleRows.map(data =>
|
|
871
|
-
convertForRequestBody<typeof data>(data, fullTableList, C6, (message) => toast.error(message, toastOptions))),
|
|
872
|
-
{
|
|
873
|
-
withCredentials: withCredentials,
|
|
874
|
-
}
|
|
875
|
-
]
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
return [
|
|
880
|
-
convertForRequestBody<RestTableInterfaces>(query as RestTableInterfaces, fullTableList, C6, (message) => toast.error(message, toastOptions)),
|
|
881
|
-
{
|
|
882
|
-
withCredentials: withCredentials,
|
|
883
|
-
}
|
|
884
|
-
]
|
|
885
|
-
|
|
886
|
-
} else if (requestMethod === PUT) {
|
|
887
|
-
|
|
888
|
-
return [
|
|
889
|
-
convertForRequestBody<RestTableInterfaces>(query as RestTableInterfaces, fullTableList, C6, (message) => toast.error(message, toastOptions)),
|
|
890
|
-
{
|
|
891
|
-
withCredentials: withCredentials,
|
|
892
|
-
}
|
|
893
|
-
]
|
|
894
|
-
} else if (requestMethod === DELETE) {
|
|
895
|
-
|
|
896
|
-
return [{
|
|
897
|
-
withCredentials: withCredentials,
|
|
898
|
-
data: convertForRequestBody<RestTableInterfaces>(query as RestTableInterfaces, fullTableList, C6, (message) => toast.error(message, toastOptions))
|
|
899
|
-
}]
|
|
900
|
-
|
|
901
|
-
} else {
|
|
902
|
-
|
|
903
|
-
throw new Error('The request method (' + requestMethod + ') was not recognized.')
|
|
904
|
-
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
})())
|
|
908
|
-
);
|
|
909
|
-
|
|
910
|
-
if (cachingConfirmed) {
|
|
911
|
-
|
|
912
|
-
// push to cache so we do not repeat the request
|
|
913
|
-
apiRequestCache.push({
|
|
914
|
-
requestArgumentsSerialized: querySerialized,
|
|
915
|
-
request: axiosActiveRequest
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// todo - wip verify this works
|
|
921
|
-
// we had removed the value from the request to add to the URI.
|
|
922
|
-
addBackPK?.(); // adding back so post-processing methods work
|
|
923
|
-
|
|
924
|
-
// returning the promise with this then is important for tests. todo - we could make that optional.
|
|
925
|
-
// https://rapidapi.com/guides/axios-async-await
|
|
926
|
-
return axiosActiveRequest.then(async (response): Promise<AxiosResponse<ResponseDataType, any>> => {
|
|
927
|
-
|
|
928
|
-
if (typeof response.data === 'string') {
|
|
929
|
-
|
|
930
|
-
if (isTest) {
|
|
931
|
-
|
|
932
|
-
console.trace()
|
|
933
|
-
|
|
934
|
-
throw new Error('The response data was a string this typically indicated html was sent. Make sure all cookies (' + JSON.stringify(response.config.headers) + ') needed are present! (' + response.data + ')')
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
return Promise.reject(response);
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
if (cachingConfirmed) {
|
|
943
|
-
|
|
944
|
-
const cacheIndex = apiRequestCache.findIndex(cache => cache.requestArgumentsSerialized === querySerialized);
|
|
945
|
-
|
|
946
|
-
apiRequestCache[cacheIndex].final = false === returnGetNextPageFunction
|
|
947
|
-
|
|
948
|
-
// only cache get method requests
|
|
949
|
-
apiRequestCache[cacheIndex].response = response
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
apiResponse = TestRestfulResponse(response, request?.success, request?.error ?? "An unexpected API error occurred!")
|
|
954
|
-
|
|
955
|
-
if (false === apiResponse) {
|
|
956
|
-
|
|
957
|
-
if (request.debug && isLocal) {
|
|
958
|
-
|
|
959
|
-
toast.warning("DEVS: TestRestfulResponse returned false for (" + operatingTable + ").", toastOptionsDevs);
|
|
960
|
-
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
return response;
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// stateful operations are done in the response callback - its leverages rest generated functions
|
|
968
|
-
if (responseCallback) {
|
|
969
|
-
|
|
970
|
-
responseCallback(response, request, apiResponse)
|
|
971
|
-
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
if (C6.GET === requestMethod) {
|
|
975
|
-
|
|
976
|
-
const responseData = response.data as iGetC6RestResponse<any>;
|
|
977
|
-
|
|
978
|
-
returnGetNextPageFunction = 1 !== query?.[C6.PAGINATION]?.[C6.LIMIT] &&
|
|
979
|
-
query?.[C6.PAGINATION]?.[C6.LIMIT] === responseData.rest.length
|
|
980
|
-
|
|
981
|
-
if (false === isTest || true === isVerbose) {
|
|
982
|
-
|
|
983
|
-
console.groupCollapsed('%c API: Response (' + requestMethod + ' ' + tableName + ') returned length (' + responseData.rest?.length + ') of possible (' + query?.[C6.PAGINATION]?.[C6.LIMIT] + ') limit!', 'color: #0c0')
|
|
984
|
-
|
|
985
|
-
console.log('%c ' + requestMethod + ' ' + tableName, 'color: #0c0')
|
|
986
|
-
|
|
987
|
-
console.log('%c Request Data (note you may see the success and/or error prompt):', 'color: #0c0', request)
|
|
988
|
-
|
|
989
|
-
console.log('%c Response Data:', 'color: #0c0', responseData.rest)
|
|
990
|
-
|
|
991
|
-
console.log('%c Will return get next page function:' + (1 !== query?.[C6.PAGINATION]?.[C6.LIMIT] ? '' : ' (Will not return with explicit limit 1 set)'), 'color: #0c0', true === returnGetNextPageFunction)
|
|
992
|
-
|
|
993
|
-
console.trace();
|
|
994
|
-
|
|
995
|
-
console.groupEnd()
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
if (false === returnGetNextPageFunction
|
|
1000
|
-
&& true === request.debug
|
|
1001
|
-
&& isLocal) {
|
|
1002
|
-
|
|
1003
|
-
toast.success("DEVS: Response returned length (" + responseData.rest?.length + ") less than limit (" + query?.[C6.PAGINATION]?.[C6.LIMIT] + ").", toastOptionsDevs);
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
request.fetchDependencies ??= eFetchDependencies.NONE;
|
|
1008
|
-
|
|
1009
|
-
if (request.fetchDependencies
|
|
1010
|
-
&& 'number' === typeof request.fetchDependencies
|
|
1011
|
-
&& responseData.rest.length > 0) {
|
|
1012
|
-
|
|
1013
|
-
const fetchDependencies = request.fetchDependencies as number;
|
|
1014
|
-
|
|
1015
|
-
console.groupCollapsed('%c API: Fetch Dependencies segment (' + requestMethod + ' ' + tableName + ')'
|
|
1016
|
-
+ (fetchDependencies & eFetchDependencies.CHILDREN ? ' | (CHILDREN|REFERENCED) ' : '')
|
|
1017
|
-
+ (fetchDependencies & eFetchDependencies.PARENTS ? ' | (PARENTS|REFERENCED_BY)' : '')
|
|
1018
|
-
+ (fetchDependencies & eFetchDependencies.C6ENTITY ? ' | (C6ENTITY)' : '')
|
|
1019
|
-
+ (fetchDependencies & eFetchDependencies.RECURSIVE ? ' | (RECURSIVE)' : ''), 'color: #33ccff')
|
|
1020
|
-
|
|
1021
|
-
console.groupCollapsed('Collapsed JS Trace');
|
|
1022
|
-
console.trace(); // hidden in collapsed group
|
|
1023
|
-
console.groupEnd();
|
|
1024
|
-
|
|
1025
|
-
// noinspection JSBitwiseOperatorUsage
|
|
1026
|
-
let dependencies: {
|
|
1027
|
-
[key: string]: iConstraint[]
|
|
1028
|
-
} = {};
|
|
1029
|
-
|
|
1030
|
-
if (fetchDependencies & eFetchDependencies.C6ENTITY) {
|
|
1031
|
-
|
|
1032
|
-
dependencies = operatingTable.endsWith("carbon_carbons")
|
|
1033
|
-
? {
|
|
1034
|
-
// the context of the entity system is a bit different
|
|
1035
|
-
...fetchDependencies & eFetchDependencies.CHILDREN // REFERENCED === CHILDREN
|
|
1036
|
-
? C6.TABLES[operatingTable].TABLE_REFERENCED_BY
|
|
1037
|
-
: {},
|
|
1038
|
-
...fetchDependencies & eFetchDependencies.PARENTS // REFERENCES === PARENTS
|
|
1039
|
-
? C6.TABLES[operatingTable].TABLE_REFERENCES
|
|
1040
|
-
: {}
|
|
1041
|
-
} : {
|
|
1042
|
-
// the context of the entity system is a bit different
|
|
1043
|
-
...fetchDependencies & eFetchDependencies.CHILDREN // REFERENCED === CHILDREN
|
|
1044
|
-
? {
|
|
1045
|
-
...Object.keys(C6.TABLES[operatingTable].TABLE_REFERENCES).reduce((accumulator, columnName) => {
|
|
1046
|
-
|
|
1047
|
-
if (!C6.TABLES[operatingTable].PRIMARY_SHORT.includes(columnName)) {
|
|
1048
|
-
accumulator[columnName] = C6.TABLES[operatingTable].TABLE_REFERENCES[columnName]
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
return accumulator
|
|
1052
|
-
}, {}),
|
|
1053
|
-
...C6.TABLES[operatingTable].TABLE_REFERENCED_BY // it is unlikely that a C6 table will have any TABLE_REFERENCED_BY
|
|
1054
|
-
}
|
|
1055
|
-
: {},
|
|
1056
|
-
...fetchDependencies & eFetchDependencies.PARENTS // REFERENCES === PARENTS
|
|
1057
|
-
? C6.TABLES[operatingTable].PRIMARY_SHORT.reduce((accumulator, primaryKey) => {
|
|
1058
|
-
if (primaryKey in C6.TABLES[operatingTable].TABLE_REFERENCES) {
|
|
1059
|
-
accumulator[primaryKey] = C6.TABLES[operatingTable].TABLE_REFERENCES[primaryKey]
|
|
1060
|
-
}
|
|
1061
|
-
return accumulator
|
|
1062
|
-
}, {})
|
|
1063
|
-
: {}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
} else {
|
|
1067
|
-
|
|
1068
|
-
// this is the natural mysql context
|
|
1069
|
-
dependencies = {
|
|
1070
|
-
...fetchDependencies & eFetchDependencies.REFERENCED // REFERENCED === CHILDREN
|
|
1071
|
-
? C6.TABLES[operatingTable].TABLE_REFERENCED_BY
|
|
1072
|
-
: {},
|
|
1073
|
-
...fetchDependencies & eFetchDependencies.REFERENCES // REFERENCES === PARENTS
|
|
1074
|
-
? C6.TABLES[operatingTable].TABLE_REFERENCES
|
|
1075
|
-
: {}
|
|
1076
|
-
};
|
|
1077
|
-
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
let fetchReferences: {
|
|
1081
|
-
[externalTable: string]: {
|
|
1082
|
-
[column: string]: string[]
|
|
1083
|
-
}
|
|
1084
|
-
} = {}
|
|
1085
|
-
|
|
1086
|
-
let apiRequestPromises: Array<apiReturn<iGetC6RestResponse<any>>> = []
|
|
1087
|
-
|
|
1088
|
-
console.log('%c Dependencies', 'color: #005555', dependencies)
|
|
1089
|
-
|
|
1090
|
-
Object.keys(dependencies)
|
|
1091
|
-
.forEach(column => dependencies[column]
|
|
1092
|
-
.forEach((constraint) => {
|
|
1093
|
-
|
|
1094
|
-
const columnValues = responseData.rest[column] ?? responseData.rest.map((row) => {
|
|
1095
|
-
|
|
1096
|
-
if (operatingTable.endsWith("carbons")
|
|
1097
|
-
&& 'entity_tag' in row
|
|
1098
|
-
&& !constraint.TABLE.endsWith(row['entity_tag'].split('\\').pop().toLowerCase())) {
|
|
1099
|
-
|
|
1100
|
-
return false; // map
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
if (!(column in row)) {
|
|
1105
|
-
return false
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// todo - row[column] is a FK value, we should optionally remove values that are already in state
|
|
1109
|
-
// this could be any column in the table constraint.TABLE, not just the primary key
|
|
1110
|
-
|
|
1111
|
-
return row[column]
|
|
1112
|
-
|
|
1113
|
-
}).filter(n => n) ?? [];
|
|
1114
|
-
|
|
1115
|
-
if (columnValues.length === 0) {
|
|
1116
|
-
|
|
1117
|
-
return; // forEach
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
fetchReferences[constraint.TABLE] ??= {};
|
|
1122
|
-
|
|
1123
|
-
fetchReferences[constraint.TABLE][constraint.COLUMN] ??= []
|
|
1124
|
-
|
|
1125
|
-
fetchReferences[constraint.TABLE][constraint.COLUMN].push(columnValues)
|
|
1126
|
-
|
|
1127
|
-
}));
|
|
1128
|
-
|
|
1129
|
-
console.log('fetchReferences', fetchReferences)
|
|
1130
|
-
|
|
1131
|
-
for (const tableToFetch in fetchReferences) {
|
|
1132
|
-
|
|
1133
|
-
if (fetchDependencies & eFetchDependencies.C6ENTITY
|
|
1134
|
-
&& 'string' === typeof tableName
|
|
1135
|
-
&& tableName.endsWith("carbon_carbons")) {
|
|
1136
|
-
|
|
1137
|
-
// todo - rethink the table ref entity system - when tables are renamed? no hooks exist in mysql
|
|
1138
|
-
// since were already filtering on column, we can assume the first row constraint is the same as the rest
|
|
1139
|
-
|
|
1140
|
-
const referencesTables: string[] = responseData.rest.reduce((accumulator: string[], row: {
|
|
1141
|
-
[x: string]: string;
|
|
1142
|
-
}) => {
|
|
1143
|
-
if ('entity_tag' in row && !accumulator.includes(row['entity_tag'])) {
|
|
1144
|
-
accumulator.push(row['entity_tag']);
|
|
1145
|
-
}
|
|
1146
|
-
return accumulator;
|
|
1147
|
-
}, []).map((entityTag) => entityTag.split('\\').pop().toLowerCase());
|
|
1148
|
-
|
|
1149
|
-
const shouldContinue = referencesTables.find((referencesTable) => tableToFetch.endsWith(referencesTable))
|
|
1150
|
-
|
|
1151
|
-
if (!shouldContinue) {
|
|
1152
|
-
|
|
1153
|
-
console.log('%c C6ENTITY: The constraintTableName (' + tableToFetch + ') did not end with any value in referencesTables', 'color: #c00', referencesTables)
|
|
1154
|
-
|
|
1155
|
-
continue;
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
console.log('%c C6ENTITY: The constraintTableName (' + tableToFetch + ') will be fetched.', 'color: #0c0')
|
|
1160
|
-
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
const fetchTable = await C6.IMPORT(tableToFetch)
|
|
1164
|
-
|
|
1165
|
-
const RestApi = fetchTable.default
|
|
1166
|
-
|
|
1167
|
-
console.log('%c Fetch Dependencies will select (' + tableToFetch + ') using GET request', 'color: #33ccff')
|
|
1168
|
-
|
|
1169
|
-
let nextFetchDependencies = eFetchDependencies.NONE
|
|
1170
|
-
|
|
1171
|
-
if (fetchDependencies & eFetchDependencies.RECURSIVE) {
|
|
1172
|
-
|
|
1173
|
-
if (fetchDependencies & eFetchDependencies.ALL) {
|
|
1174
|
-
|
|
1175
|
-
throw Error('Recursive fetch dependencies with both PARENT and CHILD reference will result in an infin1ite loop. As there is not real ending condition, this is not supported.')
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
nextFetchDependencies = fetchDependencies
|
|
1180
|
-
|
|
1181
|
-
} else if (fetchDependencies & eFetchDependencies.C6ENTITY) {
|
|
1182
|
-
|
|
1183
|
-
if (tableToFetch === "carbon_carbons") {
|
|
1184
|
-
|
|
1185
|
-
nextFetchDependencies = fetchDependencies
|
|
1186
|
-
|
|
1187
|
-
} else {
|
|
1188
|
-
|
|
1189
|
-
nextFetchDependencies = fetchDependencies ^ eFetchDependencies.C6ENTITY
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
console.log('fetchReferences', fetchReferences[tableToFetch], "Current fetchDependencies for (" + operatingTable + "):", fetchDependencies, "New fetchDependencies for (" + tableToFetch + "): ", nextFetchDependencies)
|
|
1196
|
-
|
|
1197
|
-
// todo - filter out ids that exist in state?!? note - remember that this does not necessarily mean the pk, but only known is its an FK to somewhere
|
|
1198
|
-
// it not certain that they are using carbons' entities either
|
|
1199
|
-
|
|
1200
|
-
// this is a dynamic call to the rest api, any generated table may resolve with (RestApi)
|
|
1201
|
-
// todo - using value to avoid joins.... but. maybe this should be a parameterizable option -- think race conditions; its safer to join
|
|
1202
|
-
apiRequestPromises.push(RestApi.Get({
|
|
1203
|
-
[C6.WHERE]: {
|
|
1204
|
-
0: Object.keys(fetchReferences[tableToFetch]).reduce((sum, column) => {
|
|
1205
|
-
|
|
1206
|
-
fetchReferences[tableToFetch][column] = fetchReferences[tableToFetch][column].flat(Infinity)
|
|
1207
|
-
|
|
1208
|
-
if (0 === fetchReferences[tableToFetch][column].length) {
|
|
1209
|
-
|
|
1210
|
-
console.warn('The column (' + column + ') was not found in the response data. We will not fetch.', responseData)
|
|
1211
|
-
|
|
1212
|
-
return false;
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
sum[column] = fetchReferences[tableToFetch][column].length === 1
|
|
1217
|
-
? fetchReferences[tableToFetch][column][0]
|
|
1218
|
-
: [
|
|
1219
|
-
C6.IN, fetchReferences[tableToFetch][column]
|
|
1220
|
-
]
|
|
1221
|
-
|
|
1222
|
-
return sum
|
|
1223
|
-
|
|
1224
|
-
}, {})
|
|
1225
|
-
},
|
|
1226
|
-
fetchDependencies: nextFetchDependencies
|
|
1227
|
-
}
|
|
1228
|
-
));
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
console.groupEnd()
|
|
1233
|
-
|
|
1234
|
-
await Promise.all(apiRequestPromises)
|
|
1235
|
-
|
|
1236
|
-
apiRequestPromises.map(async (promise) => {
|
|
1237
|
-
if (!Array.isArray(request.fetchDependencies)) {
|
|
1238
|
-
request.fetchDependencies = [];
|
|
1239
|
-
}
|
|
1240
|
-
request.fetchDependencies.push(await promise)
|
|
1241
|
-
})
|
|
1242
|
-
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (request.debug && isLocal) {
|
|
1249
|
-
|
|
1250
|
-
toast.success("DEVS: (" + requestMethod + ") request complete.", toastOptionsDevs);
|
|
1251
|
-
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
return response;
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1257
|
-
)
|
|
1258
|
-
;
|
|
1259
|
-
|
|
1260
|
-
} catch
|
|
1261
|
-
(error) {
|
|
1262
|
-
|
|
1263
|
-
if (isTest) {
|
|
1264
|
-
|
|
1265
|
-
throw new Error(JSON.stringify(error))
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
console.groupCollapsed('%c API: An error occurred in the try catch block. returning null!', 'color: #ff0000')
|
|
1270
|
-
|
|
1271
|
-
console.log('%c ' + requestMethod + ' ' + tableName, 'color: #A020F0')
|
|
1272
|
-
|
|
1273
|
-
console.warn(error)
|
|
1274
|
-
|
|
1275
|
-
console.trace()
|
|
1276
|
-
|
|
1277
|
-
console.groupEnd()
|
|
1278
|
-
|
|
1279
|
-
TestRestfulResponse(error, request?.success, request?.error || "An restful API error occurred!")
|
|
1280
|
-
|
|
1281
|
-
return null;
|
|
1282
|
-
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
return await apiRequest()
|
|
1288
|
-
|
|
1289
|
-
}
|
|
1290
|
-
|
|
25
|
+
return async (
|
|
26
|
+
request: iAPI<Modify<RestTableInterface, RequestTableOverrides>> & CustomAndRequiredFields = {} as iAPI<Modify<RestTableInterface, RequestTableOverrides>> & CustomAndRequiredFields
|
|
27
|
+
): Promise<apiReturn<ResponseDataType>> => {
|
|
28
|
+
|
|
29
|
+
// SQL path if on Node with a provided pool
|
|
30
|
+
if (isNode && config.mysqlPool) {
|
|
31
|
+
const {SqlExecutor} = await import('./executors/SqlExecutor');
|
|
32
|
+
const executor = new SqlExecutor<
|
|
33
|
+
RestShortTableName,
|
|
34
|
+
RestTableInterface,
|
|
35
|
+
PrimaryKey,
|
|
36
|
+
CustomAndRequiredFields,
|
|
37
|
+
RequestTableOverrides,
|
|
38
|
+
ResponseDataType
|
|
39
|
+
>(config, request);
|
|
40
|
+
return executor.execute();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// HTTP path fallback
|
|
44
|
+
const {HttpExecutor} = await import('./executors/HttpExecutor');
|
|
45
|
+
const http = new HttpExecutor<
|
|
46
|
+
RestShortTableName,
|
|
47
|
+
RestTableInterface,
|
|
48
|
+
PrimaryKey,
|
|
49
|
+
CustomAndRequiredFields,
|
|
50
|
+
RequestTableOverrides,
|
|
51
|
+
ResponseDataType
|
|
52
|
+
>(config, request);
|
|
53
|
+
return http.execute();
|
|
54
|
+
};
|
|
1291
55
|
}
|
|
1292
|
-
|