@carbonorm/carbonnode 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/api/builders/sqlBuilder.d.ts +3 -0
  2. package/dist/api/convertForRequestBody.d.ts +1 -1
  3. package/dist/api/executors/Executor.d.ts +16 -0
  4. package/dist/api/executors/HttpExecutor.d.ts +13 -0
  5. package/dist/api/executors/SqlExecutor.d.ts +19 -0
  6. package/dist/api/restRequest.d.ts +9 -166
  7. package/dist/api/types/dynamicFetching.d.ts +10 -0
  8. package/dist/api/types/modifyTypes.d.ts +9 -0
  9. package/dist/api/types/mysqlTypes.d.ts +4 -0
  10. package/dist/api/types/ormInterfaces.d.ts +219 -0
  11. package/dist/api/utils/apiHelpers.d.ts +9 -0
  12. package/dist/api/utils/cacheManager.d.ts +10 -0
  13. package/dist/api/utils/determineRuntimeJsType.d.ts +5 -0
  14. package/dist/api/utils/logger.d.ts +7 -0
  15. package/dist/api/utils/sortAndSerializeQueryObject.d.ts +1 -0
  16. package/dist/api/utils/testHelpers.d.ts +1 -0
  17. package/dist/api/utils/toastNotifier.d.ts +2 -0
  18. package/dist/index.cjs.js +665 -614
  19. package/dist/index.cjs.js.map +1 -1
  20. package/dist/index.d.ts +15 -2
  21. package/dist/index.esm.js +655 -618
  22. package/dist/index.esm.js.map +1 -1
  23. package/package.json +22 -6
  24. package/scripts/assets/handlebars/C6.ts.handlebars +13 -5
  25. package/scripts/assets/handlebars/Table.ts.handlebars +44 -12
  26. package/scripts/generateRestBindings.cjs +1 -1
  27. package/scripts/generateRestBindings.ts +1 -1
  28. package/src/api/builders/sqlBuilder.ts +173 -0
  29. package/src/api/convertForRequestBody.ts +2 -3
  30. package/src/api/executors/Executor.ts +28 -0
  31. package/src/api/executors/HttpExecutor.ts +794 -0
  32. package/src/api/executors/SqlExecutor.ts +104 -0
  33. package/src/api/restRequest.ts +50 -1287
  34. package/src/api/types/dynamicFetching.ts +10 -0
  35. package/src/api/types/modifyTypes.ts +25 -0
  36. package/src/api/types/mysqlTypes.ts +33 -0
  37. package/src/api/types/ormInterfaces.ts +310 -0
  38. package/src/api/utils/apiHelpers.ts +82 -0
  39. package/src/api/utils/cacheManager.ts +67 -0
  40. package/src/api/utils/determineRuntimeJsType.ts +46 -0
  41. package/src/api/utils/logger.ts +24 -0
  42. package/src/api/utils/sortAndSerializeQueryObject.ts +12 -0
  43. package/src/api/utils/testHelpers.ts +24 -0
  44. package/src/api/utils/toastNotifier.ts +11 -0
  45. package/src/index.ts +15 -2
  46. package/src/api/carbonSqlExecutor.ts +0 -279
  47. package/src/api/interfaces/ormInterfaces.ts +0 -87
