@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.
Files changed (46) hide show
  1. package/dist/api/C6Constants.d.ts +183 -0
  2. package/dist/api/builders/queryHelpers.d.ts +4 -0
  3. package/dist/api/executors/Executor.d.ts +9 -15
  4. package/dist/api/executors/HttpExecutor.d.ts +7 -14
  5. package/dist/api/executors/SqlExecutor.d.ts +9 -12
  6. package/dist/api/orm/SqlBuilder.d.ts +17 -0
  7. package/dist/api/orm/builders/AggregateBuilder.d.ts +5 -0
  8. package/dist/api/orm/builders/ConditionBuilder.d.ts +11 -0
  9. package/dist/api/orm/builders/JoinBuilder.d.ts +5 -0
  10. package/dist/api/orm/builders/PaginationBuilder.d.ts +18 -0
  11. package/dist/api/orm/queries/DeleteQueryBuilder.d.ts +6 -0
  12. package/dist/api/orm/queries/SelectQueryBuilder.d.ts +6 -0
  13. package/dist/api/orm/queries/UpdateQueryBuilder.d.ts +6 -0
  14. package/dist/api/orm/queryHelpers.d.ts +4 -0
  15. package/dist/api/orm/utils/sqlUtils.d.ts +7 -0
  16. package/dist/api/restOrm.d.ts +3 -10
  17. package/dist/api/restRequest.d.ts +3 -10
  18. package/dist/api/types/ormGenerics.d.ts +13 -0
  19. package/dist/api/types/ormInterfaces.d.ts +23 -40
  20. package/dist/index.cjs.js +389 -407
  21. package/dist/index.cjs.js.map +1 -1
  22. package/dist/index.d.ts +10 -1
  23. package/dist/index.esm.js +377 -407
  24. package/dist/index.esm.js.map +1 -1
  25. package/package.json +1 -1
  26. package/scripts/assets/handlebars/C6.ts.handlebars +2 -4
  27. package/src/api/C6Constants.ts +37 -2
  28. package/src/api/executors/Executor.ts +18 -36
  29. package/src/api/executors/HttpExecutor.ts +46 -59
  30. package/src/api/executors/SqlExecutor.ts +17 -29
  31. package/src/api/handlers/ExpressHandler.ts +1 -1
  32. package/src/api/orm/builders/AggregateBuilder.ts +38 -0
  33. package/src/api/orm/builders/ConditionBuilder.ts +113 -0
  34. package/src/api/orm/builders/JoinBuilder.ts +25 -0
  35. package/src/api/orm/builders/PaginationBuilder.ts +56 -0
  36. package/src/api/orm/queries/DeleteQueryBuilder.ts +28 -0
  37. package/src/api/orm/queries/SelectQueryBuilder.ts +49 -0
  38. package/src/api/orm/queries/UpdateQueryBuilder.ts +42 -0
  39. package/src/api/orm/queryHelpers.ts +18 -0
  40. package/src/api/orm/utils/sqlUtils.ts +24 -0
  41. package/src/api/restOrm.ts +4 -14
  42. package/src/api/restRequest.ts +16 -34
  43. package/src/api/types/ormGenerics.ts +18 -0
  44. package/src/api/types/ormInterfaces.ts +31 -46
  45. package/src/index.ts +10 -1
  46. 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
- RequestMethod extends iRestMethods,
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 {SqlBuilder} from "api/builders/sqlBuilder";
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
- RequestMethod extends iRestMethods,
16
- RestShortTableName extends string = any,
17
- RestTableInterface extends Record<string, any> = any,
18
- PrimaryKey extends Extract<keyof RestTableInterface, string> = Extract<keyof RestTableInterface, string>,
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<TName extends string>(table: TName, primary: string | undefined, args: any) {
96
- const QueryResult = this.buildSelectQuery<TName>(table, primary, args);
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<TName extends string>(table: TName, data: Record<string, any>) {
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<TName extends string>(table: TName, primary: string[], data: Record<string, any>) {
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<TName extends string>(table: TName, primary: string[], args: Record<string, any>) {
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
+ }