@carbonorm/carbonnode 3.0.15 → 3.1.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/C6Constants.d.ts +183 -0
- package/dist/api/builders/queryHelpers.d.ts +4 -0
- package/dist/api/executors/Executor.d.ts +9 -15
- package/dist/api/executors/HttpExecutor.d.ts +7 -14
- package/dist/api/executors/SqlExecutor.d.ts +9 -12
- package/dist/api/orm/SqlBuilder.d.ts +17 -0
- package/dist/api/orm/builders/AggregateBuilder.d.ts +5 -0
- package/dist/api/orm/builders/ConditionBuilder.d.ts +11 -0
- package/dist/api/orm/builders/JoinBuilder.d.ts +5 -0
- package/dist/api/orm/builders/PaginationBuilder.d.ts +18 -0
- package/dist/api/orm/queries/DeleteQueryBuilder.d.ts +6 -0
- package/dist/api/orm/queries/SelectQueryBuilder.d.ts +6 -0
- package/dist/api/orm/queries/UpdateQueryBuilder.d.ts +6 -0
- package/dist/api/orm/queryHelpers.d.ts +4 -0
- package/dist/api/orm/utils/sqlUtils.d.ts +7 -0
- package/dist/api/restOrm.d.ts +3 -10
- package/dist/api/restRequest.d.ts +3 -10
- package/dist/api/types/ormGenerics.d.ts +13 -0
- package/dist/api/types/ormInterfaces.d.ts +23 -40
- package/dist/index.cjs.js +389 -407
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.esm.js +377 -407
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/scripts/assets/handlebars/C6.ts.handlebars +2 -4
- package/src/api/C6Constants.ts +37 -2
- package/src/api/executors/Executor.ts +18 -36
- package/src/api/executors/HttpExecutor.ts +46 -59
- package/src/api/executors/SqlExecutor.ts +17 -29
- package/src/api/handlers/ExpressHandler.ts +1 -1
- package/src/api/orm/builders/AggregateBuilder.ts +38 -0
- package/src/api/orm/builders/ConditionBuilder.ts +113 -0
- package/src/api/orm/builders/JoinBuilder.ts +25 -0
- package/src/api/orm/builders/PaginationBuilder.ts +56 -0
- package/src/api/orm/queries/DeleteQueryBuilder.ts +28 -0
- package/src/api/orm/queries/SelectQueryBuilder.ts +49 -0
- package/src/api/orm/queries/UpdateQueryBuilder.ts +42 -0
- package/src/api/orm/queryHelpers.ts +18 -0
- package/src/api/orm/utils/sqlUtils.ts +24 -0
- package/src/api/restOrm.ts +4 -14
- package/src/api/restRequest.ts +16 -34
- package/src/api/types/ormGenerics.ts +18 -0
- package/src/api/types/ormInterfaces.ts +31 -46
- package/src/index.ts +10 -1
- package/src/api/builders/sqlBuilder.ts +0 -454
|
@@ -5,6 +5,7 @@ import isTest from "../../variables/isTest";
|
|
|
5
5
|
import isVerbose from "../../variables/isVerbose";
|
|
6
6
|
import convertForRequestBody from "../convertForRequestBody";
|
|
7
7
|
import {eFetchDependencies} from "../types/dynamicFetching";
|
|
8
|
+
import {OrmGenerics} from "../types/ormGenerics";
|
|
8
9
|
import {
|
|
9
10
|
apiReturn,
|
|
10
11
|
DELETE, DetermineResponseDataType,
|
|
@@ -12,7 +13,6 @@ import {
|
|
|
12
13
|
iCacheAPI,
|
|
13
14
|
iConstraint,
|
|
14
15
|
iGetC6RestResponse,
|
|
15
|
-
iRestMethods,
|
|
16
16
|
POST,
|
|
17
17
|
PUT, RequestQueryBody
|
|
18
18
|
} from "../types/ormInterfaces";
|
|
@@ -23,36 +23,24 @@ import {Executor} from "./Executor";
|
|
|
23
23
|
import {toastOptions, toastOptionsDevs} from "variables/toastOptions";
|
|
24
24
|
|
|
25
25
|
export class HttpExecutor<
|
|
26
|
-
|
|
27
|
-
RestShortTableName extends string = any,
|
|
28
|
-
RestTableInterface extends { [key: string]: any } = any,
|
|
29
|
-
PrimaryKey extends Extract<keyof RestTableInterface, string> = Extract<keyof RestTableInterface, string>,
|
|
30
|
-
CustomAndRequiredFields extends { [key: string]: any } = any,
|
|
31
|
-
RequestTableOverrides extends { [key in keyof RestTableInterface]: any } = { [key in keyof RestTableInterface]: any }
|
|
26
|
+
G extends OrmGenerics
|
|
32
27
|
>
|
|
33
|
-
extends Executor<
|
|
34
|
-
RequestMethod,
|
|
35
|
-
RestShortTableName,
|
|
36
|
-
RestTableInterface,
|
|
37
|
-
PrimaryKey,
|
|
38
|
-
CustomAndRequiredFields,
|
|
39
|
-
RequestTableOverrides
|
|
40
|
-
> {
|
|
28
|
+
extends Executor<G> {
|
|
41
29
|
|
|
42
30
|
public putState(
|
|
43
|
-
response: AxiosResponse<DetermineResponseDataType<RequestMethod, RestTableInterface>>,
|
|
31
|
+
response: AxiosResponse<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>,
|
|
44
32
|
request: RequestQueryBody<
|
|
45
|
-
RequestMethod,
|
|
46
|
-
RestTableInterface,
|
|
47
|
-
CustomAndRequiredFields,
|
|
48
|
-
RequestTableOverrides
|
|
33
|
+
G['RequestMethod'],
|
|
34
|
+
G['RestTableInterface'],
|
|
35
|
+
G['CustomAndRequiredFields'],
|
|
36
|
+
G['RequestTableOverrides']
|
|
49
37
|
>,
|
|
50
38
|
callback: () => void
|
|
51
39
|
) {
|
|
52
|
-
this.config.reactBootstrap?.updateRestfulObjectArrays<RestTableInterface>({
|
|
40
|
+
this.config.reactBootstrap?.updateRestfulObjectArrays<G['RestTableInterface']>({
|
|
53
41
|
callback,
|
|
54
42
|
dataOrCallback: [
|
|
55
|
-
removeInvalidKeys<RestTableInterface>({
|
|
43
|
+
removeInvalidKeys<G['RestTableInterface']>({
|
|
56
44
|
...request,
|
|
57
45
|
...response?.data?.rest,
|
|
58
46
|
}, this.config.C6.TABLES)
|
|
@@ -63,12 +51,12 @@ export class HttpExecutor<
|
|
|
63
51
|
}
|
|
64
52
|
|
|
65
53
|
public postState(
|
|
66
|
-
response: AxiosResponse<DetermineResponseDataType<RequestMethod, RestTableInterface>>,
|
|
54
|
+
response: AxiosResponse<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>,
|
|
67
55
|
request: RequestQueryBody<
|
|
68
|
-
RequestMethod,
|
|
69
|
-
RestTableInterface,
|
|
70
|
-
CustomAndRequiredFields,
|
|
71
|
-
RequestTableOverrides
|
|
56
|
+
G['RequestMethod'],
|
|
57
|
+
G['RestTableInterface'],
|
|
58
|
+
G['CustomAndRequiredFields'],
|
|
59
|
+
G['RequestTableOverrides']
|
|
72
60
|
>,
|
|
73
61
|
callback: () => void
|
|
74
62
|
) {
|
|
@@ -87,49 +75,49 @@ export class HttpExecutor<
|
|
|
87
75
|
|
|
88
76
|
}
|
|
89
77
|
|
|
90
|
-
this.config.reactBootstrap?.updateRestfulObjectArrays<RestTableInterface>({
|
|
78
|
+
this.config.reactBootstrap?.updateRestfulObjectArrays<G['RestTableInterface']>({
|
|
91
79
|
callback,
|
|
92
80
|
dataOrCallback: undefined !== request.dataInsertMultipleRows
|
|
93
81
|
? request.dataInsertMultipleRows.map((request, index) => {
|
|
94
|
-
return removeInvalidKeys<RestTableInterface>({
|
|
82
|
+
return removeInvalidKeys<G['RestTableInterface']>({
|
|
95
83
|
...request,
|
|
96
84
|
...(index === 0 ? response?.data?.rest : {}),
|
|
97
85
|
}, this.config.C6.TABLES)
|
|
98
86
|
})
|
|
99
87
|
: [
|
|
100
|
-
removeInvalidKeys<RestTableInterface>({
|
|
88
|
+
removeInvalidKeys<G['RestTableInterface']>({
|
|
101
89
|
...request,
|
|
102
90
|
...response?.data?.rest,
|
|
103
91
|
}, this.config.C6.TABLES)
|
|
104
92
|
],
|
|
105
93
|
stateKey: this.config.restModel.TABLE_NAME,
|
|
106
|
-
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof RestTableInterface)[]
|
|
94
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof G['RestTableInterface'])[]
|
|
107
95
|
})
|
|
108
96
|
}
|
|
109
97
|
|
|
110
98
|
public deleteState(
|
|
111
|
-
_response: AxiosResponse<DetermineResponseDataType<RequestMethod, RestTableInterface>>,
|
|
99
|
+
_response: AxiosResponse<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>,
|
|
112
100
|
request: RequestQueryBody<
|
|
113
|
-
RequestMethod,
|
|
114
|
-
RestTableInterface,
|
|
115
|
-
CustomAndRequiredFields,
|
|
116
|
-
RequestTableOverrides
|
|
101
|
+
G['RequestMethod'],
|
|
102
|
+
G['RestTableInterface'],
|
|
103
|
+
G['CustomAndRequiredFields'],
|
|
104
|
+
G['RequestTableOverrides']
|
|
117
105
|
>,
|
|
118
106
|
callback: () => void
|
|
119
107
|
) {
|
|
120
|
-
this.config.reactBootstrap?.deleteRestfulObjectArrays<RestTableInterface>({
|
|
108
|
+
this.config.reactBootstrap?.deleteRestfulObjectArrays<G['RestTableInterface']>({
|
|
121
109
|
callback,
|
|
122
110
|
dataOrCallback: [
|
|
123
|
-
request as unknown as RestTableInterface,
|
|
111
|
+
request as unknown as G['RestTableInterface'],
|
|
124
112
|
],
|
|
125
113
|
stateKey: this.config.restModel.TABLE_NAME,
|
|
126
|
-
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof RestTableInterface)[]
|
|
114
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof G['RestTableInterface'])[]
|
|
127
115
|
})
|
|
128
116
|
}
|
|
129
117
|
|
|
130
|
-
public async execute(): Promise<apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>> {
|
|
118
|
+
public async execute(): Promise<apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>> {
|
|
131
119
|
|
|
132
|
-
type ResponseDataType = DetermineResponseDataType<RequestMethod, RestTableInterface>;
|
|
120
|
+
type ResponseDataType = DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>;
|
|
133
121
|
|
|
134
122
|
const {
|
|
135
123
|
C6,
|
|
@@ -151,7 +139,7 @@ export class HttpExecutor<
|
|
|
151
139
|
});
|
|
152
140
|
|
|
153
141
|
|
|
154
|
-
const tableName = restModel.TABLE_NAME;
|
|
142
|
+
const tableName = restModel.TABLE_NAME as string;
|
|
155
143
|
|
|
156
144
|
const fullTableList = Array.isArray(tableName) ? tableName : [tableName];
|
|
157
145
|
|
|
@@ -218,7 +206,7 @@ export class HttpExecutor<
|
|
|
218
206
|
}
|
|
219
207
|
|
|
220
208
|
// this could return itself with a new page number, or undefined if the end is reached
|
|
221
|
-
const apiRequest = async (): Promise<apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>> => {
|
|
209
|
+
const apiRequest = async (): Promise<apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>> => {
|
|
222
210
|
|
|
223
211
|
const {
|
|
224
212
|
debug,
|
|
@@ -261,10 +249,10 @@ export class HttpExecutor<
|
|
|
261
249
|
if (undefined === query || null === query) {
|
|
262
250
|
|
|
263
251
|
query = {} as RequestQueryBody<
|
|
264
|
-
RequestMethod,
|
|
265
|
-
RestTableInterface,
|
|
266
|
-
CustomAndRequiredFields,
|
|
267
|
-
RequestTableOverrides
|
|
252
|
+
G['RequestMethod'],
|
|
253
|
+
G['RestTableInterface'],
|
|
254
|
+
G['CustomAndRequiredFields'],
|
|
255
|
+
G['RequestTableOverrides']
|
|
268
256
|
>
|
|
269
257
|
|
|
270
258
|
}
|
|
@@ -351,7 +339,7 @@ export class HttpExecutor<
|
|
|
351
339
|
|
|
352
340
|
let addBackPK: (() => void) | undefined;
|
|
353
341
|
|
|
354
|
-
let apiResponse: RestTableInterface[PrimaryKey] | string | boolean | number | undefined;
|
|
342
|
+
let apiResponse: G['RestTableInterface'][G['PrimaryKey']] | string | boolean | number | undefined;
|
|
355
343
|
|
|
356
344
|
let returnGetNextPageFunction = false;
|
|
357
345
|
|
|
@@ -427,10 +415,10 @@ export class HttpExecutor<
|
|
|
427
415
|
|
|
428
416
|
addBackPK = () => {
|
|
429
417
|
query ??= {} as RequestQueryBody<
|
|
430
|
-
RequestMethod,
|
|
431
|
-
RestTableInterface,
|
|
432
|
-
CustomAndRequiredFields,
|
|
433
|
-
RequestTableOverrides
|
|
418
|
+
G['RequestMethod'],
|
|
419
|
+
G['RestTableInterface'],
|
|
420
|
+
G['CustomAndRequiredFields'],
|
|
421
|
+
G['RequestTableOverrides']
|
|
434
422
|
>;
|
|
435
423
|
query[primaryKey] = removedPkValue;
|
|
436
424
|
}
|
|
@@ -470,10 +458,10 @@ export class HttpExecutor<
|
|
|
470
458
|
...(() => {
|
|
471
459
|
const convert = (data: any) =>
|
|
472
460
|
convertForRequestBody<
|
|
473
|
-
RequestMethod,
|
|
474
|
-
RestTableInterface,
|
|
475
|
-
CustomAndRequiredFields,
|
|
476
|
-
RequestTableOverrides
|
|
461
|
+
G['RequestMethod'],
|
|
462
|
+
G['RestTableInterface'],
|
|
463
|
+
G['CustomAndRequiredFields'],
|
|
464
|
+
G['RequestTableOverrides']
|
|
477
465
|
>(
|
|
478
466
|
data,
|
|
479
467
|
fullTableList,
|
|
@@ -574,7 +562,6 @@ export class HttpExecutor<
|
|
|
574
562
|
if (false === apiResponse) {
|
|
575
563
|
|
|
576
564
|
|
|
577
|
-
|
|
578
565
|
if (debug && isLocal()) {
|
|
579
566
|
|
|
580
567
|
toast.warning("DEVS: TestRestfulResponse returned false for (" + operatingTable + ").", toastOptionsDevs);
|
|
@@ -596,10 +583,10 @@ export class HttpExecutor<
|
|
|
596
583
|
if (undefined !== reactBootstrap && response) {
|
|
597
584
|
switch (requestMethod) {
|
|
598
585
|
case GET:
|
|
599
|
-
reactBootstrap.updateRestfulObjectArrays<RestTableInterface>({
|
|
586
|
+
reactBootstrap.updateRestfulObjectArrays<G['RestTableInterface']>({
|
|
600
587
|
dataOrCallback: Array.isArray(response.data.rest) ? response.data.rest : [response.data.rest],
|
|
601
588
|
stateKey: this.config.restModel.TABLE_NAME,
|
|
602
|
-
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof RestTableInterface)[],
|
|
589
|
+
uniqueObjectId: this.config.restModel.PRIMARY_SHORT as (keyof G['RestTableInterface'])[],
|
|
603
590
|
callback
|
|
604
591
|
})
|
|
605
592
|
break;
|
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {SelectQueryBuilder} from "../orm/queries/SelectQueryBuilder";
|
|
2
|
+
import {OrmGenerics} from "../types/ormGenerics";
|
|
2
3
|
import {
|
|
3
4
|
apiReturn,
|
|
4
5
|
DetermineResponseDataType,
|
|
5
6
|
iPostC6RestResponse,
|
|
6
7
|
iPutC6RestResponse,
|
|
7
|
-
iDeleteC6RestResponse
|
|
8
|
-
iRestMethods
|
|
8
|
+
iDeleteC6RestResponse
|
|
9
9
|
} from "../types/ormInterfaces";
|
|
10
10
|
import namedPlaceholders from 'named-placeholders';
|
|
11
11
|
import {PoolConnection, RowDataPacket, ResultSetHeader} from 'mysql2/promise';
|
|
12
12
|
import {Buffer} from 'buffer';
|
|
13
|
+
import {Executor} from "./Executor";
|
|
13
14
|
|
|
14
15
|
export class SqlExecutor<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
CustomAndRequiredFields extends Record<string, any> = any,
|
|
20
|
-
RequestTableOverrides extends { [key in keyof RestTableInterface]: any } = { [key in keyof RestTableInterface]: any }
|
|
21
|
-
> extends SqlBuilder<
|
|
22
|
-
RequestMethod,
|
|
23
|
-
RestShortTableName,
|
|
24
|
-
RestTableInterface,
|
|
25
|
-
PrimaryKey,
|
|
26
|
-
CustomAndRequiredFields,
|
|
27
|
-
RequestTableOverrides
|
|
28
|
-
> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
async execute(): Promise<apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>> {
|
|
16
|
+
G extends OrmGenerics
|
|
17
|
+
> extends Executor<G>{
|
|
18
|
+
|
|
19
|
+
async execute(): Promise<apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>> {
|
|
32
20
|
const {TABLE_NAME} = this.config.restModel;
|
|
33
21
|
const method = this.config.requestMethod;
|
|
34
22
|
|
|
@@ -39,14 +27,14 @@ export class SqlExecutor<
|
|
|
39
27
|
case 'GET': {
|
|
40
28
|
const rest = await this.select(TABLE_NAME, undefined, this.request);
|
|
41
29
|
console.log(`[SQL EXECUTOR] ✅ GET result:`, rest);
|
|
42
|
-
return rest as apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>;
|
|
30
|
+
return rest as apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>;
|
|
43
31
|
}
|
|
44
32
|
|
|
45
33
|
case 'POST': {
|
|
46
34
|
const result = await this.insert(TABLE_NAME, this.request);
|
|
47
35
|
console.log(`[SQL EXECUTOR] ✅ POST result:`, result);
|
|
48
36
|
const created: iPostC6RestResponse = {rest: result, created: true};
|
|
49
|
-
return created as apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>;
|
|
37
|
+
return created as apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>;
|
|
50
38
|
}
|
|
51
39
|
|
|
52
40
|
case 'PUT': {
|
|
@@ -57,7 +45,7 @@ export class SqlExecutor<
|
|
|
57
45
|
updated: true,
|
|
58
46
|
rowCount: result.rest.affectedRows
|
|
59
47
|
};
|
|
60
|
-
return updated as apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>;
|
|
48
|
+
return updated as apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>;
|
|
61
49
|
}
|
|
62
50
|
|
|
63
51
|
case 'DELETE': {
|
|
@@ -68,7 +56,7 @@ export class SqlExecutor<
|
|
|
68
56
|
deleted: true,
|
|
69
57
|
rowCount: result.rest.affectedRows
|
|
70
58
|
};
|
|
71
|
-
return deleted as apiReturn<DetermineResponseDataType<RequestMethod, RestTableInterface>>;
|
|
59
|
+
return deleted as apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>;
|
|
72
60
|
}
|
|
73
61
|
|
|
74
62
|
default:
|
|
@@ -92,8 +80,8 @@ export class SqlExecutor<
|
|
|
92
80
|
([k, v]) => [k, Buffer.isBuffer(v) ? v.toString('hex').toUpperCase() : v]
|
|
93
81
|
));
|
|
94
82
|
|
|
95
|
-
async select
|
|
96
|
-
const QueryResult = this.
|
|
83
|
+
async select(table: G['RestShortTableName'], primary: string | undefined, args: any) {
|
|
84
|
+
const QueryResult = (new SelectQueryBuilder(this.config, this.request)).build(table, args, primary);
|
|
97
85
|
console.log(`[SQL EXECUTOR] 🧠 Generated SELECT SQL:`, QueryResult);
|
|
98
86
|
const formatted = this.formatSQLWithParams(QueryResult.sql, QueryResult.params);
|
|
99
87
|
console.log(`[SQL EXECUTOR] 🧠 Formatted SELECT SQL:`, formatted);
|
|
@@ -111,7 +99,7 @@ export class SqlExecutor<
|
|
|
111
99
|
});
|
|
112
100
|
}
|
|
113
101
|
|
|
114
|
-
async insert
|
|
102
|
+
async insert(table: G['RestShortTableName'], data: Record<string, any>) {
|
|
115
103
|
const keys = Object.keys(data);
|
|
116
104
|
const values = keys.map(k => data[k]);
|
|
117
105
|
const placeholders = keys.map(() => '?').join(', ');
|
|
@@ -132,7 +120,7 @@ export class SqlExecutor<
|
|
|
132
120
|
});
|
|
133
121
|
}
|
|
134
122
|
|
|
135
|
-
async update
|
|
123
|
+
async update(table: G['RestShortTableName'], primary: string[], data: Record<string, any>) {
|
|
136
124
|
if (!primary?.length) throw new Error('Primary key is required for update');
|
|
137
125
|
const keys = Object.keys(data);
|
|
138
126
|
const values = keys.map(k => data[k]);
|
|
@@ -156,7 +144,7 @@ export class SqlExecutor<
|
|
|
156
144
|
});
|
|
157
145
|
}
|
|
158
146
|
|
|
159
|
-
async delete
|
|
147
|
+
async delete(table: G['RestShortTableName'], primary: string[], args: Record<string, any>) {
|
|
160
148
|
const key = primary?.[0];
|
|
161
149
|
if (!key || !args?.[key]) throw new Error('Primary key and value required for delete');
|
|
162
150
|
const sql = `DELETE
|
|
@@ -49,7 +49,7 @@ export function ExpressHandler({C6, mysqlPool}: { C6: iC6Object, mysqlPool: Pool
|
|
|
49
49
|
res.status(200).json({success: true, ...response});
|
|
50
50
|
|
|
51
51
|
} catch (err) {
|
|
52
|
-
res.status(500).json({success: false});
|
|
52
|
+
res.status(500).json({success: false, error: err});
|
|
53
53
|
next(err);
|
|
54
54
|
}
|
|
55
55
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import isVerbose from "../../../variables/isVerbose";
|
|
2
|
+
import {Executor} from "../../executors/Executor";
|
|
3
|
+
import {OrmGenerics} from "../../types/ormGenerics";
|
|
4
|
+
|
|
5
|
+
export abstract class AggregateBuilder<G extends OrmGenerics> extends Executor<G>{
|
|
6
|
+
buildAggregateField(field: string | any[]): string {
|
|
7
|
+
if (typeof field === 'string') {
|
|
8
|
+
return field;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (!Array.isArray(field) || field.length === 0) {
|
|
12
|
+
throw new Error('Invalid SELECT field entry');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let [fn, ...args] = field;
|
|
16
|
+
let alias: string | undefined;
|
|
17
|
+
|
|
18
|
+
if (args.length >= 2 && String(args[args.length - 2]).toUpperCase() === 'AS') {
|
|
19
|
+
alias = String(args.pop());
|
|
20
|
+
args.pop();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const F = String(fn).toUpperCase();
|
|
24
|
+
const argList = args.map(arg =>
|
|
25
|
+
Array.isArray(arg) ? this.buildAggregateField(arg) : arg
|
|
26
|
+
).join(', ');
|
|
27
|
+
|
|
28
|
+
let expr = `${F}(${argList})`;
|
|
29
|
+
|
|
30
|
+
if (alias) {
|
|
31
|
+
expr += ` AS ${alias}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
isVerbose() && console.log(`[SELECT] ${expr}`);
|
|
35
|
+
|
|
36
|
+
return expr;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {C6Constants} from "api/C6Constants";
|
|
2
|
+
import isVerbose from "../../../variables/isVerbose";
|
|
3
|
+
import {OrmGenerics} from "../../types/ormGenerics";
|
|
4
|
+
import {apiReturn, DetermineResponseDataType} from "../../types/ormInterfaces";
|
|
5
|
+
import {convertHexIfBinary} from "../utils/sqlUtils";
|
|
6
|
+
import {AggregateBuilder} from "./AggregateBuilder";
|
|
7
|
+
|
|
8
|
+
export class ConditionBuilder<
|
|
9
|
+
G extends OrmGenerics
|
|
10
|
+
> extends AggregateBuilder<G> {
|
|
11
|
+
execute(): Promise<apiReturn<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>> {
|
|
12
|
+
throw new Error("Method not implemented.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private readonly OPERATORS = new Set([
|
|
16
|
+
'=', '!=', '<', '<=', '>', '>=',
|
|
17
|
+
'LIKE', 'NOT LIKE', 'IN', 'NOT IN',
|
|
18
|
+
'IS', 'IS NOT', 'BETWEEN', 'NOT BETWEEN',
|
|
19
|
+
C6Constants.MATCH_AGAINST
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
private validateOperator(op: string) {
|
|
23
|
+
if (!this.OPERATORS.has(op)) {
|
|
24
|
+
throw new Error(`Invalid or unsupported SQL operator detected: '${op}'`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private addParam(
|
|
29
|
+
params: any[] | Record<string, any>,
|
|
30
|
+
column: string,
|
|
31
|
+
value: any
|
|
32
|
+
): string {
|
|
33
|
+
const columnDef = this.config.C6[column.split('.')[0]]?.TYPE_VALIDATION?.[column];
|
|
34
|
+
const val = convertHexIfBinary(column, value, columnDef);
|
|
35
|
+
|
|
36
|
+
if (this.useNamedParams) {
|
|
37
|
+
const key = `param${Object.keys(params).length}`;
|
|
38
|
+
(params as Record<string, any>)[key] = val;
|
|
39
|
+
return `:${key}`;
|
|
40
|
+
} else {
|
|
41
|
+
(params as any[]).push(val);
|
|
42
|
+
return '?';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
buildBooleanJoinedConditions(
|
|
47
|
+
set: any,
|
|
48
|
+
andMode: boolean = true,
|
|
49
|
+
params: any[] | Record<string, any> = []
|
|
50
|
+
): string {
|
|
51
|
+
const booleanOperator = andMode ? 'AND' : 'OR';
|
|
52
|
+
|
|
53
|
+
const addCondition = (column: string, op: string, value: any): string => {
|
|
54
|
+
this.validateOperator(op);
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if (op === C6Constants.MATCH_AGAINST && Array.isArray(value)) {
|
|
58
|
+
const [search, mode] = value;
|
|
59
|
+
const paramName = this.useNamedParams ? `param${Object.keys(params).length}` : null;
|
|
60
|
+
if (this.useNamedParams) {
|
|
61
|
+
params[paramName!] = search;
|
|
62
|
+
} else {
|
|
63
|
+
params.push(search);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let againstClause: string;
|
|
67
|
+
|
|
68
|
+
switch ((mode || '').toUpperCase()) {
|
|
69
|
+
case 'BOOLEAN':
|
|
70
|
+
againstClause = this.useNamedParams ? `AGAINST(:${paramName} IN BOOLEAN MODE)` : `AGAINST(? IN BOOLEAN MODE)`;
|
|
71
|
+
break;
|
|
72
|
+
case 'WITH QUERY EXPANSION':
|
|
73
|
+
againstClause = this.useNamedParams ? `AGAINST(:${paramName} WITH QUERY EXPANSION)` : `AGAINST(? WITH QUERY EXPANSION)`;
|
|
74
|
+
break;
|
|
75
|
+
default: // NATURAL or undefined
|
|
76
|
+
againstClause = this.useNamedParams ? `AGAINST(:${paramName})` : `AGAINST(?)`;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const matchClause = `(MATCH(${column}) ${againstClause})`;
|
|
81
|
+
isVerbose() && console.log(`[MATCH_AGAINST] ${matchClause}`);
|
|
82
|
+
return matchClause;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// handle other operators
|
|
86
|
+
return `( ${column} ${op} ${this.addParam(params, column, value)} )`;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const parts: string[] = [];
|
|
90
|
+
|
|
91
|
+
if (typeof set === 'object' && !Array.isArray(set)) {
|
|
92
|
+
for (const [key, value] of Object.entries(set)) {
|
|
93
|
+
if (typeof value === 'object' && value !== null && Object.keys(value).length === 1) {
|
|
94
|
+
const [op, val] = Object.entries(value)[0];
|
|
95
|
+
parts.push(addCondition(key, op, val));
|
|
96
|
+
} else {
|
|
97
|
+
parts.push(addCondition(key, '=', value));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const clause = parts.join(` ${booleanOperator} `);
|
|
103
|
+
return clause ? `(${clause})` : '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
buildWhereClause(whereArg: any, params: any[] | Record<string, any>): string {
|
|
107
|
+
const clause = this.buildBooleanJoinedConditions(whereArg, true, params);
|
|
108
|
+
if (!clause) return '';
|
|
109
|
+
const trimmed = clause.replace(/^\((.*)\)$/, '$1');
|
|
110
|
+
isVerbose() && console.log(`[WHERE] ${trimmed}`);
|
|
111
|
+
return ` WHERE ${trimmed}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import isVerbose from "../../../variables/isVerbose";
|
|
2
|
+
import {OrmGenerics} from "../../types/ormGenerics";
|
|
3
|
+
import {ConditionBuilder} from "./ConditionBuilder";
|
|
4
|
+
|
|
5
|
+
export class JoinBuilder<G extends OrmGenerics> extends ConditionBuilder<G>{
|
|
6
|
+
|
|
7
|
+
buildJoinClauses(joinArgs: any, params: any[] | Record<string, any>): string {
|
|
8
|
+
let sql = '';
|
|
9
|
+
|
|
10
|
+
for (const joinType in joinArgs) {
|
|
11
|
+
const joinKind = joinType.replace('_', ' ').toUpperCase();
|
|
12
|
+
|
|
13
|
+
for (const raw in joinArgs[joinType]) {
|
|
14
|
+
const [table, alias] = raw.split(' ');
|
|
15
|
+
const onClause = this.buildBooleanJoinedConditions(joinArgs[joinType][raw], true, params);
|
|
16
|
+
const joinSql = alias ? `\`${table}\` AS \`${alias}\`` : `\`${table}\``;
|
|
17
|
+
sql += ` ${joinKind} JOIN ${joinSql} ON ${onClause}`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
isVerbose() && console.log(`[JOIN] ${sql.trim()}`);
|
|
22
|
+
|
|
23
|
+
return sql;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {C6Constants} from "api/C6Constants";
|
|
2
|
+
import isVerbose from "../../../variables/isVerbose";
|
|
3
|
+
import {OrmGenerics} from "../../types/ormGenerics";
|
|
4
|
+
import {JoinBuilder} from "./JoinBuilder";
|
|
5
|
+
|
|
6
|
+
export class PaginationBuilder<G extends OrmGenerics> extends JoinBuilder<G> {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* MySQL ORDER/LIMIT/OFFSET generator.
|
|
10
|
+
*
|
|
11
|
+
* Accepted structures:
|
|
12
|
+
* ```ts
|
|
13
|
+
* ORDER: {
|
|
14
|
+
* // simple column with direction
|
|
15
|
+
* [property_units.UNIT_ID]: "DESC",
|
|
16
|
+
* // function call (array of arguments)
|
|
17
|
+
* [C6Constants.ST_DISTANCE_SPHERE]: [property_units.LOCATION, F(property_units.LOCATION, "pu_target")]
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
buildPaginationClause(pagination: any): string {
|
|
22
|
+
let sql = "";
|
|
23
|
+
|
|
24
|
+
/* -------- ORDER BY -------- */
|
|
25
|
+
if (pagination?.[C6Constants.ORDER]) {
|
|
26
|
+
const orderParts: string[] = [];
|
|
27
|
+
|
|
28
|
+
for (const [key, val] of Object.entries(pagination[C6Constants.ORDER])) {
|
|
29
|
+
// FUNCTION CALL: val is an array of args
|
|
30
|
+
if (Array.isArray(val)) {
|
|
31
|
+
const args = val
|
|
32
|
+
.map((arg) => Array.isArray(arg) ? this.buildAggregateField(arg) : String(arg))
|
|
33
|
+
.join(", ");
|
|
34
|
+
orderParts.push(`${key}(${args})`);
|
|
35
|
+
}
|
|
36
|
+
// SIMPLE COLUMN + DIR (ASC/DESC)
|
|
37
|
+
else {
|
|
38
|
+
orderParts.push(`${key} ${String(val).toUpperCase()}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (orderParts.length) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* -------- LIMIT / OFFSET -------- */
|
|
46
|
+
if (pagination?.[C6Constants.LIMIT] != null) {
|
|
47
|
+
const lim = parseInt(pagination[C6Constants.LIMIT], 10);
|
|
48
|
+
const page = parseInt(pagination[C6Constants.PAGE] ?? 1, 10);
|
|
49
|
+
const offset = (page - 1) * lim;
|
|
50
|
+
sql += ` LIMIT ${offset}, ${lim}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
isVerbose() && console.log(`[PAGINATION] ${sql.trim()}`);
|
|
54
|
+
return sql;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {OrmGenerics} from "../../types/ormGenerics";
|
|
2
|
+
import {PaginationBuilder} from "../builders/PaginationBuilder";
|
|
3
|
+
import {SqlBuilderResult} from "../utils/sqlUtils";
|
|
4
|
+
|
|
5
|
+
export class DeleteQueryBuilder<G extends OrmGenerics>
|
|
6
|
+
extends PaginationBuilder<G> {
|
|
7
|
+
|
|
8
|
+
build(table: string, args: any = {}): SqlBuilderResult {
|
|
9
|
+
const params = this.useNamedParams ? {} : [];
|
|
10
|
+
let sql = args.JOIN ? `DELETE ${table}
|
|
11
|
+
FROM \`${table}\`` : `DELETE
|
|
12
|
+
FROM \`${table}\``;
|
|
13
|
+
|
|
14
|
+
if (args.JOIN) {
|
|
15
|
+
sql += this.buildJoinClauses(args.JOIN, params);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (args.WHERE) {
|
|
19
|
+
sql += this.buildWhereClause(args.WHERE, params);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (args.PAGINATION) {
|
|
23
|
+
sql += this.buildPaginationClause(args.PAGINATION);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {sql, params};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {OrmGenerics} from "../../types/ormGenerics";
|
|
2
|
+
import {PaginationBuilder} from "../builders/PaginationBuilder";
|
|
3
|
+
import {SqlBuilderResult} from "../utils/sqlUtils";
|
|
4
|
+
|
|
5
|
+
export class SelectQueryBuilder<G extends OrmGenerics> extends PaginationBuilder<G>{
|
|
6
|
+
|
|
7
|
+
build(
|
|
8
|
+
table: string,
|
|
9
|
+
args: any,
|
|
10
|
+
primary?: string,
|
|
11
|
+
isSubSelect: boolean = false
|
|
12
|
+
): SqlBuilderResult {
|
|
13
|
+
const params = this.useNamedParams ? {} : [];
|
|
14
|
+
|
|
15
|
+
const selectList = args.SELECT ?? ['*'];
|
|
16
|
+
const selectFields = selectList
|
|
17
|
+
.map((f: any) => this.buildAggregateField(f))
|
|
18
|
+
.join(', ');
|
|
19
|
+
|
|
20
|
+
let sql = `SELECT ${selectFields} FROM \`${table}\``;
|
|
21
|
+
|
|
22
|
+
if (args.JOIN) {
|
|
23
|
+
sql += this.buildJoinClauses(args.JOIN, params);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (args.WHERE) {
|
|
27
|
+
sql += this.buildWhereClause(args.WHERE, params);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (args.GROUP_BY) {
|
|
31
|
+
const groupBy = Array.isArray(args.GROUP_BY)
|
|
32
|
+
? args.GROUP_BY.join(', ')
|
|
33
|
+
: args.GROUP_BY;
|
|
34
|
+
sql += ` GROUP BY ${groupBy}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (args.HAVING) {
|
|
38
|
+
sql += ` HAVING ${this.buildBooleanJoinedConditions(args.HAVING, true, params)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.PAGINATION) {
|
|
42
|
+
sql += this.buildPaginationClause(args.PAGINATION);
|
|
43
|
+
} else if (!isSubSelect) {
|
|
44
|
+
sql += primary ? ` ORDER BY ${primary} ASC LIMIT 1` : ` LIMIT 100`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { sql, params };
|
|
48
|
+
}
|
|
49
|
+
}
|