@@ -1,279 +0,0 @@
1
- import {iC6Object} from "@carbonorm/carbonnode";
2
- import { Pool, PoolConnection, RowDataPacket } from 'mysql2/promise';
3
- import { Request, Response, NextFunction } from 'express';
4
- // import { validatePayloadAgainstSchema } from './validator'; // C6 schema validator
5
-
6
-
7
- export class CarbonSqlExecutor {
8
-
9
- constructor(private pool: Pool, private C6 : iC6Object ) {}
10
-
11
- private async withConnection<T>(cb: (conn: PoolConnection) => Promise<T>): Promise<T> {
12
- const conn = await this.pool.getConnection();
13
- try {
14
- return await cb(conn);
15
- } finally {
16
- conn.release();
17
- }
18
- }
19
-
20
- public async handle(req: Request, res: Response, next: NextFunction) {
21
- try {
22
- const method = req.method.toUpperCase();
23
- const table = req.params.table;
24
- const primary = req.params.primary;
25
- const payload = method === 'GET' ? req.query : req.body;
26
-
27
- if (!(table in this.C6.TABLES)){
28
- res.status(400).json({ error: `Invalid table: ${table}` });
29
- return;
30
- }
31
-
32
- let result: unknown;
33
-
34
- switch (method) {
35
- case 'GET':
36
- case 'OPTIONS':
37
- result = await this.select(table, primary, payload);
38
- break;
39
- case 'POST':
40
- result = await this.insert(table, payload);
41
- break;
42
- case 'PUT':
43
- result = await this.update(table, primary, payload);
44
- break;
45
- case 'DELETE':
46
- result = await this.delete(table, primary, payload);
47
- break;
48
- default:
49
- throw new Error(`Unsupported method: ${method}`);
50
- }
51
-
52
- res.status(200).json({ success: true, result });
53
- } catch (err) {
54
- next(err);
55
- }
56
- }
57
-
58
- private buildBooleanJoinedConditions(set: any, andMode = true): string {
59
- const booleanOperator = andMode ? 'AND' : 'OR';
60
- let sql = '';
61
-
62
- const OPERATORS = ['=', '!=', '<', '<=', '>', '>=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'IS', 'IS NOT'];
63
-
64
- const isAggregateArray = (value: any) => Array.isArray(value) && typeof value[0] === 'string' && OPERATORS.includes(value[0]);
65
-
66
- const isNumericKeyed = (obj: any) => Array.isArray(obj) && Object.keys(obj).every(k => /^\d+$/.test(k));
67
-
68
- // todo - we should be doing something with value no????
69
- const addCondition = (column: string, op: string, _value: any): string => {
70
- const paramName = column.replace(/\W+/g, '_');
71
- return `(${column} ${op} :${paramName})`;
72
- };
73
-
74
- if (isNumericKeyed(set)) {
75
- switch (set.length) {
76
- case 2:
77
- sql += addCondition(set[0], '=', set[1]);
78
- break;
79
- case 3:
80
- if (!OPERATORS.includes(set[1])) {
81
- throw new Error(`Invalid operator: ${set[1]}`);
82
- }
83
- sql += addCondition(set[0], set[1], set[2]);
84
- break;
85
- default:
86
- throw new Error(`Invalid array condition: ${JSON.stringify(set)}`);
87
- }
88
- } else {
89
- const parts: string[] = [];
90
- for (const [key, value] of Object.entries(set)) {
91
- if (/^\d+$/.test(key)) {
92
- parts.push(this.buildBooleanJoinedConditions(value, !andMode));
93
- continue;
94
- }
95
-
96
- if (!Array.isArray(value) || isAggregateArray(value)) {
97
- parts.push(addCondition(key, '=', value));
98
- continue;
99
- }
100
-
101
- if (value.length === 2 && OPERATORS.includes(value[0])) {
102
- parts.push(addCondition(key, value[0], value[1]));
103
- } else if (value.length === 1 && isAggregateArray(value[0])) {
104
- parts.push(addCondition(key, '=', value[0]));
105
- } else {
106
- throw new Error(`Invalid condition for ${key}: ${JSON.stringify(value)}`);
107
- }
108
- }
109
-
110
- sql = parts.join(` ${booleanOperator} `);
111
- }
112
-
113
- return `(${sql})`;
114
- }
115
-
116
- private buildAggregateField(field: string | any[]): string {
117
- if (typeof field === 'string') return field;
118
-
119
- if (!Array.isArray(field)) throw new Error('Invalid SELECT entry: must be string or array');
120
-
121
- const [agg, ...args] = field;
122
-
123
- switch (agg) {
124
- case 'COUNT':
125
- return `COUNT(${args[0] || '*'})`;
126
- case 'SUM':
127
- case 'AVG':
128
- case 'MIN':
129
- case 'MAX':
130
- return `${agg}(${args[0]})${args[1] ? ` AS ${args[1]}` : ''}`;
131
- case 'DISTINCT':
132
- return `DISTINCT(${args[0]})${args[1] ? ` AS ${args[1]}` : ''}`;
133
- case 'GROUP_CONCAT': {
134
- const [col, alias, sortCol, sortType] = args;
135
- const order = sortCol ? ` ORDER BY ${sortCol} ${sortType || 'ASC'}` : '';
136
- return `GROUP_CONCAT(DISTINCT ${col}${order} SEPARATOR ',')${alias ? ` AS ${alias}` : ''}`;
137
- }
138
- case 'AS': {
139
- const [col, alias] = args;
140
- return `${col} AS ${alias}`;
141
- }
142
- case 'CONVERT_TZ': {
143
- const [ts, fromTz, toTz] = args;
144
- return `CONVERT_TZ(${ts}, ${fromTz}, ${toTz})`;
145
- }
146
- case 'NOW':
147
- return 'NOW()';
148
- default:
149
- throw new Error(`Unsupported aggregate: ${agg}`);
150
- }
151
- }
152
-
153
- private buildSelectQuery<RestShortTableNames>(table: RestShortTableNames, primary: string | undefined, args: any, isSubSelect = false): string {
154
- const selectList = args?.[this.C6.SELECT] ?? ['*'];
155
- const selectFields = Array.isArray(selectList)
156
- ? selectList.map(f => this.buildAggregateField(f)).join(', ')
157
- : '*';
158
-
159
- let sql = `SELECT ${selectFields} FROM \`${table}\``;
160
-
161
- if (args?.[this.C6.JOIN]) {
162
- const joins = args[this.C6.JOIN];
163
- for (const joinType in joins) {
164
- const joinKeyword = joinType.replace('_', ' ').toUpperCase();
165
- for (const joinTable in joins[joinType]) {
166
- const onClause = this.buildBooleanJoinedConditions(joins[joinType][joinTable]);
167
- sql += ` ${joinKeyword} JOIN \`${joinTable}\` ON ${onClause}`;
168
- }
169
- }
170
- }
171
-
172
- if (args?.[this.C6.WHERE]) {
173
- sql += ` WHERE ${this.buildBooleanJoinedConditions(args[this.C6.WHERE])}`;
174
- }
175
-
176
- if (args?.[this.C6.GROUP_BY]) {
177
- const groupByFields = Array.isArray(args[this.C6.GROUP_BY]) ? args[this.C6.GROUP_BY].join(', ') : args[this.C6.GROUP_BY];
178
- sql += ` GROUP BY ${groupByFields}`;
179
- }
180
-
181
- if (args?.[this.C6.HAVING]) {
182
- sql += ` HAVING ${this.buildBooleanJoinedConditions(args[this.C6.HAVING])}`;
183
- }
184
-
185
- if (args?.[this.C6.PAGINATION]) {
186
- const p = args[this.C6.PAGINATION];
187
- let limitClause = '';
188
-
189
- if (p[this.C6.ORDER]) {
190
- const orderArray = Object.entries(p[this.C6.ORDER]).map(([col, dir]) => {
191
- if (!['ASC', 'DESC'].includes(String(dir).toUpperCase())) {
192
- throw new Error(`Invalid order direction: ${dir}`);
193
- }
194
- return `${col} ${String(dir).toUpperCase()}`;
195
- });
196
- sql += ` ORDER BY ${orderArray.join(', ')}`;
197
- } else if (primary) {
198
- sql += ` ORDER BY ${primary} DESC`;
199
- } else {
200
- // todo this is wrong
201
- const primaryKey = this.C6.TABLES['users'].PRIMARY_SHORT?.[0] ?? 'user_id';
202
- sql += ` ORDER BY ${primaryKey} DESC`;
203
- }
204
-
205
- if (p[this.C6.LIMIT] != null) {
206
- const limit = parseInt(p[this.C6.LIMIT], 10);
207
- if (isNaN(limit) || limit < 0) {
208
- throw new Error(`Invalid LIMIT: ${p[this.C6.LIMIT]}`);
209
- }
210
-
211
- const page = parseInt(p[this.C6.PAGE] ?? 1, 10);
212
- if (isNaN(page) || page < 1) {
213
- throw new Error(`PAGE must be >= 1 (got ${p[this.C6.PAGE]})`);
214
- }
215
-
216
- const offset = (page - 1) * limit;
217
- limitClause += ` LIMIT ${offset}, ${limit}`;
218
- }
219
-
220
- sql += limitClause;
221
- } else if (!isSubSelect && primary) {
222
- sql += ` ORDER BY ${primary} ASC LIMIT 1`;
223
- } else if (!isSubSelect && !primary) {
224
- sql += ` ORDER BY id ASC LIMIT 100`; // fallback default limit
225
- }
226
-
227
- return sql;
228
- }
229
-
230
- async select<RestShortTableNames>(table: RestShortTableNames, primary: string | undefined, args: any) {
231
- const sql = this.buildSelectQuery<RestShortTableNames>(table, primary, args);
232
- return await this.withConnection(async (conn) => {
233
- console.log(sql)
234
- const [rows] = await conn.query<RowDataPacket[]>(sql);
235
- return rows;
236
- });
237
- }
238
-
239
- async insert<RestShortTableNames>(table: RestShortTableNames, data: any) {
240
- const keys = Object.keys(data);
241
- const values = keys.map(k => data[k]);
242
- const placeholders = keys.map(() => '?').join(', ');
243
- const sql = `INSERT INTO \`${table}\` (${keys.join(', ')}) VALUES (${placeholders})`;
244
- return await this.withConnection(async (conn) => {
245
- const [result] = await conn.execute(sql, values);
246
- return result;
247
- });
248
- }
249
-
250
- async update<RestShortTableNames>(table: RestShortTableNames, primary: string | undefined, data: any) {
251
- if (!primary) {
252
- throw new Error('Primary key is required for update');
253
- }
254
-
255
- const keys = Object.keys(data);
256
- const values = keys.map(k => data[k]);
257
- const updates = keys.map(k => `\`${k}\` = ?`).join(', ');
258
- const sql = `UPDATE \`${table}\` SET ${updates} WHERE \`${primary}\` = ?`;
259
- values.push(data[primary]);
260
-
261
- return await this.withConnection(async (conn) => {
262
- const [result] = await conn.execute(sql, values);
263
- return result;
264
- });
265
- }
266
-
267
- async delete<RestShortTableNames>(table: RestShortTableNames, primary: string | undefined, args: any) {
268
- if (!primary || !args?.[primary]) {
269
- throw new Error('Primary key and value required for delete');
270
- }
271
-
272
- const sql = `DELETE FROM \`${table}\` WHERE \`${primary}\` = ?`;
273
- return await this.withConnection(async (conn) => {
274
- const [result] = await conn.execute(sql, [args[primary]]);
275
- return result;
276
- });
277
- }
278
- }
279
-
@@ -1,87 +0,0 @@
1
- import {
2
- apiReturn,
3
- iAPI,
4
- iDeleteC6RestResponse,
5
- iPostC6RestResponse,
6
- iGetC6RestResponse,
7
- iPutC6RestResponse, RequestGetPutDeleteBody
8
- } from "api/restRequest";
9
- import {AxiosResponse} from "axios";
10
-
11
-
12
- export interface stringMap {
13
- [key: string]: string;
14
- }
15
-
16
- export interface stringNumberMap {
17
- [key: string]: string | number;
18
- }
19
-
20
- export interface RegExpMap {
21
- [key: string]: RegExp | RegExpMap;
22
- }
23
-
24
- export interface complexMap {
25
- [key: string]: stringMap | stringNumberMap | stringMap[] | RegExpMap;
26
- }
27
-
28
- export interface iTypeValidation {
29
- MYSQL_TYPE: string,
30
- MAX_LENGTH: string,
31
- AUTO_INCREMENT: boolean,
32
- SKIP_COLUMN_IN_POST: boolean
33
- }
34
-
35
- export type iRestReactiveLifecycle<T extends RequestGetPutDeleteBody> = {
36
- beforeProcessing?: (args: { request: T[]; requestMeta?: any }) => void | Promise<void>;
37
- beforeExecution?: (args: { request: T[]; requestMeta?: any }) => void | Promise<void>;
38
- afterExecution?: (args: { response: T[]; request: T[]; responseMeta?: any }) => void | Promise<void>;
39
- afterCommit?: (args: { response: T[]; request: T[]; responseMeta?: any }) => void | Promise<void>;
40
- };
41
-
42
- export interface iConstraint {
43
- TABLE: string,
44
- COLUMN: string,
45
- CONSTRAINT: string
46
- }
47
-
48
- export interface iC6RestfulModel<RestShortTableNames extends string = string> {
49
- TABLE_NAME: RestShortTableNames,
50
- PRIMARY: string[],
51
- PRIMARY_SHORT: string[],
52
- COLUMNS: stringMap,
53
- LIFECYCLE_HOOKS: iRestReactiveLifecycle<RequestGetPutDeleteBody>[],
54
- REGEX_VALIDATION: RegExpMap,
55
- TYPE_VALIDATION: { [key: string]: iTypeValidation },
56
- TABLE_REFERENCES: { [columnName: string]: iConstraint[] },
57
- TABLE_REFERENCED_BY: { [columnName: string]: iConstraint[] },
58
- }
59
-
60
- export interface iRestApiFunctions<RestData = any> {
61
- Delete: (request?: (iAPI<any> & any)) => apiReturn<iDeleteC6RestResponse<RestData>>;
62
- Post: (request?: (iAPI<any> & any)) => apiReturn<iPostC6RestResponse<RestData>>;
63
- Get: (request?: (iAPI<any> & any)) => apiReturn<iGetC6RestResponse<RestData>>;
64
- Put: (request?: (iAPI<any> & any)) => apiReturn<iPutC6RestResponse<RestData>>,
65
- }
66
-
67
- export interface iDynamicApiImport<RestData = any> {
68
- default: iRestApiFunctions<RestData>
69
- // the methods below are optional
70
- postState?: (response: AxiosResponse<iPostC6RestResponse<RestData>>, request: iAPI<any>, id: string | number | boolean) => void,
71
- deleteState?: (response: AxiosResponse<iDeleteC6RestResponse<RestData>>, request: iAPI<any>) => void,
72
- putState?: (response: AxiosResponse<iPutC6RestResponse<RestData>>, request: iAPI<any>) => void
73
- }
74
-
75
- export interface tC6Tables { [key: string]: (iC6RestfulModel & { [key: string]: any }) }
76
-
77
- export interface tC6RestApi {
78
- [key: string]: {
79
- REST: iRestApiFunctions,
80
- PUT: Function;
81
- POST: Function;
82
- DELETE: Function;
83
- };
84
- }
85
-
86
-
87
-