@carbonorm/carbonnode 3.0.2 → 3.0.3
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/executors/Executor.d.ts +8 -7
- package/dist/api/executors/HttpExecutor.d.ts +10 -5
- package/dist/api/executors/SqlExecutor.d.ts +5 -5
- package/dist/api/restOrm.d.ts +43 -0
- package/dist/api/restRequest.d.ts +4 -4
- package/dist/api/types/ormInterfaces.d.ts +53 -27
- package/dist/index.cjs.js +125 -473
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +125 -474
- package/dist/index.esm.js.map +1 -1
- package/package.json +5 -4
- package/scripts/assets/handlebars/C6.test.ts.handlebars +88 -0
- package/scripts/assets/handlebars/C6.ts.handlebars +30 -4
- package/scripts/generateRestBindings.cjs +2 -7
- package/scripts/generateRestBindings.ts +2 -14
- package/src/api/executors/Executor.ts +63 -13
- package/src/api/executors/HttpExecutor.ts +150 -27
- package/src/api/executors/SqlExecutor.ts +6 -6
- package/src/api/rest/C6.test.ts +88 -0
- package/src/api/rest/C6.ts +5338 -0
- package/src/api/restOrm.ts +61 -0
- package/src/api/restRequest.ts +14 -12
- package/src/api/types/ormInterfaces.ts +102 -19
- package/src/api/utils/apiHelpers.ts +4 -0
- package/src/index.ts +1 -0
- package/scripts/assets/handlebars/Table.test.ts.handlebars +0 -126
- package/scripts/assets/handlebars/Table.ts.handlebars +0 -193
|
@@ -6,31 +6,117 @@ import isVerbose from "../../variables/isVerbose";
|
|
|
6
6
|
import convertForRequestBody from "../convertForRequestBody";
|
|
7
7
|
import {eFetchDependencies} from "../types/dynamicFetching";
|
|
8
8
|
import {Modify} from "../types/modifyTypes";
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
apiReturn,
|
|
11
|
+
DELETE, DetermineResponseDataType,
|
|
12
|
+
GET, iAPI,
|
|
13
|
+
iCacheAPI,
|
|
14
|
+
iConstraint,
|
|
15
|
+
iGetC6RestResponse,
|
|
16
|
+
iRestMethods,
|
|
17
|
+
POST,
|
|
18
|
+
PUT,
|
|
19
|
+
RequestQueryBody
|
|
20
|
+
} from "../types/ormInterfaces";
|
|
21
|
+
import {removeInvalidKeys, removePrefixIfExists, TestRestfulResponse} from "../utils/apiHelpers";
|
|
11
22
|
import {apiRequestCache, checkCache, userCustomClearCache} from "../utils/cacheManager";
|
|
12
23
|
import {sortAndSerializeQueryObject} from "../utils/sortAndSerializeQueryObject";
|
|
13
24
|
import {Executor} from "./Executor";
|
|
14
|
-
import {toastOptions, toastOptionsDevs
|
|
25
|
+
import {toastOptions, toastOptionsDevs} from "variables/toastOptions";
|
|
15
26
|
|
|
16
27
|
export class HttpExecutor<
|
|
28
|
+
RequestMethod extends iRestMethods,
|
|
17
29
|
RestShortTableName extends string = any,
|
|
18
30
|
RestTableInterface extends { [key: string]: any } = any,
|
|
19
31
|
PrimaryKey extends Extract<keyof RestTableInterface, string> = Extract<keyof RestTableInterface, string>,
|
|
20
32
|
CustomAndRequiredFields extends { [key: string]: any } = any,
|
|
21
|
-
RequestTableOverrides extends { [key
|
|
22
|
-
ResponseDataType = any
|
|
33
|
+
RequestTableOverrides extends { [key in keyof RestTableInterface]: any } = { [key in keyof RestTableInterface]: any }
|
|
23
34
|
>
|
|
24
35
|
extends Executor<
|
|
36
|
+
RequestMethod,
|
|
25
37
|
RestShortTableName,
|
|
26
38
|
RestTableInterface,
|
|
27
39
|
PrimaryKey,
|
|
28
40
|
CustomAndRequiredFields,
|
|
29
|
-
RequestTableOverrides
|
|
30
|
-
ResponseDataType
|
|
41
|
+
RequestTableOverrides
|
|
31
42
|
> {
|
|
32
43
|
|
|
33
|
-
public
|
|
44
|
+
public putState(
|
|
45
|
+
response: AxiosResponse<DetermineResponseDataType<RequestMethod, RestTableInterface>>,
|
|
46
|
+
request: iAPI<Modify<RestTableInterface, RequestTableOverrides>> & CustomAndRequiredFields,
|
|
47
|
+
callback: () => void
|
|
48
|
+
) {
|
|
49
|
+
this.config.reactBootstrap?.updateRestfulObjectArrays<RestTableInterface>({
|
|
50
|
+
callback,
|
|
51
|
+
dataOrCallback: [
|
|
52
|
+
removeInvalidKeys<RestTableInterface>({
|
|
53
|
+
...request,
|
|
54
|
+
...response?.data?.rest,
|
|
55
|
+
}, this.config.C6.TABLES)
|
|
56
|
+
],
|
|
57
|
+
stateKey: this.config.restModel.TABLE_NAME,
|
|
58
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public postState(
|
|
63
|
+
response: AxiosResponse<DetermineResponseDataType<RequestMethod, RestTableInterface>>,
|
|
64
|
+
request: iAPI<Modify<RestTableInterface, RequestTableOverrides>> & CustomAndRequiredFields,
|
|
65
|
+
callback: () => void
|
|
66
|
+
) {
|
|
67
|
+
|
|
68
|
+
if (1 !== this.config.restModel.PRIMARY_SHORT.length) {
|
|
69
|
+
|
|
70
|
+
console.error("C6 received unexpected result's given the primary key length");
|
|
71
|
+
|
|
72
|
+
} else {
|
|
73
|
+
|
|
74
|
+
const pk = this.config.restModel.PRIMARY_SHORT[0];
|
|
75
|
+
|
|
76
|
+
// TODO - should overrides be handled differently? Why override: (react/php), driver missmatches, aux data..
|
|
77
|
+
// @ts-ignore - this is technically a correct error, but we allow it anyway...
|
|
78
|
+
request[pk] = response.data?.created as RestTableInterface[PrimaryKey]
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.config.reactBootstrap?.updateRestfulObjectArrays<RestTableInterface>({
|
|
83
|
+
callback,
|
|
84
|
+
dataOrCallback: undefined !== request.dataInsertMultipleRows
|
|
85
|
+
? request.dataInsertMultipleRows.map((request, index) => {
|
|
86
|
+
return removeInvalidKeys<RestTableInterface>({
|
|
87
|
+
...request,
|
|
88
|
+
...(index === 0 ? response?.data?.rest : {}),
|
|
89
|
+
}, this.config.C6.TABLES)
|
|
90
|
+
})
|
|
91
|
+
: [
|
|
92
|
+
removeInvalidKeys<RestTableInterface>({
|
|
93
|
+
...request,
|
|
94
|
+
...response?.data?.rest,
|
|
95
|
+
}, this.config.C6.TABLES)
|
|
96
|
+
],
|
|
97
|
+
stateKey: this.config.restModel.TABLE_NAME,
|
|
98
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof RestTableInterface)[]
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public deleteState(
|
|
103
|
+
_response: AxiosResponse<DetermineResponseDataType<RequestMethod, RestTableInterface>>,
|
|
104
|
+
request: iAPI<Modify<RestTableInterface, RequestTableOverrides>> & CustomAndRequiredFields,
|
|
105
|
+
callback: () => void
|
|
106
|
+
) {
|
|
107
|
+
this.config.reactBootstrap?.deleteRestfulObjectArrays<RestTableInterface>({
|
|
108
|
+
callback,
|
|
109
|
+
dataOrCallback: [
|
|
110
|
+
request as unknown as RestTableInterface,
|
|
111
|
+
],
|
|
112
|
+
stateKey: this.config.restModel.TABLE_NAME,
|
|
113
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof RestTableInterface)[]
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public async execute(): Promise<apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>> {
|
|
118
|
+
|
|
119
|
+
type ResponseDataType = DetermineResponseDataType<RequestMethod, RestTableInterface>;
|
|
34
120
|
|
|
35
121
|
const {
|
|
36
122
|
C6,
|
|
@@ -38,13 +124,20 @@ export class HttpExecutor<
|
|
|
38
124
|
restURL,
|
|
39
125
|
withCredentials,
|
|
40
126
|
restModel,
|
|
127
|
+
reactBootstrap,
|
|
41
128
|
requestMethod,
|
|
42
|
-
queryCallback,
|
|
43
|
-
responseCallback,
|
|
44
129
|
skipPrimaryCheck,
|
|
45
130
|
clearCache,
|
|
46
131
|
} = this.config
|
|
47
132
|
|
|
133
|
+
|
|
134
|
+
await this.runLifecycleHooks<"beforeProcessing">(
|
|
135
|
+
"beforeProcessing", {
|
|
136
|
+
config: this.config,
|
|
137
|
+
request: this.request,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
|
|
48
141
|
const tableName = restModel.TABLE_NAME;
|
|
49
142
|
|
|
50
143
|
const fullTableList = Array.isArray(tableName) ? tableName : [tableName];
|
|
@@ -81,16 +174,6 @@ export class HttpExecutor<
|
|
|
81
174
|
// thus the request shouldn't fire as is in custom cache
|
|
82
175
|
let query: RequestQueryBody<Modify<RestTableInterface, RequestTableOverrides>> | undefined | null;
|
|
83
176
|
|
|
84
|
-
if ('function' === typeof queryCallback) {
|
|
85
|
-
|
|
86
|
-
query = queryCallback(this.request); // obj or obj[]
|
|
87
|
-
|
|
88
|
-
} else {
|
|
89
|
-
|
|
90
|
-
query = queryCallback;
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
177
|
if (undefined === query || null === query) {
|
|
95
178
|
|
|
96
179
|
if (this.request.debug && isLocal) {
|
|
@@ -126,7 +209,7 @@ export class HttpExecutor<
|
|
|
126
209
|
}
|
|
127
210
|
|
|
128
211
|
// this could return itself with a new page number, or undefined if the end is reached
|
|
129
|
-
const apiRequest = async (): Promise<apiReturn<
|
|
212
|
+
const apiRequest = async (): Promise<apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>> => {
|
|
130
213
|
|
|
131
214
|
const {
|
|
132
215
|
debug,
|
|
@@ -134,7 +217,7 @@ export class HttpExecutor<
|
|
|
134
217
|
dataInsertMultipleRows,
|
|
135
218
|
success,
|
|
136
219
|
fetchDependencies = eFetchDependencies.NONE,
|
|
137
|
-
error =
|
|
220
|
+
error = "An unexpected API error occurred!"
|
|
138
221
|
} = this.request
|
|
139
222
|
|
|
140
223
|
if (C6.GET === requestMethod
|
|
@@ -357,6 +440,12 @@ export class HttpExecutor<
|
|
|
357
440
|
|
|
358
441
|
console.groupEnd()
|
|
359
442
|
|
|
443
|
+
this.runLifecycleHooks<"beforeExecution">(
|
|
444
|
+
"beforeExecution", {
|
|
445
|
+
config: this.config,
|
|
446
|
+
request: this.request
|
|
447
|
+
})
|
|
448
|
+
|
|
360
449
|
const axiosActiveRequest: AxiosPromise<ResponseDataType> = axios![requestMethod.toLowerCase()]<ResponseDataType>(
|
|
361
450
|
restRequestUri,
|
|
362
451
|
...((() => {
|
|
@@ -431,8 +520,9 @@ export class HttpExecutor<
|
|
|
431
520
|
|
|
432
521
|
// returning the promise with this then is important for tests. todo - we could make that optional.
|
|
433
522
|
// https://rapidapi.com/guides/axios-async-await
|
|
434
|
-
return axiosActiveRequest.then(async (response): Promise<AxiosResponse<ResponseDataType, any>> => {
|
|
523
|
+
return axiosActiveRequest.then(async (response: AxiosResponse<ResponseDataType, any>): Promise<AxiosResponse<ResponseDataType, any>> => {
|
|
435
524
|
|
|
525
|
+
// noinspection SuspiciousTypeOfGuard
|
|
436
526
|
if (typeof response.data === 'string') {
|
|
437
527
|
|
|
438
528
|
if (isTest) {
|
|
@@ -458,6 +548,14 @@ export class HttpExecutor<
|
|
|
458
548
|
|
|
459
549
|
}
|
|
460
550
|
|
|
551
|
+
this.runLifecycleHooks<"afterExecution">(
|
|
552
|
+
"afterExecution", {
|
|
553
|
+
config: this.config,
|
|
554
|
+
request: this.request,
|
|
555
|
+
response
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
// todo - this feels dumb now, but i digress
|
|
461
559
|
apiResponse = TestRestfulResponse(response, success, error)
|
|
462
560
|
|
|
463
561
|
if (false === apiResponse) {
|
|
@@ -472,11 +570,36 @@ export class HttpExecutor<
|
|
|
472
570
|
|
|
473
571
|
}
|
|
474
572
|
|
|
475
|
-
// stateful operations are done in the response callback - its leverages rest generated functions
|
|
476
|
-
if (responseCallback) {
|
|
477
|
-
|
|
478
|
-
responseCallback(response, this.request, apiResponse)
|
|
479
573
|
|
|
574
|
+
const callback = () => this.runLifecycleHooks<"afterCommit">(
|
|
575
|
+
"afterCommit", {
|
|
576
|
+
config: this.config,
|
|
577
|
+
request: this.request,
|
|
578
|
+
response
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
if (undefined !== reactBootstrap && response) {
|
|
582
|
+
switch (requestMethod) {
|
|
583
|
+
case GET:
|
|
584
|
+
reactBootstrap.updateRestfulObjectArrays<RestTableInterface>({
|
|
585
|
+
dataOrCallback: Array.isArray(response.data.rest) ? response.data.rest : [response.data.rest],
|
|
586
|
+
stateKey: this.config.restModel.TABLE_NAME,
|
|
587
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof RestTableInterface)[],
|
|
588
|
+
callback
|
|
589
|
+
})
|
|
590
|
+
break;
|
|
591
|
+
case POST:
|
|
592
|
+
this.postState(response, this.request, callback);
|
|
593
|
+
break;
|
|
594
|
+
case PUT:
|
|
595
|
+
this.putState(response, this.request, callback);
|
|
596
|
+
break;
|
|
597
|
+
case DELETE:
|
|
598
|
+
this.deleteState(response, this.request, callback);
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
callback();
|
|
480
603
|
}
|
|
481
604
|
|
|
482
605
|
if (C6.GET === requestMethod) {
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {iRestMethods} from "@carbonorm/carbonnode";
|
|
2
2
|
import { PoolConnection, RowDataPacket } from 'mysql2/promise';
|
|
3
3
|
import {buildSelectQuery} from "../builders/sqlBuilder";
|
|
4
4
|
import {Executor} from "./Executor";
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export class SqlExecutor<
|
|
8
|
+
RequestMethod extends iRestMethods,
|
|
8
9
|
RestShortTableName extends string = any,
|
|
9
10
|
RestTableInterface extends { [key: string]: any } = any,
|
|
10
11
|
PrimaryKey extends Extract<keyof RestTableInterface, string> = Extract<keyof RestTableInterface, string>,
|
|
11
12
|
CustomAndRequiredFields extends { [key: string]: any } = any,
|
|
12
|
-
RequestTableOverrides extends { [key
|
|
13
|
-
ResponseDataType = any
|
|
13
|
+
RequestTableOverrides extends { [key in keyof RestTableInterface]: any } = { [key in keyof RestTableInterface]: any }
|
|
14
14
|
>
|
|
15
15
|
extends Executor<
|
|
16
|
+
RequestMethod,
|
|
16
17
|
RestShortTableName,
|
|
17
18
|
RestTableInterface,
|
|
18
19
|
PrimaryKey,
|
|
19
20
|
CustomAndRequiredFields,
|
|
20
|
-
RequestTableOverrides
|
|
21
|
-
ResponseDataType
|
|
21
|
+
RequestTableOverrides
|
|
22
22
|
> {
|
|
23
23
|
|
|
24
|
-
public execute()
|
|
24
|
+
public execute() {
|
|
25
25
|
switch (this.config.requestMethod) {
|
|
26
26
|
case 'GET':
|
|
27
27
|
return (this.select(
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { checkAllRequestsComplete } from '@carbonorm/carbonnode';
|
|
3
|
+
import { act, waitFor } from '@testing-library/react';
|
|
4
|
+
import { C6 } from "./C6";
|
|
5
|
+
|
|
6
|
+
const fillString = () => Math.random().toString(36).substring(2, 12);
|
|
7
|
+
const fillNumber = () => Math.floor(Math.random() * 1000000);
|
|
8
|
+
|
|
9
|
+
const RESERVED_COLUMNS = ['created_at', 'updated_at', 'deleted_at'];
|
|
10
|
+
|
|
11
|
+
function buildTestData(tableModel: any): Record<string, any> {
|
|
12
|
+
const data: Record<string, any> = {};
|
|
13
|
+
const validation = tableModel.TYPE_VALIDATION;
|
|
14
|
+
|
|
15
|
+
for (const col of Object.keys(validation)) {
|
|
16
|
+
const { MYSQL_TYPE, SKIP_COLUMN_IN_POST, MAX_LENGTH } = validation[col];
|
|
17
|
+
|
|
18
|
+
if (SKIP_COLUMN_IN_POST || RESERVED_COLUMNS.includes(col)) continue;
|
|
19
|
+
|
|
20
|
+
if (MYSQL_TYPE.startsWith('varchar') || MYSQL_TYPE === 'text') {
|
|
21
|
+
let str = fillString();
|
|
22
|
+
if (MAX_LENGTH) str = str.substring(0, MAX_LENGTH);
|
|
23
|
+
data[col] = str;
|
|
24
|
+
} else if (MYSQL_TYPE.includes('int') || MYSQL_TYPE === 'decimal') {
|
|
25
|
+
data[col] = fillNumber();
|
|
26
|
+
} else if (MYSQL_TYPE === 'json') {
|
|
27
|
+
data[col] = {};
|
|
28
|
+
} else if (MYSQL_TYPE === 'tinyint(1)') {
|
|
29
|
+
data[col] = 1;
|
|
30
|
+
} else {
|
|
31
|
+
data[col] = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('CarbonORM table API integration tests', () => {
|
|
39
|
+
for (const [shortName, tableModel] of Object.entries(C6.TABLES)) {
|
|
40
|
+
const primaryKeys: string[] = tableModel.PRIMARY_SHORT;
|
|
41
|
+
|
|
42
|
+
// Get restOrm binding
|
|
43
|
+
const restBinding = (C6 as any)[shortName[0].toUpperCase() + shortName.slice(1)];
|
|
44
|
+
if (!restBinding) continue;
|
|
45
|
+
|
|
46
|
+
test(`[${shortName}] GET → POST → GET → PUT → DELETE`, async () => {
|
|
47
|
+
|
|
48
|
+
const testData = buildTestData(tableModel);
|
|
49
|
+
|
|
50
|
+
await act(async () => {
|
|
51
|
+
|
|
52
|
+
// GET all
|
|
53
|
+
const all = await restBinding.Get({});
|
|
54
|
+
expect(all?.data?.rest).toBeDefined();
|
|
55
|
+
|
|
56
|
+
// POST one
|
|
57
|
+
const post = await restBinding.Post(testData);
|
|
58
|
+
expect(post?.data?.created).toBeDefined();
|
|
59
|
+
|
|
60
|
+
const postID = post?.data?.created;
|
|
61
|
+
const pkName = primaryKeys[0];
|
|
62
|
+
testData[pkName] = postID;
|
|
63
|
+
|
|
64
|
+
// GET single
|
|
65
|
+
const select = await restBinding.Get({
|
|
66
|
+
[C6.WHERE]: {
|
|
67
|
+
[tableModel[pkName.toUpperCase()]]: postID
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(select?.data?.rest?.[0]?.[pkName]).toEqual(postID);
|
|
72
|
+
|
|
73
|
+
// PUT update
|
|
74
|
+
const updated = await restBinding.Put(testData);
|
|
75
|
+
expect(updated?.data?.updated).toBeDefined();
|
|
76
|
+
|
|
77
|
+
// DELETE
|
|
78
|
+
const deleted = await restBinding.Delete({ [pkName]: postID });
|
|
79
|
+
expect(deleted?.data?.deleted).toBeDefined();
|
|
80
|
+
|
|
81
|
+
// Wait for all requests to settle
|
|
82
|
+
await waitFor(() => {
|
|
83
|
+
expect(checkAllRequestsComplete()).toEqual(true);
|
|
84
|
+
}, { timeout: 10000, interval: 1000 });
|
|
85
|
+
});
|
|
86
|
+
}, 100000);
|
|
87
|
+
}
|
|
88
|
+
});
|