@carbonorm/carbonnode 1.1.3 → 1.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbonorm/carbonnode",
3
- "version": "1.1.3",
3
+ "version": "1.1.8",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "browser": "dist/index.umd.js",
@@ -25,6 +25,7 @@
25
25
  "@types/qs": "^6.9.8",
26
26
  "autoprefixer": "^10.4.14",
27
27
  "deepmerge": "^4.3.1",
28
+ "handlebars": "^4.7.8",
28
29
  "postcss": "^8.4.27",
29
30
  "postcss-nested": "^6.0.1",
30
31
  "postcss-simple-vars": "^7.0.1",
@@ -34,14 +35,20 @@
34
35
  "typescript": "^5.1.6"
35
36
  },
36
37
  "scripts": {
37
- "build": "npm run build:index && rollup -c",
38
+ "build": "npm run build:index && npm run build:generateRestBindings && rollup -c",
38
39
  "dev": "rollup -c -w",
39
40
  "test": "node test/test.js",
40
41
  "pretest": "npm run build",
41
- "build:index": "npx barrelsby -d ./src --delete --exclude '(jestHoc|\\.test|\\.d).(js|tsx?)$' --exportDefault --verbose"
42
+ "build:index": "npx barrelsby -d ./src --delete --exclude '(jestHoc|\\.test|\\.d).(js|tsx?)$' --exportDefault --verbose",
43
+ "build:generateRestBindings": "cd ./scripts/ && tsc --downlevelIteration generateRestBindings.ts && mv generateRestBindings.js generateRestBindings.cjs",
44
+ "generateRestBindings": "npm run build:generateRestBindings && node ./scripts/generateRestBindings.cjs"
45
+ },
46
+ "bin": {
47
+ "generateRestBindings": "./scripts/generateRestBindings.cjs"
42
48
  },
43
49
  "files": [
44
50
  "dist",
45
- "src"
51
+ "src",
52
+ "scripts"
46
53
  ]
47
54
  }
