@carbonorm/carbonnode 2.0.34 → 3.0.1

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.
Files changed (106) hide show
  1. package/dist/api/builders/sqlBuilder.d.ts +3 -0
  2. package/dist/api/carbonSqlExecutor.d.ts +17 -0
  3. package/dist/api/convertForRequestBody.d.ts +1 -1
  4. package/dist/api/executors/Executor.d.ts +18 -0
  5. package/dist/api/executors/HttpExecutor.d.ts +15 -0
  6. package/dist/api/executors/SqlExecutor.d.ts +11 -0
  7. package/dist/api/interfaces/ormInterfaces.d.ts +22 -1
  8. package/dist/api/rest/Blog_Categories.d.ts +37 -0
  9. package/dist/api/rest/Blog_Categories.test.d.ts +11 -0
  10. package/dist/api/rest/Blog_Images.d.ts +37 -0
  11. package/dist/api/rest/Blog_Images.test.d.ts +15 -0
  12. package/dist/api/rest/Blog_Post_Categories.d.ts +37 -0
  13. package/dist/api/rest/Blog_Post_Categories.test.d.ts +13 -0
  14. package/dist/api/rest/Blog_Post_Tags.d.ts +37 -0
  15. package/dist/api/rest/Blog_Post_Tags.test.d.ts +13 -0
  16. package/dist/api/rest/Blog_Posts.d.ts +37 -0
  17. package/dist/api/rest/Blog_Posts.test.d.ts +21 -0
  18. package/dist/api/rest/Blog_Tags.d.ts +37 -0
  19. package/dist/api/rest/Blog_Tags.test.d.ts +11 -0
  20. package/dist/api/rest/C6.d.ts +1000 -0
  21. package/dist/api/rest/Cache.d.ts +37 -0
  22. package/dist/api/rest/Cache.test.d.ts +12 -0
  23. package/dist/api/rest/Cities.d.ts +37 -0
  24. package/dist/api/rest/Cities.test.d.ts +17 -0
  25. package/dist/api/rest/Counties.d.ts +37 -0
  26. package/dist/api/rest/Counties.test.d.ts +18 -0
  27. package/dist/api/rest/Countries.d.ts +37 -0
  28. package/dist/api/rest/Countries.test.d.ts +24 -0
  29. package/dist/api/rest/Geometries.d.ts +37 -0
  30. package/dist/api/rest/Geometries.test.d.ts +16 -0
  31. package/dist/api/rest/Images.d.ts +37 -0
  32. package/dist/api/rest/Images.test.d.ts +16 -0
  33. package/dist/api/rest/Land_Section_Info.d.ts +37 -0
  34. package/dist/api/rest/Land_Section_Info.test.d.ts +15 -0
  35. package/dist/api/rest/Neighborhoods.d.ts +37 -0
  36. package/dist/api/rest/Neighborhoods.test.d.ts +12 -0
  37. package/dist/api/rest/Parcel_Building_Details.d.ts +37 -0
  38. package/dist/api/rest/Parcel_Building_Details.test.d.ts +40 -0
  39. package/dist/api/rest/Parcel_Neighborhoods.d.ts +37 -0
  40. package/dist/api/rest/Parcel_Neighborhoods.test.d.ts +15 -0
  41. package/dist/api/rest/Parcel_Owners.d.ts +37 -0
  42. package/dist/api/rest/Parcel_Owners.test.d.ts +23 -0
  43. package/dist/api/rest/Parcel_Sales.d.ts +37 -0
  44. package/dist/api/rest/Parcel_Sales.test.d.ts +19 -0
  45. package/dist/api/rest/Parcel_Tax_History.d.ts +37 -0
  46. package/dist/api/rest/Parcel_Tax_History.test.d.ts +24 -0
  47. package/dist/api/rest/Parcels.d.ts +37 -0
  48. package/dist/api/rest/Parcels.test.d.ts +42 -0
  49. package/dist/api/rest/Payment_Charge_Logs.d.ts +37 -0
  50. package/dist/api/rest/Payment_Charge_Logs.test.d.ts +17 -0
  51. package/dist/api/rest/Payment_Subscriptions.d.ts +37 -0
  52. package/dist/api/rest/Payment_Subscriptions.test.d.ts +20 -0
  53. package/dist/api/rest/Property_Units.d.ts +37 -0
  54. package/dist/api/rest/Property_Units.test.d.ts +55 -0
  55. package/dist/api/rest/Sources.d.ts +37 -0
  56. package/dist/api/rest/Sources.test.d.ts +17 -0
  57. package/dist/api/rest/States.d.ts +37 -0
  58. package/dist/api/rest/States.test.d.ts +20 -0
  59. package/dist/api/rest/Tax_Districts.d.ts +37 -0
  60. package/dist/api/rest/Tax_Districts.test.d.ts +12 -0
  61. package/dist/api/rest/Users.d.ts +37 -0
  62. package/dist/api/rest/Users.test.d.ts +14 -0
  63. package/dist/api/rest/Valuation_Reports.d.ts +37 -0
  64. package/dist/api/rest/Valuation_Reports.test.d.ts +36 -0
  65. package/dist/api/rest/Zip_Codes.d.ts +37 -0
  66. package/dist/api/rest/Zip_Codes.test.d.ts +15 -0
  67. package/dist/api/restRequest.d.ts +5 -141
  68. package/dist/api/types/dynamicFetching.d.ts +10 -0
  69. package/dist/api/types/modifyTypes.d.ts +9 -0
  70. package/dist/api/types/mysqlTypes.d.ts +4 -0
  71. package/dist/api/types/ormInterfaces.d.ts +223 -0
  72. package/dist/api/utils/apiHelpers.d.ts +9 -0
  73. package/dist/api/utils/cacheManager.d.ts +10 -0
  74. package/dist/api/utils/logger.d.ts +7 -0
  75. package/dist/api/utils/sortAndSerializeQueryObject.d.ts +1 -0
  76. package/dist/api/utils/testHelpers.d.ts +1 -0
  77. package/dist/api/utils/toastNotifier.d.ts +2 -0
  78. package/dist/index.cjs.js +973 -537
  79. package/dist/index.cjs.js.map +1 -1
  80. package/dist/index.d.ts +16 -1
  81. package/dist/index.esm.js +958 -537
  82. package/dist/index.esm.js.map +1 -1
  83. package/dist/variables/isNode.d.ts +2 -0
  84. package/package.json +32 -6
  85. package/scripts/assets/handlebars/C6.ts.handlebars +5 -1
  86. package/scripts/generateRestBindings.cjs +89 -23
  87. package/scripts/generateRestBindings.ts +100 -27
  88. package/src/api/builders/sqlBuilder.ts +173 -0
  89. package/src/api/convertForRequestBody.ts +1 -2
  90. package/src/api/executors/Executor.ts +26 -0
  91. package/src/api/executors/HttpExecutor.ts +790 -0
  92. package/src/api/executors/SqlExecutor.ts +90 -0
  93. package/src/api/restRequest.ts +20 -1128
  94. package/src/api/types/dynamicFetching.ts +10 -0
  95. package/src/api/types/modifyTypes.ts +25 -0
  96. package/src/api/types/mysqlTypes.ts +33 -0
  97. package/src/api/types/ormInterfaces.ts +287 -0
  98. package/src/api/utils/apiHelpers.ts +83 -0
  99. package/src/api/utils/cacheManager.ts +67 -0
  100. package/src/api/utils/logger.ts +24 -0
  101. package/src/api/utils/sortAndSerializeQueryObject.ts +12 -0
  102. package/src/api/utils/testHelpers.ts +24 -0
  103. package/src/api/utils/toastNotifier.ts +11 -0
  104. package/src/index.ts +16 -1
  105. package/src/variables/isNode.ts +3 -0
  106. package/src/api/interfaces/ormInterfaces.ts +0 -79