@@ -0,0 +1,115 @@
1
+ import {
2
+ C6RestfulModel,
3
+ C6Constants
4
+ } from "@carbonorm/carbonnode";
5
+
6
+ export type RestTableNames = {{{RestTableNames}}};
7
+
8
+ export type RestShortTableNames = {{{RestShortTableNames}}};
9
+
10
+ {{#TABLES}}
11
+
12
+ export interface i{{TABLE_NAME_SHORT_PASCAL_CASE}} {
13
+ {{#each TYPE_VALIDATION}}
14
+ '{{this.COLUMN_NAME}}'?: {{this.TYPESCRIPT_TYPE}};
15
+ {{/each}}
16
+ }
17
+
18
+ interface iDefine{{TABLE_NAME_SHORT_PASCAL_CASE}} {
19
+ {{#each COLUMNS_UPPERCASE}}
20
+ '{{@key}}': string;
21
+ {{/each}}
22
+ }
23
+
24
+ export const {{TABLE_NAME_SHORT}}: C6RestfulModel & iDefine{{TABLE_NAME_SHORT_PASCAL_CASE}} = {
25
+ TABLE_NAME: '{{TABLE_NAME}}',
26
+ {{#each COLUMNS_UPPERCASE}}
27
+ {{@key}}: '{{this}}',
28
+ {{/each}}
29
+ PRIMARY: [
30
+ {{#PRIMARY}}
31
+ '{{this}}',
32
+ {{/PRIMARY}}
33
+ ],
34
+ PRIMARY_SHORT: [
35
+ {{#each PRIMARY_SHORT}}
36
+ '{{this}}',
37
+ {{/each}}
38
+ ],
39
+ COLUMNS: {
40
+ {{#each COLUMNS}}
41
+ '{{@key}}': '{{this}}',
42
+ {{/each}}
43
+ },
44
+ TYPE_VALIDATION: {
45
+ {{#each TYPE_VALIDATION}}
46
+ '{{@key}}': {
47
+ MYSQL_TYPE: '{{this.MYSQL_TYPE}}',
48
+ MAX_LENGTH: '{{this.MAX_LENGTH}}',
49
+ AUTO_INCREMENT: {{this.AUTO_INCREMENT}},
50
+ SKIP_COLUMN_IN_POST: {{this.SKIP_COLUMN_IN_POST}}
51
+ },
52
+ {{/each}}
53
+ },
54
+ REGEX_VALIDATION: {
55
+ {{#each REGEX_VALIDATION}}
56
+ '{{@key}}': '{{this}}',
57
+ {{/each}}
58
+ },
59
+ TABLE_REFERENCES: {
60
+ {{#each TABLE_REFERENCES}}'{{@key}}': [{{#this}}{
61
+ TABLE: '{{TABLE}}',
62
+ COLUMN: '{{COLUMN}}',
63
+ CONSTRAINT: '{{CONSTRAINT}}',
64
+ },{{/this}}],{{/each}}
65
+ },
66
+ TABLE_REFERENCED_BY: {
67
+ {{#each TABLE_REFERENCED_BY}}'{{@key}}': [{{#this}}{
68
+ TABLE: '{{TABLE}}',
69
+ COLUMN: '{{COLUMN}}',
70
+ CONSTRAINT: '{{CONSTRAINT}}',
71
+ },{{/this}}],{{/each}}
72
+ },
73
+ }
74
+ {{/TABLES}}
75
+
76
+ export const TABLES = {
77
+ {{#TABLES}}
78
+ {{TABLE_NAME_SHORT}}: {{TABLE_NAME_SHORT}},
79
+ {{/TABLES}}
80
+ };
81
+
82
+ export const C6 : { TABLES: { [key: string]: (C6RestfulModel & { [key: string]: any }) } }
83
+ & { [key: string]: any } = {
84
+ ...C6Constants,
85
+ TABLES: TABLES,
86
+ ...TABLES
87
+ };
88
+
89
+ export const COLUMNS = {
90
+ {{#TABLES}}{{#each TYPE_VALIDATION}}'{{@key}}': '{{this.COLUMN_NAME}}',{{/each}}
91
+ {{/TABLES}}
92
+
93
+ };
94
+
95
+
96
+ export type RestTableInterfaces = {{{RestTableInterfaces}}};
97
+
98
+ export type tStatefulApiData<T> = T[] | undefined | null;
99
+
100
+
101
+ // this refers to the value types of the keys above, aka values in the state
102
+ export interface iRestfulObjectArrayTypes {
103
+ {{#TABLES}}
104
+ {{TABLE_NAME_SHORT}}: tStatefulApiData<i{{TABLE_NAME_SHORT_PASCAL_CASE}}>,
105
+ {{/TABLES}}
106
+ }
107
+
108
+ export const initialRestfulObjectsState: iRestfulObjectArrayTypes = {
109
+ {{#TABLES}}
110
+ {{TABLE_NAME_SHORT}}: undefined,
111
+ {{/TABLES}}
112
+ };
113
+
114
+ export type tRestfulObjectArrayValues = iRestfulObjectArrayTypes[keyof iRestfulObjectArrayTypes];
115
+
@@ -0,0 +1,121 @@
1
+ import {xdescribe, expect, test} from '@jest/globals';
2
+ import {CarbonReact} from "@carbonorm/carbonreact";
3
+ import {checkAllRequestsComplete} from "@carbonorm/carbonnode";
4
+ import {act, waitFor} from '@testing-library/react';
5
+ import {C6, iRestfulObjectArrayTypes, i{{TABLE_NAME_SHORT_PASCAL_CASE}}, {{TABLE_NAME_SHORT}} } from "{{RELATIVE_OUTPUT_DIR}}/C6";
6
+
7
+ const randomString = Math.random().toString(36).substring(7);
8
+ const randomInt = Math.floor(Math.random() * 1000000);
9
+ const fillString = 'string' + randomString + randomInt;
10
+
11
+ /**
12
+ {{{TABLE_DEFINITION}}}
13
+ **/
14
+
15
+ const Test_Data: i{{TABLE_NAME_SHORT_PASCAL_CASE}} = {
16
+ {{#each TYPE_VALIDATION}}
17
+ {{#SKIP_COLUMN_IN_POST}}{{COLUMN_NAME}}: {{#TYPESCRIPT_TYPE_IS_STRING}}fillString.substring(0, {{MAX_LENGTH}}){{/TYPESCRIPT_TYPE_IS_STRING}}{{#TYPESCRIPT_TYPE_IS_NUMBER}}randomInt,{{/TYPESCRIPT_TYPE_IS_NUMBER}}{{/SKIP_COLUMN_IN_POST}}
18
+ {{/each}}
19
+ }
20
+
21
+ export default Test_Data;
22
+
23
+ xdescribe('REST {{TABLE_NAME_SHORT_PASCAL_CASE}} api', () => {
24
+
25
+ let testData = Test_Data;
26
+
27
+ test('GET POST PUT DELETE', async () => {
28
+
29
+ await act(async () => {
30
+
31
+ let selectAllResponse = await {{TABLE_NAME_SHORT}}.Get({})
32
+
33
+ if ('function' === typeof selectAllResponse) {
34
+ throw Error('selectAllResponse is a promise, this typically means this specific get request has already run during test setup.');
35
+ }
36
+
37
+ // We don't care if it is filled or not, just that the request can be made.
38
+ expect(selectAllResponse?.data?.rest).not.toBeUndefined();
39
+
40
+ const postResponse = await {{TABLE_NAME_SHORT}}.Post(testData);
41
+
42
+ console.log('postResponse', postResponse?.data)
43
+
44
+ expect(postResponse?.data?.created).not.toBeUndefined();
45
+
46
+ const primaryKey = {{TABLE_NAME_SHORT}}.PRIMARY_SHORT[0];
47
+
48
+ const postID = postResponse?.data?.created
49
+
50
+ const singleRowSelect = await {{TABLE_NAME_SHORT}}.Get({
51
+ [C6.WHERE]: {
52
+ [{{TABLE_NAME_SHORT}}[primaryKey.toUpperCase()]]: postID,
53
+ }
54
+ })
55
+
56
+ if ('function' === typeof singleRowSelect) {
57
+ throw Error('singleRowSelect is a promise, this is unexpected.');
58
+ }
59
+
60
+ console.log('singleRowSelect', singleRowSelect?.data)
61
+
62
+ // Ensure the expected response datastructure is returned
63
+ expect(singleRowSelect?.data?.rest).not.toBeUndefined();
64
+
65
+ // Make sure the previously created post is now returned
66
+ expect(typeof singleRowSelect?.data?.rest).toEqual('object');
67
+
68
+ // todo - make this work correctly with multiple primary keys
69
+ const selectedPostId = singleRowSelect?.data?.rest[0][primaryKey]
70
+
71
+ expect(selectedPostId).toEqual(postID);
72
+
73
+ const multipleRowSelect = await {{TABLE_NAME_SHORT}}.Get({
74
+ [C6.WHERE]: {
75
+ [{{TABLE_NAME_SHORT}}[primaryKey.toUpperCase()]]: [C6.IN, [0, postID]],
76
+ }
77
+ })
78
+
79
+ if ('function' === typeof multipleRowSelect) {
80
+ throw Error('singleRowSelect is a promise, this is unexpected.');
81
+ }
82
+
83
+ console.log('singleRowSelect', multipleRowSelect?.data)
84
+
85
+ // Ensure the expected response datastructure is returned
86
+ expect(multipleRowSelect?.data?.rest).not.toBeUndefined();
87
+
88
+ // Make sure the previously created post is now returned
89
+ expect(typeof multipleRowSelect?.data?.rest).toEqual('object');
90
+
91
+ testData[primaryKey] = postID
92
+
93
+ {{#each TYPE_VALIDATION}}
94
+ testData.{{@key}} = {{#TYPESCRIPT_TYPE_IS_STRING}}fillString.substring(0, {{TYPE_VALIDATION.MAX_LENGTH}}){{/TYPESCRIPT_TYPE_IS_STRING}}{{#TYPESCRIPT_TYPE_IS_NUMBER}}randomInt{{/TYPESCRIPT_TYPE_IS_NUMBER}};
95
+ {{/each}}
96
+
97
+ // wait for the global state to be updated
98
+ expect(CarbonReact.getState<iRestfulObjectArrayTypes>().{{TABLE_NAME_SHORT}}).not.toBeUndefined();
99
+
100
+ const updateResponse = await {{TABLE_NAME_SHORT}}.Put(testData)
101
+
102
+ expect(updateResponse?.data?.updated).not.toBeUndefined();
103
+
104
+ const deleteResponse = await {{TABLE_NAME_SHORT}}.Delete({
105
+ [primaryKey]: postID
106
+ })
107
+
108
+ console.log('deleteResponse', deleteResponse?.data)
109
+
110
+ expect(deleteResponse?.data?.deleted).not.toBeUndefined();
111
+
112
+ await waitFor(async () => {
113
+ expect(checkAllRequestsComplete()).toEqual(true);
114
+ }, {timeout: 10000, interval: 1000});
115
+
116
+ })
117
+
118
+ }, 100000);
119
+
120
+ })
121
+
@@ -0,0 +1,291 @@
1
+ var __values = (this && this.__values) || function(o) {
2
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
3
+ if (m) return m.call(o);
4
+ if (o && typeof o.length === "number") return {
5
+ next: function () {
6
+ if (o && i >= o.length) o = void 0;
7
+ return { value: o && o[i++], done: !o };
8
+ }
9
+ };
10
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
11
+ };
12
+ var execSync = require('child_process').execSync;
13
+ var fs = require('fs');
14
+ var path = require('path');
15
+ var Handlebars = require('handlebars');
16
+ var args = process.argv.slice(2); // Slice the first two elements
17
+ var argMap = {};
18
+ for (var i = 0; i < args.length; i += 2) {
19
+ argMap[args[i]] = args[i + 1];
20
+ }
21
+ var createDirIfNotExists = function (dir) {
22
+ return !fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined;
23
+ };
24
+ var MySQLDump = /** @class */ (function () {
25
+ function MySQLDump() {
26
+ }
27
+ MySQLDump.buildCNF = function (cnfFile) {
28
+ if (cnfFile === void 0) { cnfFile = null; }
29
+ if (this.mysqlcnf !== '') {
30
+ return this.mysqlcnf;
31
+ }
32
+ var cnf = [
33
+ '[client]',
34
+ "user = ".concat(this.DB_USER),
35
+ "password = ".concat(this.DB_PASS),
36
+ "host = ".concat(this.DB_HOST),
37
+ "port = ".concat(this.DB_PORT),
38
+ '',
39
+ ];
40
+ cnf.push("");
41
+ cnfFile !== null && cnfFile !== void 0 ? cnfFile : (cnfFile = path.join(process.cwd(), '/mysql.cnf'));
42
+ try {
43
+ fs.writeFileSync(cnfFile, cnf.join('\n'));
44
+ fs.chmodSync(cnfFile, 488);
45
+ console.log("Successfully created mysql.cnf file in (".concat(cnfFile, ")"));
46
+ }
47
+ catch (error) {
48
+ console.error("Failed to store file contents of mysql.cnf in (".concat(process.cwd(), ")"), error);
49
+ process.exit(1);
50
+ }
51
+ return (this.mysqlcnf = cnfFile);
52
+ };
53
+ MySQLDump.MySQLDump = function (mysqldump, data, schemas, outputFile, otherOption, specificTable) {
54
+ if (mysqldump === void 0) { mysqldump = null; }
55
+ if (data === void 0) { data = false; }
56
+ if (schemas === void 0) { schemas = true; }
57
+ if (outputFile === void 0) { outputFile = null; }
58
+ if (otherOption === void 0) { otherOption = ''; }
59
+ if (specificTable === void 0) { specificTable = null; }
60
+ specificTable = specificTable || '';
61
+ if (outputFile === null) {
62
+ outputFile = path.join(process.cwd(), 'mysqldump.sql');
63
+ }
64
+ if (!data && !schemas) {
65
+ console.warn("MysqlDump is running with --no-create-info and --no-data. Why?");
66
+ }
67
+ var defaultsExtraFile = this.buildCNF();
68
+ var hexBlobOption = data ? '--hex-blob ' : '--no-data ';
69
+ var createInfoOption = schemas ? '' : ' --no-create-info ';
70
+ var cmd = "".concat(mysqldump || 'mysqldump', " --defaults-extra-file=\"").concat(defaultsExtraFile, "\" ").concat(otherOption, " --skip-add-locks --single-transaction --quick ").concat(createInfoOption).concat(hexBlobOption).concat(this.DB_NAME, " ").concat(specificTable, " > '").concat(outputFile, "'");
71
+ this.executeAndCheckStatus(cmd);
72
+ return (this.mysqldump = outputFile);
73
+ };
74
+ MySQLDump.executeAndCheckStatus = function (command, exitOnFailure, output) {
75
+ if (exitOnFailure === void 0) { exitOnFailure = true; }
76
+ if (output === void 0) { output = []; }
77
+ try {
78
+ var stdout = execSync(command, { encoding: 'utf-8' });
79
+ output.push(stdout);
80
+ }
81
+ catch (error) {
82
+ console.log("The command >> ".concat(command, " \n\t returned with a status code (").concat(error.status, "). Expecting 0 for success."));
83
+ console.log("Command output::\t ".concat(error.stdout));
84
+ if (exitOnFailure) {
85
+ process.exit(error.status);
86
+ }
87
+ }
88
+ };
89
+ MySQLDump.mysqlcnf = '';
90
+ MySQLDump.mysqldump = '';
91
+ MySQLDump.DB_USER = argMap['--user'] || 'root';
92
+ MySQLDump.DB_PASS = argMap['--pass'] || 'password';
93
+ MySQLDump.DB_HOST = argMap['--host'] || '127.0.0.1';
94
+ MySQLDump.DB_PORT = argMap['--port'] || '3306';
95
+ MySQLDump.DB_NAME = argMap['--dbname'] || 'carbonPHP';
96
+ MySQLDump.DB_PREFIX = argMap['--prefix'] || 'carbon_';
97
+ MySQLDump.RELATIVE_OUTPUT_DIR = argMap['--output'] || '/src/api/rest';
98
+ MySQLDump.OUTPUT_DIR = path.join(process.cwd(), MySQLDump.RELATIVE_OUTPUT_DIR);
99
+ return MySQLDump;
100
+ }());
101
+ createDirIfNotExists(MySQLDump.OUTPUT_DIR);
102
+ var pathRuntimeReference = MySQLDump.RELATIVE_OUTPUT_DIR.replace(/(^\/(src\/)?)|(\/+$)/g, '');
103
+ // Usage example
104
+ var dumpFileLocation = MySQLDump.MySQLDump();
105
+ function capitalizeFirstLetter(string) {
106
+ return string.charAt(0).toUpperCase() + string.slice(1);
107
+ }
108
+ function determineTypeScriptType(mysqlType) {
109
+ switch (mysqlType.toLowerCase()) {
110
+ case 'varchar':
111
+ case 'text':
112
+ case 'char':
113
+ case 'datetime':
114
+ case 'timestamp':
115
+ case 'date':
116
+ return 'string';
117
+ case 'int':
118
+ case 'bigint':
119
+ case 'smallint':
120
+ case 'decimal':
121
+ case 'float':
122
+ case 'double':
123
+ return 'number';
124
+ case 'boolean':
125
+ case 'tinyint(1)':
126
+ return 'boolean';
127
+ case 'json':
128
+ return 'any'; // or 'object' based on usage
129
+ default:
130
+ return 'string';
131
+ }
132
+ }
133
+ var parseSQLToTypeScript = function (sql) {
134
+ var e_1, _a, e_2, _b;
135
+ var tableMatches = sql.matchAll(/CREATE\s+TABLE\s+`?(\w+)`?\s+\(((.|\n)+?)\)\s*(ENGINE=.+?);/gm);
136
+ var tableData = {};
137
+ var references = [];
138
+ var _loop_1 = function (tableMatch) {
139
+ var tableName = tableMatch[1];
140
+ var columnDefinitions = tableMatch[2];
141
+ var columns = {};
142
+ var columnRegex = /^\s*`(\w+)` (\w+)(?:\((\d+)\))?( NOT NULL)?( AUTO_INCREMENT)?(?: DEFAULT '(\w+)')?/g;
143
+ var columnMatch = void 0;
144
+ while ((columnMatch = columnRegex.exec(columnDefinitions))) {
145
+ columns[columnMatch[1]] = {
146
+ type: columnMatch[2],
147
+ length: columnMatch[3] || '',
148
+ notNull: !!columnMatch[4],
149
+ autoIncrement: !!columnMatch[5],
150
+ defaultValue: columnMatch[6] || '',
151
+ };
152
+ }
153
+ // Extract primary keys
154
+ var primaryKeyMatch = columnDefinitions.match(/PRIMARY KEY \(([^)]+)\)/i);
155
+ var primaryKeys = primaryKeyMatch
156
+ ? primaryKeyMatch[1].split(',').map(function (key) { return key.trim().replace(/`/g, ''); })
157
+ : [];
158
+ // Extract foreign keys
159
+ var foreignKeyRegex = /CONSTRAINT `([^`]+)` FOREIGN KEY \(`([^`]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)( ON DELETE (\w+))?( ON UPDATE (\w+))?/g;
160
+ var foreignKeyMatch = void 0;
161
+ while ((foreignKeyMatch = foreignKeyRegex.exec(columnDefinitions))) {
162
+ var constraintName = foreignKeyMatch[1];
163
+ var localColumn = foreignKeyMatch[2];
164
+ var foreignTable = foreignKeyMatch[3];
165
+ var foreignColumn = foreignKeyMatch[4];
166
+ var onDeleteAction = foreignKeyMatch[6] || null;
167
+ var onUpdateAction = foreignKeyMatch[8] || null;
168
+ references.push({
169
+ TABLE: tableName,
170
+ CONSTRAINT: constraintName,
171
+ FOREIGN_KEY: localColumn,
172
+ REFERENCES: "".concat(foreignTable, ".").concat(foreignColumn),
173
+ ON_DELETE: onDeleteAction,
174
+ ON_UPDATE: onUpdateAction
175
+ });
176
+ }
177
+ var tsModel = {
178
+ RELATIVE_OUTPUT_DIR: pathRuntimeReference,
179
+ TABLE_NAME: tableName,
180
+ TABLE_DEFINITION: tableMatch[0],
181
+ TABLE_CONSTRAINT: references,
182
+ TABLE_NAME_SHORT: tableName.replace(MySQLDump.DB_PREFIX, ''),
183
+ TABLE_NAME_LOWER: tableName.toLowerCase(),
184
+ TABLE_NAME_UPPER: tableName.toUpperCase(),
185
+ TABLE_NAME_PASCAL_CASE: tableName.split('_').map(capitalizeFirstLetter).join('_'),
186
+ TABLE_NAME_SHORT_PASCAL_CASE: tableName.replace(MySQLDump.DB_PREFIX, '').split('_').map(capitalizeFirstLetter).join('_'),
187
+ PRIMARY: primaryKeys.map(function (pk) { return "".concat(tableName, ".").concat(pk); }),
188
+ PRIMARY_SHORT: primaryKeys,
189
+ COLUMNS: {},
190
+ COLUMNS_UPPERCASE: {},
191
+ TYPE_VALIDATION: {},
192
+ REGEX_VALIDATION: {},
193
+ TABLE_REFERENCES: {},
194
+ TABLE_REFERENCED_BY: {},
195
+ };
196
+ for (var colName in columns) {
197
+ tsModel.COLUMNS["".concat(tableName, ".").concat(colName)] = colName;
198
+ tsModel.COLUMNS_UPPERCASE[colName.toUpperCase()] = tableName + '.' + colName;
199
+ var typescript_type = determineTypeScriptType(columns[colName].type.toLowerCase()) === "number" ? "number" : "string";
200
+ tsModel.TYPE_VALIDATION["".concat(tableName, ".").concat(colName)] = {
201
+ COLUMN_NAME: colName,
202
+ MYSQL_TYPE: columns[colName].type.toLowerCase(),
203
+ TYPESCRIPT_TYPE: typescript_type,
204
+ TYPESCRIPT_TYPE_IS_STRING: 'string' === typescript_type,
205
+ TYPESCRIPT_TYPE_IS_NUMBER: 'number' === typescript_type,
206
+ MAX_LENGTH: columns[colName].length,
207
+ AUTO_INCREMENT: columns[colName].autoIncrement,
208
+ SKIP_COLUMN_IN_POST: !columns[colName].notNull && !columns[colName].defaultValue,
209
+ };
210
+ }
211
+ tableData[tableName] = tsModel;
212
+ };
213
+ try {
214
+ // @ts-ignore
215
+ for (var tableMatches_1 = __values(tableMatches), tableMatches_1_1 = tableMatches_1.next(); !tableMatches_1_1.done; tableMatches_1_1 = tableMatches_1.next()) {
216
+ var tableMatch = tableMatches_1_1.value;
217
+ _loop_1(tableMatch);
218
+ }
219
+ }
220
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
221
+ finally {
222
+ try {
223
+ if (tableMatches_1_1 && !tableMatches_1_1.done && (_a = tableMatches_1.return)) _a.call(tableMatches_1);
224
+ }
225
+ finally { if (e_1) throw e_1.error; }
226
+ }
227
+ try {
228
+ for (var references_1 = __values(references), references_1_1 = references_1.next(); !references_1_1.done; references_1_1 = references_1.next()) {
229
+ var ref = references_1_1.value;
230
+ var foreignTable = ref.REFERENCES.split('.')[0];
231
+ var foreignColumn = ref.REFERENCES.split('.')[1];
232
+ var tableName = ref.TABLE;
233
+ var columnName = ref.FOREIGN_KEY;
234
+ var constraintName = ref.CONSTRAINT;
235
+ if (!tableData[foreignTable]) {
236
+ console.log("Foreign table ".concat(foreignTable, " not found for ").concat(ref.TABLE, ".").concat(ref.CONSTRAINT));
237
+ continue;
238
+ }
239
+ if (!tableData[foreignTable].TABLE_REFERENCED_BY) {
240
+ tableData[foreignTable].TABLE_REFERENCED_BY = {};
241
+ }
242
+ if (!tableData[foreignTable].TABLE_REFERENCED_BY[foreignColumn]) {
243
+ tableData[foreignTable].TABLE_REFERENCED_BY[foreignColumn] = [];
244
+ }
245
+ tableData[foreignTable].TABLE_REFERENCED_BY[foreignColumn].push({
246
+ TABLE: tableName,
247
+ COLUMN: columnName,
248
+ CONSTRAINT: constraintName
249
+ });
250
+ if (!tableData[tableName].TABLE_REFERENCES) {
251
+ tableData[tableName].TABLE_REFERENCES = {};
252
+ }
253
+ if (!tableData[tableName].TABLE_REFERENCES[columnName]) {
254
+ tableData[tableName].TABLE_REFERENCES[columnName] = [];
255
+ }
256
+ tableData[tableName].TABLE_REFERENCES[columnName].push({
257
+ TABLE: foreignTable,
258
+ COLUMN: foreignColumn,
259
+ CONSTRAINT: constraintName
260
+ });
261
+ }
262
+ }
263
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
264
+ finally {
265
+ try {
266
+ if (references_1_1 && !references_1_1.done && (_b = references_1.return)) _b.call(references_1);
267
+ }
268
+ finally { if (e_2) throw e_2.error; }
269
+ }
270
+ var tables = Object.values(tableData);
271
+ return {
272
+ TABLES: tables,
273
+ RestTableNames: tables.map(function (table) { return "'" + table.TABLE_NAME + "'"; }).join('\n | '),
274
+ RestShortTableNames: tables.map(function (table) { return "'" + table.TABLE_NAME_SHORT + "'"; }).join('\n | '),
275
+ RestTableInterfaces: tables.map(function (table) { return 'i' + table.TABLE_NAME_SHORT_PASCAL_CASE; }).join('\n | '),
276
+ };
277
+ };
278
+ // use dumpFileLocation to get sql
279
+ var sql = fs.readFileSync(dumpFileLocation, 'utf-8');
280
+ var tableData = parseSQLToTypeScript(sql);
281
+ // write to file
282
+ fs.writeFileSync(path.join(process.cwd(), 'C6MySqlDump.json'), JSON.stringify(tableData));
283
+ // import this file src/assets/handlebars/C6.tsx.handlebars for a mustache template
284
+ var template = fs.readFileSync(path.resolve(__dirname, 'assets/handlebars/C6.tsx.handlebars'), 'utf-8');
285
+ fs.writeFileSync(path.join(MySQLDump.OUTPUT_DIR, 'C6.tsx'), Handlebars.compile(template)(tableData));
286
+ var testTemplate = fs.readFileSync(path.resolve(__dirname, 'assets/handlebars/Tests.tsx.handlebars'), 'utf-8');
287
+ Object.values(tableData.TABLES).map(function (tableData, key) {
288
+ var tableName = tableData.TABLE_NAME_SHORT;
289
+ fs.writeFileSync(path.join(MySQLDump.OUTPUT_DIR, tableName + '.tsx'), Handlebars.compile(testTemplate)(tableData));
290
+ });
291
+ console.log('Successfully created CarbonORM bindings!');
@@ -0,0 +1,383 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {execSync} = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const Handlebars = require('handlebars');
7
+
8
+
9
+ const args = process.argv.slice(2); // Slice the first two elements
10
+ const argMap = {};
11
+
12
+ for (let i = 0; i < args.length; i += 2) {
13
+ argMap[args[i]] = args[i + 1];
14
+ }
15
+
16
+ const createDirIfNotExists = dir =>
17
+ !fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined;
18
+
19
+ class MySQLDump {
20
+
21
+ static mysqlcnf: string = '';
22
+ static mysqldump: string = '';
23
+ static DB_USER = argMap['--user'] || 'root';
24
+ static DB_PASS = argMap['--pass'] || 'password';
25
+ static DB_HOST = argMap['--host'] || '127.0.0.1';
26
+ static DB_PORT = argMap['--port'] || '3306';
27
+ static DB_NAME = argMap['--dbname'] || 'carbonPHP';
28
+ static DB_PREFIX = argMap['--prefix'] || 'carbon_';
29
+ static RELATIVE_OUTPUT_DIR = argMap['--output'] || '/src/api/rest';
30
+ static OUTPUT_DIR = path.join(process.cwd(), MySQLDump.RELATIVE_OUTPUT_DIR);
31
+
32
+ static buildCNF(cnfFile = null) {
33
+
34
+ if (this.mysqlcnf !== '') {
35
+
36
+ return this.mysqlcnf;
37
+
38
+ }
39
+
40
+ const cnf = [
41
+ '[client]',
42
+ `user = ${this.DB_USER}`,
43
+ `password = ${this.DB_PASS}`,
44
+ `host = ${this.DB_HOST}`,
45
+ `port = ${this.DB_PORT}`,
46
+ '',
47
+ ];
48
+
49
+
50
+ cnf.push(``);
51
+
52
+ cnfFile ??= path.join(process.cwd(), '/mysql.cnf');
53
+
54
+ try {
55
+
56
+ fs.writeFileSync(cnfFile, cnf.join('\n'));
57
+
58
+ fs.chmodSync(cnfFile, 0o750);
59
+
60
+ console.log(`Successfully created mysql.cnf file in (${cnfFile})`);
61
+
62
+ } catch (error) {
63
+
64
+ console.error(`Failed to store file contents of mysql.cnf in (${process.cwd()})`, error);
65
+
66
+ process.exit(1);
67
+
68
+ }
69
+
70
+ return (this.mysqlcnf = cnfFile);
71
+
72
+ }
73
+
74
+ static MySQLDump(mysqldump = null, data = false, schemas = true, outputFile = null, otherOption = '', specificTable = null) {
75
+ specificTable = specificTable || '';
76
+
77
+ if (outputFile === null) {
78
+ outputFile = path.join(process.cwd(), 'mysqldump.sql');
79
+ }
80
+
81
+ if (!data && !schemas) {
82
+ console.warn("MysqlDump is running with --no-create-info and --no-data. Why?");
83
+ }
84
+
85
+ const defaultsExtraFile = this.buildCNF();
86
+
87
+ const hexBlobOption = data ? '--hex-blob ' : '--no-data ';
88
+
89
+ const createInfoOption = schemas ? '' : ' --no-create-info ';
90
+
91
+ const cmd = `${mysqldump || 'mysqldump'} --defaults-extra-file="${defaultsExtraFile}" ${otherOption} --skip-add-locks --single-transaction --quick ${createInfoOption}${hexBlobOption}${this.DB_NAME} ${specificTable} > '${outputFile}'`;
92
+
93
+ this.executeAndCheckStatus(cmd);
94
+
95
+ return (this.mysqldump = outputFile);
96
+
97
+ }
98
+
99
+ static executeAndCheckStatus(command, exitOnFailure = true, output = []) {
100
+
101
+ try {
102
+
103
+ const stdout = execSync(command, {encoding: 'utf-8'});
104
+
105
+ output.push(stdout);
106
+
107
+ } catch (error) {
108
+
109
+ console.log(`The command >> ${command} \n\t returned with a status code (${error.status}). Expecting 0 for success.`);
110
+
111
+ console.log(`Command output::\t ${error.stdout}`);
112
+
113
+ if (exitOnFailure) {
114
+
115
+ process.exit(error.status);
116
+
117
+ }
118
+
119
+ }
120
+
121
+ }
122
+
123
+ }
124
+
125
+ createDirIfNotExists(MySQLDump.OUTPUT_DIR)
126
+
127
+ const pathRuntimeReference = MySQLDump.RELATIVE_OUTPUT_DIR.replace(/(^\/(src\/)?)|(\/+$)/g, '')
128
+
129
+ // Usage example
130
+ const dumpFileLocation = MySQLDump.MySQLDump();
131
+
132
+ type ColumnInfo = {
133
+ type: string;
134
+ length?: string;
135
+ autoIncrement: boolean;
136
+ notNull: boolean;
137
+ defaultValue?: string;
138
+ };
139
+
140
+ type foreignKeyInfo = {
141
+ TABLE: string,
142
+ CONSTRAINT: string,
143
+ FOREIGN_KEY: string,
144
+ REFERENCES: string,
145
+ ON_DELETE: string,
146
+ ON_UPDATE: string
147
+ }
148
+
149
+ function capitalizeFirstLetter(string) {
150
+ return string.charAt(0).toUpperCase() + string.slice(1);
151
+ }
152
+
153
+ function determineTypeScriptType(mysqlType) {
154
+ switch (mysqlType.toLowerCase()) {
155
+ case 'varchar':
156
+ case 'text':
157
+ case 'char':
158
+ case 'datetime':
159
+ case 'timestamp':
160
+ case 'date':
161
+ return 'string';
162
+ case 'int':
163
+ case 'bigint':
164
+ case 'smallint':
165
+ case 'decimal':
166
+ case 'float':
167
+ case 'double':
168
+ return 'number';
169
+ case 'boolean':
170
+ case 'tinyint(1)':
171
+ return 'boolean';
172
+ case 'json':
173
+ return 'any'; // or 'object' based on usage
174
+ default:
175
+ return 'string';
176
+ }
177
+ }
178
+
179
+
180
+ const parseSQLToTypeScript = (sql: string) => {
181
+
182
+ const tableMatches = sql.matchAll(/CREATE\s+TABLE\s+`?(\w+)`?\s+\(((.|\n)+?)\)\s*(ENGINE=.+?);/gm);
183
+
184
+ let tableData: {
185
+ [TableName: string]: {
186
+ RELATIVE_OUTPUT_DIR: string,
187
+ TABLE_NAME: string,
188
+ TABLE_DEFINITION: string,
189
+ TABLE_CONSTRAINT: {},
190
+ TABLE_NAME_SHORT: string,
191
+ TABLE_NAME_LOWER: string,
192
+ TABLE_NAME_UPPER: string,
193
+ TABLE_NAME_PASCAL_CASE: string,
194
+ TABLE_NAME_SHORT_PASCAL_CASE: string,
195
+ TABLE_REFERENCED_BY?: {},
196
+ TABLE_REFERENCES?: {},
197
+ PRIMARY: string[],
198
+ PRIMARY_SHORT: string[],
199
+ COLUMNS: {},
200
+ COLUMNS_UPPERCASE: {},
201
+ TYPE_VALIDATION: {},
202
+ REGEX_VALIDATION: {},
203
+ }
204
+ } = {};
205
+
206
+ let references: foreignKeyInfo[] = [];
207
+
208
+ // @ts-ignore
209
+ for (const tableMatch of tableMatches) {
210
+
211
+ const tableName = tableMatch[1];
212
+ const columnDefinitions = tableMatch[2];
213
+
214
+ let columns: any = {};
215
+ const columnRegex: RegExp = /^\s*`(\w+)` (\w+)(?:\((\d+)\))?( NOT NULL)?( AUTO_INCREMENT)?(?: DEFAULT '(\w+)')?/g;
216
+ let columnMatch: RegExpExecArray | null;
217
+
218
+ while ((columnMatch = columnRegex.exec(columnDefinitions))) {
219
+ columns[columnMatch[1]] = {
220
+ type: columnMatch[2],
221
+ length: columnMatch[3] || '',
222
+ notNull: !!columnMatch[4],
223
+ autoIncrement: !!columnMatch[5],
224
+ defaultValue: columnMatch[6] || '',
225
+ };
226
+ }
227
+
228
+ // Extract primary keys
229
+ const primaryKeyMatch = columnDefinitions.match(/PRIMARY KEY \(([^)]+)\)/i);
230
+ const primaryKeys = primaryKeyMatch
231
+ ? primaryKeyMatch[1].split(',').map(key => key.trim().replace(/`/g, ''))
232
+ : [];
233
+
234
+ // Extract foreign keys
235
+ const foreignKeyRegex: RegExp = /CONSTRAINT `([^`]+)` FOREIGN KEY \(`([^`]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)( ON DELETE (\w+))?( ON UPDATE (\w+))?/g;
236
+ let foreignKeyMatch: RegExpExecArray | null;
237
+
238
+ while ((foreignKeyMatch = foreignKeyRegex.exec(columnDefinitions))) {
239
+ const constraintName = foreignKeyMatch[1];
240
+ const localColumn = foreignKeyMatch[2];
241
+ const foreignTable = foreignKeyMatch[3];
242
+ const foreignColumn = foreignKeyMatch[4];
243
+ const onDeleteAction = foreignKeyMatch[6] || null;
244
+ const onUpdateAction = foreignKeyMatch[8] || null;
245
+
246
+ references.push({
247
+ TABLE: tableName,
248
+ CONSTRAINT: constraintName,
249
+ FOREIGN_KEY: localColumn,
250
+ REFERENCES: `${foreignTable}.${foreignColumn}`,
251
+ ON_DELETE: onDeleteAction,
252
+ ON_UPDATE: onUpdateAction
253
+ });
254
+
255
+ }
256
+
257
+ const tsModel = {
258
+ RELATIVE_OUTPUT_DIR: pathRuntimeReference,
259
+ TABLE_NAME: tableName,
260
+ TABLE_DEFINITION: tableMatch[0],
261
+ TABLE_CONSTRAINT: references,
262
+ TABLE_NAME_SHORT: tableName.replace(MySQLDump.DB_PREFIX, ''),
263
+ TABLE_NAME_LOWER: tableName.toLowerCase(),
264
+ TABLE_NAME_UPPER: tableName.toUpperCase(),
265
+ TABLE_NAME_PASCAL_CASE: tableName.split('_').map(capitalizeFirstLetter).join('_'),
266
+ TABLE_NAME_SHORT_PASCAL_CASE: tableName.replace(MySQLDump.DB_PREFIX, '').split('_').map(capitalizeFirstLetter).join('_'),
267
+ PRIMARY: primaryKeys.map(pk => `${tableName}.${pk}`),
268
+ PRIMARY_SHORT: primaryKeys,
269
+ COLUMNS: {},
270
+ COLUMNS_UPPERCASE: {},
271
+ TYPE_VALIDATION: {},
272
+ REGEX_VALIDATION: {},
273
+ TABLE_REFERENCES: {},
274
+ TABLE_REFERENCED_BY:{},
275
+ };
276
+
277
+ for (const colName in columns) {
278
+
279
+ tsModel.COLUMNS[`${tableName}.${colName}`] = colName;
280
+
281
+ tsModel.COLUMNS_UPPERCASE[colName.toUpperCase()] = tableName + '.' + colName;
282
+
283
+ const typescript_type = determineTypeScriptType(columns[colName].type.toLowerCase()) === "number" ? "number" : "string"
284
+
285
+ tsModel.TYPE_VALIDATION[`${tableName}.${colName}`] = {
286
+ COLUMN_NAME: colName,
287
+ MYSQL_TYPE: columns[colName].type.toLowerCase(),
288
+ TYPESCRIPT_TYPE: typescript_type,
289
+ TYPESCRIPT_TYPE_IS_STRING: 'string' === typescript_type,
290
+ TYPESCRIPT_TYPE_IS_NUMBER: 'number' === typescript_type,
291
+ MAX_LENGTH: columns[colName].length,
292
+ AUTO_INCREMENT: columns[colName].autoIncrement,
293
+ SKIP_COLUMN_IN_POST: !columns[colName].notNull && !columns[colName].defaultValue,
294
+ };
295
+
296
+ }
297
+
298
+ tableData[tableName] = tsModel;
299
+
300
+ }
301
+
302
+ for (const ref of references) {
303
+
304
+ const foreignTable = ref.REFERENCES.split('.')[0];
305
+ const foreignColumn = ref.REFERENCES.split('.')[1];
306
+ const tableName = ref.TABLE;
307
+ const columnName = ref.FOREIGN_KEY;
308
+ const constraintName = ref.CONSTRAINT;
309
+
310
+ if (!tableData[foreignTable]) {
311
+ console.log(`Foreign table ${foreignTable} not found for ${ref.TABLE}.${ref.CONSTRAINT}`);
312
+ continue;
313
+ }
314
+
315
+ if (!tableData[foreignTable].TABLE_REFERENCED_BY) {
316
+ tableData[foreignTable].TABLE_REFERENCED_BY = {};
317
+ }
318
+
319
+ if (!tableData[foreignTable].TABLE_REFERENCED_BY[foreignColumn]) {
320
+ tableData[foreignTable].TABLE_REFERENCED_BY[foreignColumn] = [];
321
+ }
322
+
323
+ tableData[foreignTable].TABLE_REFERENCED_BY[foreignColumn].push({
324
+ TABLE: tableName,
325
+ COLUMN: columnName,
326
+ CONSTRAINT: constraintName
327
+ });
328
+
329
+ if (!tableData[tableName].TABLE_REFERENCES) {
330
+ tableData[tableName].TABLE_REFERENCES = {};
331
+ }
332
+
333
+ if (!tableData[tableName].TABLE_REFERENCES[columnName]) {
334
+ tableData[tableName].TABLE_REFERENCES[columnName] = [];
335
+ }
336
+
337
+ tableData[tableName].TABLE_REFERENCES[columnName].push({
338
+ TABLE: foreignTable,
339
+ COLUMN: foreignColumn,
340
+ CONSTRAINT: constraintName
341
+ });
342
+
343
+ }
344
+
345
+ const tables = Object.values(tableData);
346
+
347
+
348
+ return {
349
+ TABLES: tables,
350
+ RestTableNames: tables.map(table => "'" + table.TABLE_NAME + "'").join('\n | '),
351
+ RestShortTableNames: tables.map(table => "'" + table.TABLE_NAME_SHORT + "'").join('\n | '),
352
+ RestTableInterfaces: tables.map(table => 'i' + table.TABLE_NAME_SHORT_PASCAL_CASE).join('\n | '),
353
+ };
354
+ };
355
+
356
+
357
+ // use dumpFileLocation to get sql
358
+ const sql = fs.readFileSync(dumpFileLocation, 'utf-8');
359
+
360
+ const tableData = parseSQLToTypeScript(sql);
361
+
362
+ // write to file
363
+ fs.writeFileSync(path.join(process.cwd(), 'C6MySqlDump.json'), JSON.stringify(tableData));
364
+
365
+
366
+ // import this file src/assets/handlebars/C6.tsx.handlebars for a mustache template
367
+
368
+ const template = fs.readFileSync(path.resolve(__dirname, 'assets/handlebars/C6.tsx.handlebars'), 'utf-8');
369
+
370
+ fs.writeFileSync(path.join(MySQLDump.OUTPUT_DIR, 'C6.tsx'), Handlebars.compile(template)(tableData));
371
+
372
+ const testTemplate = fs.readFileSync(path.resolve(__dirname, 'assets/handlebars/Tests.tsx.handlebars'), 'utf-8');
373
+
374
+ Object.values(tableData.TABLES).map((tableData, key) => {
375
+
376
+ const tableName = tableData.TABLE_NAME_SHORT
377
+
378
+ fs.writeFileSync(path.join(MySQLDump.OUTPUT_DIR, tableName + '.tsx'), Handlebars.compile(testTemplate)(tableData));
379
+
380
+ })
381
+
382
+ console.log('Successfully created CarbonORM bindings!')
383
+
@@ -22,6 +22,12 @@ export interface iTypeValidation {
22
22
  SKIP_COLUMN_IN_POST: boolean
23
23
  }
24
24
 
25
+ export interface iConstraint {
26
+ TABLE: string,
27
+ COLUMN: string,
28
+ CONSTRAINT: string
29
+ }
30
+
25
31
  export interface C6RestfulModel<RestShortTableNames extends string = string> {
26
32
  TABLE_NAME: RestShortTableNames,
27
33
  PRIMARY: string[],
@@ -29,4 +35,6 @@ export interface C6RestfulModel<RestShortTableNames extends string = string> {
29
35
  COLUMNS: stringMap,
30
36
  REGEX_VALIDATION: RegExpMap,
31
37
  TYPE_VALIDATION: {[key: string]: iTypeValidation},
38
+ TABLE_REFERENCES: {[columnName: string]: iConstraint[]},
39
+ TABLE_REFERENCED_BY: {[columnName: string]: iConstraint[]}
32
40
  }