@@ -0,0 +1,790 @@
1
+ import {AxiosPromise, AxiosResponse} from "axios";
2
+ import {toast} from "react-toastify";
3
+ import isLocal from "../../variables/isLocal";
4
+ import isTest from "../../variables/isTest";
5
+ import isVerbose from "../../variables/isVerbose";
6
+ import convertForRequestBody from "../convertForRequestBody";
7
+ import {eFetchDependencies} from "../types/dynamicFetching";
8
+ import {Modify} from "../types/modifyTypes";
9
+ import {apiReturn, DELETE, GET, iCacheAPI, iConstraint, iGetC6RestResponse, POST, PUT, RequestQueryBody} from "../types/ormInterfaces";
10
+ import {removePrefixIfExists, TestRestfulResponse} from "../utils/apiHelpers";
11
+ import {apiRequestCache, checkCache, userCustomClearCache} from "../utils/cacheManager";
12
+ import {sortAndSerializeQueryObject} from "../utils/sortAndSerializeQueryObject";
13
+ import {Executor} from "./Executor";
14
+ import {toastOptions, toastOptionsDevs } from "variables/toastOptions";
15
+
16
+ export class HttpExecutor<
17
+ CustomAndRequiredFields extends { [key: string]: any }, // CustomAndRequiredFields
18
+ RestTableInterfaces extends { [key: string]: any }, // RestTableInterfaces
19
+ RequestTableOverrides = { [key in keyof RestTableInterfaces]: any }, // RequestTableOverrides
20
+ ResponseDataType = any, // ResponseDataType
21
+ RestShortTableNames extends string = "" // RestShortTableNames
22
+ >
23
+ extends Executor<
24
+ CustomAndRequiredFields,
25
+ RestTableInterfaces,
26
+ RequestTableOverrides,
27
+ ResponseDataType,
28
+ RestShortTableNames
29
+ > {
30
+
31
+ public async execute() : Promise<apiReturn<ResponseDataType>> {
32
+
33
+ const {
34
+ C6,
35
+ axios,
36
+ restURL,
37
+ withCredentials,
38
+ tableName,
39
+ requestMethod,
40
+ queryCallback,
41
+ responseCallback,
42
+ skipPrimaryCheck,
43
+ clearCache,
44
+ } = this.config
45
+
46
+ const fullTableList = Array.isArray(tableName) ? tableName : [tableName];
47
+
48
+ const operatingTableFullName = fullTableList[0];
49
+
50
+ const operatingTable = removePrefixIfExists(operatingTableFullName, C6.PREFIX);
51
+
52
+ const tables = fullTableList.join(',')
53
+
54
+ switch (requestMethod) {
55
+ case GET:
56
+ case POST:
57
+ case PUT:
58
+ case DELETE:
59
+ break;
60
+ default:
61
+ throw Error('Bad request method passed to getApi')
62
+ }
63
+
64
+ if (null !== clearCache || undefined !== clearCache) {
65
+
66
+ userCustomClearCache[tables + requestMethod] = clearCache;
67
+
68
+ }
69
+
70
+ console.groupCollapsed('%c API: (' + requestMethod + ') Request for (' + tableName + ')', 'color: #0c0')
71
+
72
+ console.log('request', this.request)
73
+
74
+ console.groupEnd()
75
+
76
+ // an undefined query would indicate queryCallback returned undefined,
77
+ // thus the request shouldn't fire as is in custom cache
78
+ let query: RequestQueryBody<Modify<RestTableInterfaces, RequestTableOverrides>> | undefined | null;
79
+
80
+ if ('function' === typeof queryCallback) {
81
+
82
+ query = queryCallback(this.request); // obj or obj[]
83
+
84
+ } else {
85
+
86
+ query = queryCallback;
87
+
88
+ }
89
+
90
+ if (undefined === query || null === query) {
91
+
92
+ if (this.request.debug && isLocal) {
93
+
94
+ toast.warning("DEV: queryCallback returned undefined, signaling in Custom Cache. (returning null)", toastOptionsDevs)
95
+
96
+ }
97
+
98
+ console.groupCollapsed('%c API: (' + requestMethod + ') Request Query for (' + tableName + ') undefined, returning null (will not fire ajax)!', 'color: #c00')
99
+
100
+ 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')
101
+
102
+ console.trace();
103
+
104
+ console.groupEnd()
105
+
106
+ return null;
107
+
108
+ }
109
+
110
+ if (C6.GET === requestMethod) {
111
+
112
+ if (undefined === query[C6.PAGINATION]) {
113
+
114
+ query[C6.PAGINATION] = {}
115
+
116
+ }
117
+
118
+ query[C6.PAGINATION][C6.PAGE] = query[C6.PAGINATION][C6.PAGE] || 1;
119
+
120
+ query[C6.PAGINATION][C6.LIMIT] = query[C6.PAGINATION][C6.LIMIT] || 100;
121
+
122
+ }
123
+
124
+ // this could return itself with a new page number, or undefined if the end is reached
125
+ const apiRequest = async (): Promise<apiReturn<ResponseDataType>> => {
126
+
127
+ const {
128
+ debug,
129
+ cacheResults = (C6.GET === requestMethod),
130
+ dataInsertMultipleRows,
131
+ success,
132
+ fetchDependencies = eFetchDependencies.NONE,
133
+ error = "An unexpected API error occurred!"
134
+ } = this.request
135
+
136
+ if (C6.GET === requestMethod
137
+ && undefined !== query?.[C6.PAGINATION]?.[C6.PAGE]
138
+ && 1 !== query[C6.PAGINATION][C6.PAGE]) {
139
+
140
+ console.groupCollapsed('Request on table (' + tableName + ') is firing for page (' + query[C6.PAGINATION][C6.PAGE] + '), please wait!')
141
+
142
+ console.log('Request Data (note you may see the success and/or error prompt):', this.request)
143
+
144
+ console.trace();
145
+
146
+ console.groupEnd()
147
+
148
+ }
149
+
150
+ // 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.
151
+ // 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
152
+
153
+ // literally impossible for query to be undefined or null here but the editor is too busy licking windows to understand that
154
+ let querySerialized: string = sortAndSerializeQueryObject(tables, query ?? {});
155
+
156
+ let cacheResult: iCacheAPI | undefined = apiRequestCache.find(cache => cache.requestArgumentsSerialized === querySerialized);
157
+
158
+ let cachingConfirmed = false;
159
+
160
+ // determine if we need to paginate.
161
+ if (requestMethod === C6.GET) {
162
+
163
+ if (undefined === query?.[C6.PAGINATION]) {
164
+
165
+ if (undefined === query || null === query) {
166
+
167
+ query = {}
168
+
169
+ }
170
+
171
+ query[C6.PAGINATION] = {}
172
+
173
+ }
174
+
175
+ query[C6.PAGINATION][C6.PAGE] = query[C6.PAGINATION][C6.PAGE] || 1;
176
+
177
+ query[C6.PAGINATION][C6.LIMIT] = query[C6.PAGINATION][C6.LIMIT] || 100;
178
+
179
+ // this will evaluate true most the time
180
+ if (true === cacheResults) {
181
+
182
+ // just find the next, non-fetched, page and return a function to request it
183
+ if (undefined !== cacheResult) {
184
+
185
+ do {
186
+
187
+ const cacheCheck = checkCache<ResponseDataType>(cacheResult, requestMethod, tableName, this.request);
188
+
189
+ if (false !== cacheCheck) {
190
+
191
+ return cacheCheck;
192
+
193
+ }
194
+
195
+ // this line incrementing page is why we return recursively
196
+ ++query[C6.PAGINATION][C6.PAGE];
197
+
198
+ // this json stringify is to capture the new page number
199
+ querySerialized = sortAndSerializeQueryObject(tables, query ?? {});
200
+
201
+ cacheResult = apiRequestCache.find(cache => cache.requestArgumentsSerialized === querySerialized)
202
+
203
+ } while (undefined !== cacheResult)
204
+
205
+ if (debug && isLocal) {
206
+
207
+ toast.warning("DEVS: Request in cache. (" + apiRequestCache.findIndex(cache => cache.requestArgumentsSerialized === querySerialized) + "). Returning function to request page (" + query[C6.PAGINATION][C6.PAGE] + ")", toastOptionsDevs);
208
+
209
+ }
210
+
211
+ // @ts-ignore - this is an incorrect warning on TS, it's well typed
212
+ return apiRequest;
213
+
214
+ }
215
+
216
+ cachingConfirmed = true;
217
+
218
+ } else {
219
+
220
+ if (debug && isLocal) {
221
+
222
+ toast.info("DEVS: Ignore cache was set to true.", toastOptionsDevs);
223
+
224
+ }
225
+
226
+ }
227
+
228
+ if (debug && isLocal) {
229
+
230
+ toast.success("DEVS: Request not in cache." + (requestMethod === C6.GET ? "Page (" + query[C6.PAGINATION][C6.PAGE] + ")." : '') + " Logging cache 2 console.", toastOptionsDevs);
231
+
232
+ }
233
+
234
+ } else if (cacheResults) { // if we are not getting, we are updating, deleting, or inserting
235
+
236
+ if (cacheResult) {
237
+ const cacheCheck = checkCache<ResponseDataType>(cacheResult, requestMethod, tableName, this.request);
238
+
239
+ if (false !== cacheCheck) {
240
+
241
+ return cacheCheck;
242
+
243
+ }
244
+ }
245
+
246
+ cachingConfirmed = true;
247
+ // push to cache so we do not repeat the request
248
+
249
+ }
250
+
251
+ let addBackPK: (() => void) | undefined;
252
+
253
+ let apiResponse: string | boolean | number | undefined;
254
+
255
+ let returnGetNextPageFunction = false;
256
+
257
+ let restRequestUri: string = restURL + operatingTable + '/';
258
+
259
+ const needsConditionOrPrimaryCheck = (PUT === requestMethod || DELETE === requestMethod)
260
+ && false === skipPrimaryCheck;
261
+
262
+ const TABLES = C6.TABLES;
263
+
264
+ // todo - aggregate primary key check with condition check
265
+ // check if PK exists in query, clone so pop does not affect the real data
266
+ const primaryKey = structuredClone(TABLES[operatingTable]?.PRIMARY)?.pop()?.split('.')?.pop();
267
+
268
+ if (needsConditionOrPrimaryCheck) {
269
+
270
+ if (undefined === primaryKey) {
271
+
272
+ if (null === query
273
+ || undefined === query
274
+ || undefined === query?.[C6.WHERE]
275
+ || (true === Array.isArray(query[C6.WHERE])
276
+ || query[C6.WHERE].length === 0)
277
+ || (Object.keys(query?.[C6.WHERE]).length === 0)
278
+ ) {
279
+
280
+ console.error(query)
281
+
282
+ 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 + ').')
283
+
284
+ }
285
+
286
+ } else {
287
+
288
+ if (undefined === query
289
+ || null === query
290
+ || false === primaryKey in query) {
291
+
292
+ if (true === debug && isLocal) {
293
+
294
+ toast.error('DEVS: The primary key (' + primaryKey + ') was not provided!!')
295
+
296
+ }
297
+
298
+ throw Error('You must provide the primary key (' + primaryKey + ') for table (' + operatingTable + '). Request (' + JSON.stringify(this.request, undefined, 4) + ') Query (' + JSON.stringify(query) + ')');
299
+
300
+ }
301
+
302
+ if (undefined === query?.[primaryKey]
303
+ || null === query?.[primaryKey]) {
304
+
305
+ toast.error('The primary key (' + primaryKey + ') provided is undefined or null explicitly!!')
306
+
307
+ throw Error('The primary key (' + primaryKey + ') provided in the request was exactly equal to undefined.');
308
+
309
+ }
310
+
311
+ }
312
+
313
+ }
314
+
315
+ // A part of me exists that wants to remove this, but it's a good feature
316
+ // this allows developers the ability to cache requests based on primary key
317
+ // for tables like `photos` this can be a huge performance boost
318
+ if (undefined !== query
319
+ && null !== query
320
+ && undefined !== primaryKey
321
+ && primaryKey in query) {
322
+
323
+ restRequestUri += query[primaryKey] + '/'
324
+
325
+ const removedPkValue = query[primaryKey];
326
+
327
+ addBackPK = () => {
328
+ query ??= {}
329
+ query[primaryKey] = removedPkValue
330
+ }
331
+
332
+ delete query[primaryKey]
333
+
334
+ console.log('query', query, 'primaryKey', primaryKey, 'removedPkValue', removedPkValue)
335
+
336
+ } else {
337
+
338
+ console.log('query', query)
339
+
340
+ }
341
+
342
+ try {
343
+
344
+ console.groupCollapsed('%c API: (' + requestMethod + ') Request Query for (' + operatingTable + ') is about to fire, will return with promise!', 'color: #A020F0')
345
+
346
+ console.log(this.request)
347
+
348
+ 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')
349
+
350
+ 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')
351
+
352
+ console.trace()
353
+
354
+ console.groupEnd()
355
+
356
+ const axiosActiveRequest: AxiosPromise<ResponseDataType> = axios![requestMethod.toLowerCase()]<ResponseDataType>(
357
+ restRequestUri,
358
+ ...((() => {
359
+
360
+ // @link - https://axios-http.com/docs/instance
361
+ // How configuration vs data is passed is variable, use documentation above for reference
362
+ if (requestMethod === GET) {
363
+
364
+ return [{
365
+ withCredentials: withCredentials,
366
+ params: query
367
+ }]
368
+
369
+ } else if (requestMethod === POST) {
370
+
371
+ if (undefined !== dataInsertMultipleRows) {
372
+
373
+ return [
374
+ dataInsertMultipleRows.map(data =>
375
+ convertForRequestBody<typeof data>(data, fullTableList, C6, (message) => toast.error(message, toastOptions))),
376
+ {
377
+ withCredentials: withCredentials,
378
+ }
379
+ ]
380
+
381
+ }
382
+
383
+ return [
384
+ convertForRequestBody<RestTableInterfaces>(query as RestTableInterfaces, fullTableList, C6, (message) => toast.error(message, toastOptions)),
385
+ {
386
+ withCredentials: withCredentials,
387
+ }
388
+ ]
389
+
390
+ } else if (requestMethod === PUT) {
391
+
392
+ return [
393
+ convertForRequestBody<RestTableInterfaces>(query as RestTableInterfaces, fullTableList, C6, (message) => toast.error(message, toastOptions)),
394
+ {
395
+ withCredentials: withCredentials,
396
+ }
397
+ ]
398
+ } else if (requestMethod === DELETE) {
399
+
400
+ return [{
401
+ withCredentials: withCredentials,
402
+ data: convertForRequestBody<RestTableInterfaces>(query as RestTableInterfaces, fullTableList, C6, (message) => toast.error(message, toastOptions))
403
+ }]
404
+
405
+ } else {
406
+
407
+ throw new Error('The request method (' + requestMethod + ') was not recognized.')
408
+
409
+ }
410
+
411
+ })())
412
+ );
413
+
414
+ if (cachingConfirmed) {
415
+
416
+ // push to cache so we do not repeat the request
417
+ apiRequestCache.push({
418
+ requestArgumentsSerialized: querySerialized,
419
+ request: axiosActiveRequest
420
+ });
421
+
422
+ }
423
+
424
+ // todo - wip verify this works
425
+ // we had removed the value from the request to add to the URI.
426
+ addBackPK?.(); // adding back so post-processing methods work
427
+
428
+ // returning the promise with this then is important for tests. todo - we could make that optional.
429
+ // https://rapidapi.com/guides/axios-async-await
430
+ return axiosActiveRequest.then(async (response): Promise<AxiosResponse<ResponseDataType, any>> => {
431
+
432
+ if (typeof response.data === 'string') {
433
+
434
+ if (isTest) {
435
+
436
+ console.trace()
437
+
438
+ 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 + ')')
439
+
440
+ }
441
+
442
+ return Promise.reject(response);
443
+
444
+ }
445
+
446
+ if (cachingConfirmed) {
447
+
448
+ const cacheIndex = apiRequestCache.findIndex(cache => cache.requestArgumentsSerialized === querySerialized);
449
+
450
+ apiRequestCache[cacheIndex].final = false === returnGetNextPageFunction
451
+
452
+ // only cache get method requests
453
+ apiRequestCache[cacheIndex].response = response
454
+
455
+ }
456
+
457
+ apiResponse = TestRestfulResponse(response, success, error)
458
+
459
+ if (false === apiResponse) {
460
+
461
+ if (debug && isLocal) {
462
+
463
+ toast.warning("DEVS: TestRestfulResponse returned false for (" + operatingTable + ").", toastOptionsDevs);
464
+
465
+ }
466
+
467
+ return response;
468
+
469
+ }
470
+
471
+ // stateful operations are done in the response callback - its leverages rest generated functions
472
+ if (responseCallback) {
473
+
474
+ responseCallback(response, this.request, apiResponse)
475
+
476
+ }
477
+
478
+ if (C6.GET === requestMethod) {
479
+
480
+ const responseData = response.data as iGetC6RestResponse<any>;
481
+
482
+ returnGetNextPageFunction = 1 !== query?.[C6.PAGINATION]?.[C6.LIMIT] &&
483
+ query?.[C6.PAGINATION]?.[C6.LIMIT] === responseData.rest.length
484
+
485
+ if (false === isTest || true === isVerbose) {
486
+
487
+ console.groupCollapsed('%c API: Response (' + requestMethod + ' ' + tableName + ') returned length (' + responseData.rest?.length + ') of possible (' + query?.[C6.PAGINATION]?.[C6.LIMIT] + ') limit!', 'color: #0c0')
488
+
489
+ console.log('%c ' + requestMethod + ' ' + tableName, 'color: #0c0')
490
+
491
+ console.log('%c Request Data (note you may see the success and/or error prompt):', 'color: #0c0', this.request)
492
+
493
+ console.log('%c Response Data:', 'color: #0c0', responseData.rest)
494
+
495
+ 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)
496
+
497
+ console.trace();
498
+
499
+ console.groupEnd()
500
+
501
+ }
502
+
503
+ if (false === returnGetNextPageFunction
504
+ && true === debug
505
+ && isLocal) {
506
+
507
+ toast.success("DEVS: Response returned length (" + responseData.rest?.length + ") less than limit (" + query?.[C6.PAGINATION]?.[C6.LIMIT] + ").", toastOptionsDevs);
508
+
509
+ }
510
+
511
+
512
+ if (fetchDependencies
513
+ && 'number' === typeof fetchDependencies
514
+ && responseData.rest.length > 0) {
515
+
516
+ console.groupCollapsed('%c API: Fetch Dependencies segment (' + requestMethod + ' ' + tableName + ')'
517
+ + (fetchDependencies & eFetchDependencies.CHILDREN ? ' | (CHILDREN|REFERENCED) ' : '')
518
+ + (fetchDependencies & eFetchDependencies.PARENTS ? ' | (PARENTS|REFERENCED_BY)' : '')
519
+ + (fetchDependencies & eFetchDependencies.C6ENTITY ? ' | (C6ENTITY)' : '')
520
+ + (fetchDependencies & eFetchDependencies.RECURSIVE ? ' | (RECURSIVE)' : ''), 'color: #33ccff')
521
+
522
+ console.groupCollapsed('Collapsed JS Trace');
523
+ console.trace(); // hidden in collapsed group
524
+ console.groupEnd();
525
+
526
+ // noinspection JSBitwiseOperatorUsage
527
+ let dependencies: {
528
+ [key: string]: iConstraint[]
529
+ } = {};
530
+
531
+ if (fetchDependencies & eFetchDependencies.C6ENTITY) {
532
+
533
+ dependencies = operatingTable.endsWith("carbon_carbons")
534
+ ? {
535
+ // the context of the entity system is a bit different
536
+ ...fetchDependencies & eFetchDependencies.CHILDREN // REFERENCED === CHILDREN
537
+ ? C6.TABLES[operatingTable].TABLE_REFERENCED_BY
538
+ : {},
539
+ ...fetchDependencies & eFetchDependencies.PARENTS // REFERENCES === PARENTS
540
+ ? C6.TABLES[operatingTable].TABLE_REFERENCES
541
+ : {}
542
+ } : {
543
+ // the context of the entity system is a bit different
544
+ ...fetchDependencies & eFetchDependencies.CHILDREN // REFERENCED === CHILDREN
545
+ ? {
546
+ ...Object.keys(C6.TABLES[operatingTable].TABLE_REFERENCES).reduce((accumulator, columnName) => {
547
+
548
+ if (!C6.TABLES[operatingTable].PRIMARY_SHORT.includes(columnName)) {
549
+ accumulator[columnName] = C6.TABLES[operatingTable].TABLE_REFERENCES[columnName]
550
+ }
551
+
552
+ return accumulator
553
+ }, {}),
554
+ ...C6.TABLES[operatingTable].TABLE_REFERENCED_BY // it is unlikely that a C6 table will have any TABLE_REFERENCED_BY
555
+ }
556
+ : {},
557
+ ...fetchDependencies & eFetchDependencies.PARENTS // REFERENCES === PARENTS
558
+ ? C6.TABLES[operatingTable].PRIMARY_SHORT.reduce((accumulator, primaryKey) => {
559
+ if (primaryKey in C6.TABLES[operatingTable].TABLE_REFERENCES) {
560
+ accumulator[primaryKey] = C6.TABLES[operatingTable].TABLE_REFERENCES[primaryKey]
561
+ }
562
+ return accumulator
563
+ }, {})
564
+ : {}
565
+ }
566
+
567
+ } else {
568
+
569
+ // this is the natural mysql context
570
+ dependencies = {
571
+ ...fetchDependencies & eFetchDependencies.REFERENCED // REFERENCED === CHILDREN
572
+ ? C6.TABLES[operatingTable].TABLE_REFERENCED_BY
573
+ : {},
574
+ ...fetchDependencies & eFetchDependencies.REFERENCES // REFERENCES === PARENTS
575
+ ? C6.TABLES[operatingTable].TABLE_REFERENCES
576
+ : {}
577
+ };
578
+
579
+ }
580
+
581
+ let fetchReferences: {
582
+ [externalTable: string]: {
583
+ [column: string]: string[]
584
+ }
585
+ } = {}
586
+
587
+ let apiRequestPromises: Array<apiReturn<iGetC6RestResponse<any>>> = []
588
+
589
+ console.log('%c Dependencies', 'color: #005555', dependencies)
590
+
591
+ Object.keys(dependencies)
592
+ .forEach(column => dependencies[column]
593
+ .forEach((constraint) => {
594
+
595
+ const columnValues = responseData.rest[column] ?? responseData.rest.map((row) => {
596
+
597
+ if (operatingTable.endsWith("carbons")
598
+ && 'entity_tag' in row
599
+ && !constraint.TABLE.endsWith(row['entity_tag'].split('\\').pop().toLowerCase())) {
600
+
601
+ return false; // map
602
+
603
+ }
604
+
605
+ if (!(column in row)) {
606
+ return false
607
+ }
608
+
609
+ // todo - row[column] is a FK value, we should optionally remove values that are already in state
610
+ // this could be any column in the table constraint.TABLE, not just the primary key
611
+
612
+ return row[column]
613
+
614
+ }).filter(n => n) ?? [];
615
+
616
+ if (columnValues.length === 0) {
617
+
618
+ return; // forEach
619
+
620
+ }
621
+
622
+ fetchReferences[constraint.TABLE] ??= {};
623
+
624
+ fetchReferences[constraint.TABLE][constraint.COLUMN] ??= []
625
+
626
+ fetchReferences[constraint.TABLE][constraint.COLUMN].push(columnValues)
627
+
628
+ }));
629
+
630
+ console.log('fetchReferences', fetchReferences)
631
+
632
+ for (const tableToFetch in fetchReferences) {
633
+
634
+ if (fetchDependencies & eFetchDependencies.C6ENTITY
635
+ && 'string' === typeof tableName
636
+ && tableName.endsWith("carbon_carbons")) {
637
+
638
+ // todo - rethink the table ref entity system - when tables are renamed? no hooks exist in mysql
639
+ // since were already filtering on column, we can assume the first row constraint is the same as the rest
640
+
641
+ const referencesTables: string[] = responseData.rest.reduce((accumulator: string[], row: {
642
+ [x: string]: string;
643
+ }) => {
644
+ if ('entity_tag' in row && !accumulator.includes(row['entity_tag'])) {
645
+ accumulator.push(row['entity_tag']);
646
+ }
647
+ return accumulator;
648
+ }, []).map((entityTag) => entityTag.split('\\').pop().toLowerCase());
649
+
650
+ const shouldContinue = referencesTables.find((referencesTable) => tableToFetch.endsWith(referencesTable))
651
+
652
+ if (!shouldContinue) {
653
+
654
+ console.log('%c C6ENTITY: The constraintTableName (' + tableToFetch + ') did not end with any value in referencesTables', 'color: #c00', referencesTables)
655
+
656
+ continue;
657
+
658
+ }
659
+
660
+ console.log('%c C6ENTITY: The constraintTableName (' + tableToFetch + ') will be fetched.', 'color: #0c0')
661
+
662
+ }
663
+
664
+ const fetchTable = await C6.IMPORT(tableToFetch)
665
+
666
+ const RestApi = fetchTable.default
667
+
668
+ console.log('%c Fetch Dependencies will select (' + tableToFetch + ') using GET request', 'color: #33ccff')
669
+
670
+ let nextFetchDependencies = eFetchDependencies.NONE
671
+
672
+ if (fetchDependencies & eFetchDependencies.RECURSIVE) {
673
+
674
+ if (fetchDependencies & eFetchDependencies.ALL) {
675
+
676
+ 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.')
677
+
678
+ }
679
+
680
+ nextFetchDependencies = fetchDependencies
681
+
682
+ } else if (fetchDependencies & eFetchDependencies.C6ENTITY) {
683
+
684
+ if (tableToFetch === "carbon_carbons") {
685
+
686
+ nextFetchDependencies = fetchDependencies
687
+
688
+ } else {
689
+
690
+ nextFetchDependencies = fetchDependencies ^ eFetchDependencies.C6ENTITY
691
+
692
+ }
693
+
694
+ }
695
+
696
+ console.log('fetchReferences', fetchReferences[tableToFetch], "Current fetchDependencies for (" + operatingTable + "):", fetchDependencies, "New fetchDependencies for (" + tableToFetch + "): ", nextFetchDependencies)
697
+
698
+ // 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
699
+ // it not certain that they are using carbons' entities either
700
+
701
+ // this is a dynamic call to the rest api, any generated table may resolve with (RestApi)
702
+ // todo - using value to avoid joins.... but. maybe this should be a parameterizable option -- think race conditions; its safer to join
703
+ apiRequestPromises.push(RestApi.Get({
704
+ [C6.WHERE]: {
705
+ 0: Object.keys(fetchReferences[tableToFetch]).reduce((sum, column) => {
706
+
707
+ fetchReferences[tableToFetch][column] = fetchReferences[tableToFetch][column].flat(Infinity)
708
+
709
+ if (0 === fetchReferences[tableToFetch][column].length) {
710
+
711
+ console.warn('The column (' + column + ') was not found in the response data. We will not fetch.', responseData)
712
+
713
+ return false;
714
+
715
+ }
716
+
717
+ sum[column] = fetchReferences[tableToFetch][column].length === 1
718
+ ? fetchReferences[tableToFetch][column][0]
719
+ : [
720
+ C6.IN, fetchReferences[tableToFetch][column]
721
+ ]
722
+
723
+ return sum
724
+
725
+ }, {})
726
+ },
727
+ fetchDependencies: nextFetchDependencies
728
+ }
729
+ ));
730
+
731
+ }
732
+
733
+ console.groupEnd()
734
+
735
+ await Promise.all(apiRequestPromises)
736
+
737
+ apiRequestPromises.map(async (promise) => {
738
+ if (!Array.isArray(this.request.fetchDependencies)) {
739
+ // to reassign value we must ref the root
740
+ this.request.fetchDependencies = [];
741
+ }
742
+ this.request.fetchDependencies.push(await promise)
743
+ })
744
+
745
+ }
746
+
747
+
748
+ }
749
+
750
+ if (debug && isLocal) {
751
+
752
+ toast.success("DEVS: (" + requestMethod + ") request complete.", toastOptionsDevs);
753
+
754
+ }
755
+
756
+ return response;
757
+
758
+ }
759
+ );
760
+
761
+ } catch (throwableError) {
762
+
763
+ if (isTest) {
764
+
765
+ throw new Error(JSON.stringify(throwableError))
766
+
767
+ }
768
+
769
+ console.groupCollapsed('%c API: An error occurred in the try catch block. returning null!', 'color: #ff0000')
770
+
771
+ console.log('%c ' + requestMethod + ' ' + tableName, 'color: #A020F0')
772
+
773
+ console.warn(throwableError)
774
+
775
+ console.trace()
776
+
777
+ console.groupEnd()
778
+
779
+ TestRestfulResponse(throwableError, success, error)
780
+
781
+ return null;
782
+
783
+ }
784
+
785
+ }
786
+
787
+ return await apiRequest()
788
+
789
+ }
790
+ }