@currentjs/gen 0.3.2 → 0.5.0

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 (69) hide show
  1. package/CHANGELOG.md +10 -611
  2. package/README.md +623 -427
  3. package/dist/cli.js +2 -1
  4. package/dist/commands/commit.js +25 -42
  5. package/dist/commands/createApp.js +1 -0
  6. package/dist/commands/createModule.js +151 -45
  7. package/dist/commands/diff.js +27 -40
  8. package/dist/commands/generateAll.js +141 -291
  9. package/dist/commands/migrateCommit.js +6 -18
  10. package/dist/commands/migratePush.d.ts +1 -0
  11. package/dist/commands/migratePush.js +135 -0
  12. package/dist/commands/migrateUpdate.d.ts +1 -0
  13. package/dist/commands/migrateUpdate.js +147 -0
  14. package/dist/commands/newGenerateAll.d.ts +4 -0
  15. package/dist/commands/newGenerateAll.js +336 -0
  16. package/dist/generators/controllerGenerator.d.ts +43 -19
  17. package/dist/generators/controllerGenerator.js +547 -329
  18. package/dist/generators/domainLayerGenerator.d.ts +21 -0
  19. package/dist/generators/domainLayerGenerator.js +276 -0
  20. package/dist/generators/dtoGenerator.d.ts +21 -0
  21. package/dist/generators/dtoGenerator.js +518 -0
  22. package/dist/generators/newControllerGenerator.d.ts +55 -0
  23. package/dist/generators/newControllerGenerator.js +644 -0
  24. package/dist/generators/newServiceGenerator.d.ts +19 -0
  25. package/dist/generators/newServiceGenerator.js +266 -0
  26. package/dist/generators/newStoreGenerator.d.ts +39 -0
  27. package/dist/generators/newStoreGenerator.js +408 -0
  28. package/dist/generators/newTemplateGenerator.d.ts +29 -0
  29. package/dist/generators/newTemplateGenerator.js +510 -0
  30. package/dist/generators/serviceGenerator.d.ts +16 -51
  31. package/dist/generators/serviceGenerator.js +167 -586
  32. package/dist/generators/storeGenerator.d.ts +35 -32
  33. package/dist/generators/storeGenerator.js +291 -238
  34. package/dist/generators/storeGeneratorV2.d.ts +31 -0
  35. package/dist/generators/storeGeneratorV2.js +190 -0
  36. package/dist/generators/templateGenerator.d.ts +21 -21
  37. package/dist/generators/templateGenerator.js +393 -268
  38. package/dist/generators/templates/appTemplates.d.ts +3 -1
  39. package/dist/generators/templates/appTemplates.js +15 -10
  40. package/dist/generators/templates/data/appYamlTemplate +5 -2
  41. package/dist/generators/templates/data/cursorRulesTemplate +315 -221
  42. package/dist/generators/templates/data/frontendScriptTemplate +45 -11
  43. package/dist/generators/templates/data/mainViewTemplate +1 -1
  44. package/dist/generators/templates/data/systemTsTemplate +5 -0
  45. package/dist/generators/templates/index.d.ts +0 -3
  46. package/dist/generators/templates/index.js +0 -3
  47. package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
  48. package/dist/generators/templates/newStoreTemplates.js +141 -0
  49. package/dist/generators/templates/storeTemplates.d.ts +1 -5
  50. package/dist/generators/templates/storeTemplates.js +102 -219
  51. package/dist/generators/templates/viewTemplates.js +1 -1
  52. package/dist/generators/useCaseGenerator.d.ts +13 -0
  53. package/dist/generators/useCaseGenerator.js +188 -0
  54. package/dist/types/configTypes.d.ts +148 -0
  55. package/dist/types/configTypes.js +10 -0
  56. package/dist/utils/childEntityUtils.d.ts +18 -0
  57. package/dist/utils/childEntityUtils.js +78 -0
  58. package/dist/utils/commandUtils.d.ts +43 -0
  59. package/dist/utils/commandUtils.js +124 -0
  60. package/dist/utils/commitUtils.d.ts +4 -1
  61. package/dist/utils/constants.d.ts +10 -0
  62. package/dist/utils/constants.js +13 -1
  63. package/dist/utils/diResolver.d.ts +32 -0
  64. package/dist/utils/diResolver.js +204 -0
  65. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  66. package/dist/utils/new_parts_of_migrationUtils.js +164 -0
  67. package/dist/utils/typeUtils.d.ts +19 -0
  68. package/dist/utils/typeUtils.js +70 -0
  69. package/package.json +7 -3
@@ -414,7 +414,7 @@ window.AppConfig = {
414
414
  * @returns {any} Converted value
415
415
  */
416
416
  function convertFieldValue(value, fieldType) {
417
- if (!value || value === '') {
417
+ if (value === undefined || value === null || value === '') {
418
418
  return null;
419
419
  }
420
420
 
@@ -422,6 +422,7 @@ window.AppConfig = {
422
422
  case 'number':
423
423
  case 'int':
424
424
  case 'integer':
425
+ case 'id':
425
426
  const intVal = parseInt(value, 10);
426
427
  return isNaN(intVal) ? null : intVal;
427
428
 
@@ -432,10 +433,15 @@ window.AppConfig = {
432
433
 
433
434
  case 'boolean':
434
435
  case 'bool':
435
- if (value === 'true') return true;
436
- if (value === 'false') return false;
436
+ if (value === 'true' || value === 'on' || value === '1') return true;
437
+ if (value === 'false' || value === 'off' || value === '0') return false;
437
438
  return Boolean(value);
439
+
440
+ case 'json':
441
+ try { return JSON.parse(value); } catch { return value; }
438
442
 
443
+ case 'datetime':
444
+ case 'date':
439
445
  case 'enum':
440
446
  case 'string':
441
447
  case 'text':
@@ -474,6 +480,30 @@ window.AppConfig = {
474
480
  jsonData[key] = convertedValue;
475
481
  }
476
482
  }
483
+
484
+ // Unchecked checkboxes are not included in FormData — default to false
485
+ for (const [fieldName, fieldType] of Object.entries(fieldTypes)) {
486
+ const ft = fieldType.toLowerCase();
487
+ if ((ft === 'boolean' || ft === 'bool') && !(fieldName in jsonData)) {
488
+ jsonData[fieldName] = false;
489
+ }
490
+ }
491
+
492
+ // Unflatten dot-notation keys (e.g., "amount.currency") into nested objects
493
+ const finalData = {};
494
+ for (const [key, value] of Object.entries(jsonData)) {
495
+ if (key.includes('.')) {
496
+ const parts = key.split('.');
497
+ let current = finalData;
498
+ for (let i = 0; i < parts.length - 1; i++) {
499
+ if (!(parts[i] in current)) current[parts[i]] = {};
500
+ current = current[parts[i]];
501
+ }
502
+ current[parts[parts.length - 1]] = value;
503
+ } else {
504
+ finalData[key] = value;
505
+ }
506
+ }
477
507
 
478
508
  const url = form.getAttribute('data-action') || form.action;
479
509
  const method = (form.getAttribute('data-method') || form.method || 'POST').toUpperCase();
@@ -486,16 +516,20 @@ window.AppConfig = {
486
516
  'Content-Type': 'application/json',
487
517
  'Accept': 'application/json'
488
518
  }),
489
- body: JSON.stringify(jsonData)
519
+ body: JSON.stringify(finalData)
490
520
  })
491
- .then(response => {
492
- if (!response.ok) {
493
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
521
+ .then(response =>
522
+ response.json()
523
+ .catch(() => ({}))
524
+ .then(body => ({ ok: response.ok, status: response.status, statusText: response.statusText, body }))
525
+ )
526
+ .then(({ ok, status, statusText, body }) => {
527
+ if (!ok) {
528
+ const message = body.message || body.error || `HTTP ${status}: ${statusText}`;
529
+ handleFormError({ message }, options);
530
+ return;
494
531
  }
495
- return response.json();
496
- })
497
- .then(data => {
498
- handleFormSuccess(data, strategy, options);
532
+ handleFormSuccess(body, strategy, options);
499
533
  })
500
534
  .catch(error => {
501
535
  console.error('Form submission failed:', error);
@@ -10,7 +10,7 @@
10
10
  </head>
11
11
  <body>
12
12
  <div class="container-fluid">
13
- <div id="main">{{ content }}</div>
13
+ <div id="main">{{{ content }}}</div>
14
14
  </div>
15
15
  </body>
16
16
  </html>
@@ -0,0 +1,5 @@
1
+ export function Injectable() {
2
+ return function (target: any) {
3
+ target.__injectable = true;
4
+ };
5
+ }
@@ -1,5 +1,2 @@
1
- export * from './serviceTemplates';
2
- export * from './controllerTemplates';
3
1
  export * from './storeTemplates';
4
- export * from './validationTemplates';
5
2
  export * from './appTemplates';
@@ -14,8 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./serviceTemplates"), exports);
18
- __exportStar(require("./controllerTemplates"), exports);
19
17
  __exportStar(require("./storeTemplates"), exports);
20
- __exportStar(require("./validationTemplates"), exports);
21
18
  __exportStar(require("./appTemplates"), exports);
@@ -0,0 +1,5 @@
1
+ export declare const newStoreTemplates: {
2
+ rowInterface: string;
3
+ storeClass: string;
4
+ };
5
+ export declare const newStoreFileTemplate = "import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';\nimport type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}\n\n{{ROW_INTERFACE}}\n\n{{STORE_CLASS}}\n";
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.newStoreFileTemplate = exports.newStoreTemplates = void 0;
4
+ exports.newStoreTemplates = {
5
+ rowInterface: `export interface {{ENTITY_NAME}}Row {
6
+ id: number;
7
+ {{ROW_FIELDS}}
8
+ created_at: string;
9
+ updated_at: string;
10
+ deleted_at?: string;
11
+ }`,
12
+ storeClass: `/**
13
+ * Data access layer for {{ENTITY_NAME}}
14
+ */
15
+ export class {{ENTITY_NAME}}Store {
16
+ private tableName = '{{TABLE_NAME}}';
17
+
18
+ constructor(private db: ISqlProvider) {}
19
+
20
+ private toMySQLDatetime(date: Date): string {
21
+ return date.toISOString().slice(0, 19).replace('T', ' ');
22
+ }
23
+
24
+ private rowToModel(row: {{ENTITY_NAME}}Row): {{ENTITY_NAME}} {
25
+ return new {{ENTITY_NAME}}(
26
+ row.id,
27
+ {{ROW_TO_MODEL_MAPPING}}
28
+ );
29
+ }
30
+
31
+ async getAll(page: number = 1, limit: number = 20): Promise<{{ENTITY_NAME}}[]> {
32
+ const offset = (page - 1) * limit;
33
+ const result = await this.db.query(
34
+ \`SELECT {{FIELD_NAMES}} FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL LIMIT :limit OFFSET :offset\`,
35
+ { limit: String(limit), offset: String(offset) }
36
+ );
37
+
38
+ if (result.success && result.data) {
39
+ return result.data.map((row: {{ENTITY_NAME}}Row) => this.rowToModel(row));
40
+ }
41
+ return [];
42
+ }
43
+
44
+ async count(): Promise<number> {
45
+ const result = await this.db.query(
46
+ \`SELECT COUNT(*) as count FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL\`,
47
+ {}
48
+ );
49
+
50
+ if (result.success && result.data && result.data.length > 0) {
51
+ return parseInt(result.data[0].count, 10);
52
+ }
53
+ return 0;
54
+ }
55
+
56
+ async getById(id: number): Promise<{{ENTITY_NAME}} | null> {
57
+ const result = await this.db.query(
58
+ \`SELECT {{FIELD_NAMES}} FROM \\\`\${this.tableName}\\\` WHERE id = :id AND deleted_at IS NULL\`,
59
+ { id }
60
+ );
61
+
62
+ if (result.success && result.data && result.data.length > 0) {
63
+ return this.rowToModel(result.data[0] as {{ENTITY_NAME}}Row);
64
+ }
65
+ return null;
66
+ }
67
+
68
+ async insert(entity: {{ENTITY_NAME}}): Promise<{{ENTITY_NAME}}> {
69
+ const now = new Date();
70
+ const data: Partial<{{ENTITY_NAME}}Row> = {
71
+ {{INSERT_DATA_MAPPING}},
72
+ created_at: this.toMySQLDatetime(now),
73
+ updated_at: this.toMySQLDatetime(now)
74
+ };
75
+
76
+ const fieldsList = Object.keys(data).map(f => \`\\\`\${f}\\\`\`).join(', ');
77
+ const placeholders = Object.keys(data).map(f => \`:\${f}\`).join(', ');
78
+
79
+ const result = await this.db.query(
80
+ \`INSERT INTO \\\`\${this.tableName}\\\` (\${fieldsList}) VALUES (\${placeholders})\`,
81
+ data
82
+ );
83
+
84
+ if (result.success && result.insertId) {
85
+ const newId = typeof result.insertId === 'string' ? parseInt(result.insertId, 10) : result.insertId;
86
+ return this.getById(newId) as Promise<{{ENTITY_NAME}}>;
87
+ }
88
+
89
+ throw new Error('Failed to insert {{ENTITY_NAME}}');
90
+ }
91
+
92
+ async update(id: number, entity: {{ENTITY_NAME}}): Promise<{{ENTITY_NAME}}> {
93
+ const now = new Date();
94
+ const data: Partial<{{ENTITY_NAME}}Row> & { id: number } = {
95
+ {{UPDATE_DATA_MAPPING}},
96
+ updated_at: this.toMySQLDatetime(now),
97
+ id
98
+ };
99
+
100
+ const updateFields = {{UPDATE_FIELDS_ARRAY}}.map(f => \`\\\`\${f}\\\` = :\${f}\`).join(', ');
101
+
102
+ const result = await this.db.query(
103
+ \`UPDATE \\\`\${this.tableName}\\\` SET \${updateFields}, updated_at = :updated_at WHERE id = :id\`,
104
+ data
105
+ );
106
+
107
+ if (result.success) {
108
+ return this.getById(id) as Promise<{{ENTITY_NAME}}>;
109
+ }
110
+
111
+ throw new Error('Failed to update {{ENTITY_NAME}}');
112
+ }
113
+
114
+ async softDelete(id: number): Promise<boolean> {
115
+ const now = new Date();
116
+ const result = await this.db.query(
117
+ \`UPDATE \\\`\${this.tableName}\\\` SET deleted_at = :deleted_at WHERE id = :id\`,
118
+ { deleted_at: this.toMySQLDatetime(now), id }
119
+ );
120
+
121
+ return result.success;
122
+ }
123
+
124
+ async hardDelete(id: number): Promise<boolean> {
125
+ const result = await this.db.query(
126
+ \`DELETE FROM \\\`\${this.tableName}\\\` WHERE id = :id\`,
127
+ { id }
128
+ );
129
+
130
+ return result.success;
131
+ }
132
+ {{GET_BY_PARENT_ID_METHOD}}
133
+ {{GET_RESOURCE_OWNER_METHOD}}}`
134
+ };
135
+ exports.newStoreFileTemplate = `import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
136
+ import type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}
137
+
138
+ {{ROW_INTERFACE}}
139
+
140
+ {{STORE_CLASS}}
141
+ `;
@@ -1,9 +1,5 @@
1
1
  export declare const storeTemplates: {
2
2
  rowInterface: string;
3
3
  storeClass: string;
4
- conversionMethods: string;
5
- };
6
- export declare const fileTemplates: {
7
- storeFile: string;
8
- storeInterface: string;
9
4
  };
5
+ export declare const storeFileTemplate = "import { Injectable } from '../../../../system';\nimport { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';\nimport type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}\n\n{{ROW_INTERFACE}}\n\n{{STORE_CLASS}}\n";
@@ -1,260 +1,143 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fileTemplates = exports.storeTemplates = void 0;
3
+ exports.storeFileTemplate = exports.storeTemplates = void 0;
4
4
  exports.storeTemplates = {
5
5
  rowInterface: `export interface {{ENTITY_NAME}}Row {
6
6
  id: number;
7
7
  {{ROW_FIELDS}}
8
- created_at: Date;
9
- updated_at: Date;
10
- deleted_at?: Date;
8
+ created_at: string;
9
+ updated_at: string;
10
+ deleted_at?: string;
11
11
  }`,
12
- storeClass: `export class {{ENTITY_NAME}}Store implements StoreInterface<{{ENTITY_NAME}}, {{ENTITY_NAME}}Row> {
13
- private static readonly FILTERABLE_FIELDS = [{{FILTERABLE_FIELDS_ARRAY}}];
14
- private static readonly UPDATABLE_FIELDS = [{{UPDATABLE_FIELDS_ARRAY}}];
15
-
12
+ storeClass: `/**
13
+ * Data access layer for {{ENTITY_NAME}}
14
+ */
15
+ @Injectable()
16
+ export class {{ENTITY_NAME}}Store {
17
+ private tableName = '{{TABLE_NAME}}';
18
+
16
19
  constructor(private db: ISqlProvider) {}
17
20
 
18
- async getById(id: number): Promise<{{ENTITY_NAME}} | null> {
19
- try {
20
- const query = 'SELECT * FROM {{TABLE_NAME}} WHERE id = :id AND deleted_at IS NULL';
21
- const result = await this.db.query(query, { id });
22
-
23
- if (!result.success || result.data.length === 0) {
24
- return null;
25
- }
26
-
27
- return {{ENTITY_NAME}}Store.rowToModel(result.data[0] as {{ENTITY_NAME}}Row);
28
- } catch (error) {
29
- if (error instanceof MySQLConnectionError) {
30
- throw new Error(\`Database connection error while fetching {{ENTITY_NAME}} with id \${id}: \${error.message}\`);
31
- } else if (error instanceof MySQLQueryError) {
32
- throw new Error(\`Query error while fetching {{ENTITY_NAME}} with id \${id}: \${error.message}\`);
33
- }
34
- throw error;
35
- }
21
+ private toMySQLDatetime(date: Date): string {
22
+ return date.toISOString().slice(0, 19).replace('T', ' ');
36
23
  }
37
24
 
38
- async getAll(page: number = 1, limit: number = 10): Promise<{{ENTITY_NAME}}[]> {
39
- const offset = (page - 1) * limit;
40
- const query = \`SELECT * FROM {{TABLE_NAME}} WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT \${limit} OFFSET \${offset}\`;
41
- const result = await this.db.query(query, {});
42
-
43
- if (!result.success) {
44
- return [];
45
- }
46
-
47
- return result.data.map((row: {{ENTITY_NAME}}Row) => {{ENTITY_NAME}}Store.rowToModel(row));
25
+ private rowToModel(row: {{ENTITY_NAME}}Row): {{ENTITY_NAME}} {
26
+ return new {{ENTITY_NAME}}(
27
+ row.id,
28
+ {{ROW_TO_MODEL_MAPPING}}
29
+ );
48
30
  }
49
31
 
50
- async getAllByUserId(userId: number, page: number = 1, limit: number = 10): Promise<{{ENTITY_NAME}}[]> {
32
+ async getAll(page: number = 1, limit: number = 20): Promise<{{ENTITY_NAME}}[]> {
51
33
  const offset = (page - 1) * limit;
52
- const query = \`SELECT * FROM {{TABLE_NAME}} WHERE user_id = :userId AND deleted_at IS NULL ORDER BY created_at DESC LIMIT \${limit} OFFSET \${offset}\`;
53
- const result = await this.db.query(query, { userId });
54
-
55
- if (!result.success) {
56
- return [];
57
- }
58
-
59
- return result.data.map((row: {{ENTITY_NAME}}Row) => {{ENTITY_NAME}}Store.rowToModel(row));
60
- }
34
+ const result = await this.db.query(
35
+ \`SELECT {{FIELD_NAMES}} FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL LIMIT :limit OFFSET :offset\`,
36
+ { limit: String(limit), offset: String(offset) }
37
+ );
61
38
 
62
- async getBy(filters: Partial<Pick<{{ENTITY_NAME}}Row, {{FILTERABLE_FIELDS}}>>): Promise<{{ENTITY_NAME}} | null> {
63
- const filterKeys = Object.keys(filters);
64
-
65
- // Runtime validation against SQL injection
66
- for (const key of filterKeys) {
67
- if (!{{ENTITY_NAME}}Store.FILTERABLE_FIELDS.includes(key)) {
68
- throw new Error(\`Invalid filter field: \${key}\`);
69
- }
39
+ if (result.success && result.data) {
40
+ return result.data.map((row: {{ENTITY_NAME}}Row) => this.rowToModel(row));
70
41
  }
71
-
72
- const whereConditions = filterKeys.map(key => \`\${key} = :\${key}\`);
73
-
74
- if (whereConditions.length === 0) {
75
- throw new Error('At least one filter condition is required');
76
- }
77
-
78
- const query = \`SELECT * FROM {{TABLE_NAME}} WHERE \${whereConditions.join(' AND ')} AND deleted_at IS NULL LIMIT 1\`;
79
- const result = await this.db.query(query, filters);
80
-
81
- if (!result.success || result.data.length === 0) {
82
- return null;
83
- }
84
-
85
- return {{ENTITY_NAME}}Store.rowToModel(result.data[0] as {{ENTITY_NAME}}Row);
42
+ return [];
86
43
  }
87
44
 
88
- async getAllBy(filters: Partial<Pick<{{ENTITY_NAME}}Row, {{FILTERABLE_FIELDS}}>>): Promise<{{ENTITY_NAME}}[]> {
89
- const filterKeys = Object.keys(filters);
90
-
91
- // Runtime validation against SQL injection
92
- for (const key of filterKeys) {
93
- if (!{{ENTITY_NAME}}Store.FILTERABLE_FIELDS.includes(key)) {
94
- throw new Error(\`Invalid filter field: \${key}\`);
95
- }
96
- }
97
-
98
- const whereConditions = filterKeys.map(key => \`\${key} = :\${key}\`);
99
-
100
- if (whereConditions.length === 0) {
101
- return this.getAll();
102
- }
103
-
104
- const query = \`SELECT * FROM {{TABLE_NAME}} WHERE \${whereConditions.join(' AND ')} AND deleted_at IS NULL ORDER BY created_at DESC\`;
105
- const result = await this.db.query(query, filters);
106
-
107
- if (!result.success) {
108
- return [];
45
+ async count(): Promise<number> {
46
+ const result = await this.db.query(
47
+ \`SELECT COUNT(*) as count FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL\`,
48
+ {}
49
+ );
50
+
51
+ if (result.success && result.data && result.data.length > 0) {
52
+ return parseInt(result.data[0].count, 10);
109
53
  }
110
-
111
- return result.data.map((row: {{ENTITY_NAME}}Row) => {{ENTITY_NAME}}Store.rowToModel(row));
54
+ return 0;
112
55
  }
113
56
 
114
- async insert(model: Omit<{{ENTITY_NAME}}, 'id'>): Promise<{{ENTITY_NAME}}> {
115
- try {
116
- const row = {{ENTITY_NAME}}Store.modelToRow(model as {{ENTITY_NAME}});
117
- delete row.id; // Remove id for insert
118
- row.created_at = new Date();
119
- row.updated_at = new Date();
120
-
121
- const fields = Object.keys(row);
122
- const placeholders = fields.map(field => \`:\${field}\`).join(', ');
123
-
124
- const query = \`INSERT INTO {{TABLE_NAME}} (\${fields.join(', ')}) VALUES (\${placeholders})\`;
125
- const result = await this.db.query(query, row);
126
-
127
- if (!result.success || !result.insertId) {
128
- throw new Error('Failed to insert {{ENTITY_NAME}}: Insert operation did not return a valid ID');
129
- }
130
-
131
- return this.getById(Number(result.insertId));
132
- } catch (error) {
133
- if (error instanceof MySQLQueryError) {
134
- throw new Error(\`Failed to insert {{ENTITY_NAME}}: \${error.message}\`);
135
- } else if (error instanceof MySQLConnectionError) {
136
- throw new Error(\`Database connection error while inserting {{ENTITY_NAME}}: \${error.message}\`);
137
- }
138
- throw error;
57
+ async getById(id: number): Promise<{{ENTITY_NAME}} | null> {
58
+ const result = await this.db.query(
59
+ \`SELECT {{FIELD_NAMES}} FROM \\\`\${this.tableName}\\\` WHERE id = :id AND deleted_at IS NULL\`,
60
+ { id }
61
+ );
62
+
63
+ if (result.success && result.data && result.data.length > 0) {
64
+ return this.rowToModel(result.data[0] as {{ENTITY_NAME}}Row);
139
65
  }
66
+ return null;
140
67
  }
141
68
 
142
- async update(id: number, updates: Partial<Omit<{{ENTITY_NAME}}, 'id' | 'createdAt'>>): Promise<{{ENTITY_NAME}} | null> {
143
- const existing = await this.getById(id);
144
- if (!existing) {
145
- return null;
146
- }
147
-
148
- // Extract only data properties, not methods
149
- const updateData = this.extractDataProperties(updates);
150
- const updateKeys = Object.keys(updateData);
151
-
152
- // Runtime validation against SQL injection
153
- for (const key of updateKeys) {
154
- if (!{{ENTITY_NAME}}Store.UPDATABLE_FIELDS.includes(key)) {
155
- throw new Error(\`Invalid update field: \${key}\`);
156
- }
157
- }
158
-
159
- const updateFields = updateKeys.map(key => \`\${key} = :\${key}\`);
160
-
161
- if (updateFields.length === 0) {
162
- return existing;
69
+ async insert(entity: {{ENTITY_NAME}}): Promise<{{ENTITY_NAME}}> {
70
+ const now = new Date();
71
+ const data: Partial<{{ENTITY_NAME}}Row> = {
72
+ {{INSERT_DATA_MAPPING}},
73
+ created_at: this.toMySQLDatetime(now),
74
+ updated_at: this.toMySQLDatetime(now)
75
+ };
76
+
77
+ const fieldsList = Object.keys(data).map(f => \`\\\`\${f}\\\`\`).join(', ');
78
+ const placeholders = Object.keys(data).map(f => \`:\${f}\`).join(', ');
79
+
80
+ const result = await this.db.query(
81
+ \`INSERT INTO \\\`\${this.tableName}\\\` (\${fieldsList}) VALUES (\${placeholders})\`,
82
+ data
83
+ );
84
+
85
+ if (result.success && result.insertId) {
86
+ const newId = typeof result.insertId === 'string' ? parseInt(result.insertId, 10) : result.insertId;
87
+ return this.getById(newId) as Promise<{{ENTITY_NAME}}>;
163
88
  }
164
-
165
- const params = { ...updateData, updated_at: new Date(), id };
166
-
167
- const query = \`UPDATE {{TABLE_NAME}} SET \${updateFields.join(', ')}, updated_at = :updated_at WHERE id = :id\`;
168
- await this.db.query(query, params);
169
-
170
- return this.getById(id);
89
+
90
+ throw new Error('Failed to insert {{ENTITY_NAME}}');
171
91
  }
172
92
 
173
- async upsert(model: Partial<{{ENTITY_NAME}}>): Promise<{{ENTITY_NAME}}> {
174
- if (model.id) {
175
- const existing = await this.getById(model.id);
176
- if (existing) {
177
- return this.update(model.id, model) as Promise<{{ENTITY_NAME}}>;
178
- }
93
+ async update(id: number, entity: {{ENTITY_NAME}}): Promise<{{ENTITY_NAME}}> {
94
+ const now = new Date();
95
+ const data: Partial<{{ENTITY_NAME}}Row> & { id: number } = {
96
+ {{UPDATE_DATA_MAPPING}},
97
+ updated_at: this.toMySQLDatetime(now),
98
+ id
99
+ };
100
+
101
+ const updateFields = {{UPDATE_FIELDS_ARRAY}}.map(f => \`\\\`\${f}\\\` = :\${f}\`).join(', ');
102
+
103
+ const result = await this.db.query(
104
+ \`UPDATE \\\`\${this.tableName}\\\` SET \${updateFields}, updated_at = :updated_at WHERE id = :id\`,
105
+ data
106
+ );
107
+
108
+ if (result.success) {
109
+ return this.getById(id) as Promise<{{ENTITY_NAME}}>;
179
110
  }
180
-
181
- return this.insert(model as Omit<{{ENTITY_NAME}}, 'id'>);
111
+
112
+ throw new Error('Failed to update {{ENTITY_NAME}}');
182
113
  }
183
114
 
184
115
  async softDelete(id: number): Promise<boolean> {
185
- const query = 'UPDATE {{TABLE_NAME}} SET deleted_at = :deleted_at WHERE id = :id AND deleted_at IS NULL';
186
- const result = await this.db.query(query, { deleted_at: new Date(), id });
187
-
188
- return result.success && (result.affectedRows || 0) > 0;
189
- }
116
+ const now = new Date();
117
+ const result = await this.db.query(
118
+ \`UPDATE \\\`\${this.tableName}\\\` SET deleted_at = :deleted_at WHERE id = :id\`,
119
+ { deleted_at: this.toMySQLDatetime(now), id }
120
+ );
190
121
 
191
- async count(filters?: Partial<Pick<{{ENTITY_NAME}}Row, {{FILTERABLE_FIELDS}}>>): Promise<number> {
192
- let query = 'SELECT COUNT(*) as count FROM {{TABLE_NAME}} WHERE deleted_at IS NULL';
193
- let params: Record<string, any> = {};
194
-
195
- if (filters && Object.keys(filters).length > 0) {
196
- const whereConditions = Object.keys(filters).map(key => \`\${key} = :\${key}\`);
197
- params = { ...filters };
198
- query += \` AND \${whereConditions.join(' AND ')}\`;
199
- }
200
-
201
- const result = await this.db.query(query, params);
202
-
203
- if (!result.success || result.data.length === 0) {
204
- return 0;
205
- }
206
-
207
- return result.data[0].count;
122
+ return result.success;
208
123
  }
209
124
 
210
- {{CONVERSION_METHODS}}
211
- }`,
212
- conversionMethods: ` static rowToModel(row: {{ENTITY_NAME}}Row): {{ENTITY_NAME}} {
213
- return new {{ENTITY_NAME}}(
214
- row.id,
215
- {{ROW_TO_MODEL_MAPPING}}
125
+ async hardDelete(id: number): Promise<boolean> {
126
+ const result = await this.db.query(
127
+ \`DELETE FROM \\\`\${this.tableName}\\\` WHERE id = :id\`,
128
+ { id }
216
129
  );
217
- }
218
130
 
219
- static modelToRow(model: {{ENTITY_NAME}}): Partial<{{ENTITY_NAME}}Row> {
220
- return {
221
- id: model.id,
222
- {{MODEL_TO_ROW_MAPPING}},
223
- updated_at: new Date()
224
- };
131
+ return result.success;
225
132
  }
226
-
227
- private extractDataProperties(obj: any): Record<string, any> {
228
- const result: Record<string, any> = {};
229
-
230
- for (const [key, value] of Object.entries(obj)) {
231
- // Only include properties that are not functions and are in updatable fields
232
- if (typeof value !== 'function' && {{ENTITY_NAME}}Store.UPDATABLE_FIELDS.includes(key)) {
233
- result[key] = value;
234
- }
235
- }
236
-
237
- return result;
238
- }`
133
+ {{GET_BY_PARENT_ID_METHOD}}
134
+ {{GET_RESOURCE_OWNER_METHOD}}}`
239
135
  };
240
- exports.fileTemplates = {
241
- storeFile: `import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
242
- import { StoreInterface } from '../interfaces/StoreInterface';
243
- import { ProviderMysql, ISqlProvider, MySQLQueryError, MySQLConnectionError } from '@currentjs/provider-mysql';
136
+ exports.storeFileTemplate = `import { Injectable } from '../../../../system';
137
+ import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
138
+ import type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}
244
139
 
245
140
  {{ROW_INTERFACE}}
246
141
 
247
- {{STORE_CLASS}}`,
248
- storeInterface: `export interface StoreInterface<TModel, TRow> {
249
- getById(id: number): Promise<TModel | null>;
250
- getAll(page?: number, limit?: number): Promise<TModel[]>;
251
- getAllByUserId(userId: number, page?: number, limit?: number): Promise<TModel[]>;
252
- getBy(filters: Partial<TRow>): Promise<TModel | null>;
253
- getAllBy(filters: Partial<TRow>): Promise<TModel[]>;
254
- insert(model: Omit<TModel, 'id'>): Promise<TModel>;
255
- update(id: number, updates: Partial<Omit<TModel, 'id' | 'createdAt'>>): Promise<TModel | null>;
256
- upsert(model: Partial<TModel>): Promise<TModel>;
257
- softDelete(id: number): Promise<boolean>;
258
- count(filters?: Partial<TRow>): Promise<number>;
259
- }`
260
- };
142
+ {{STORE_CLASS}}
143
+ `;
@@ -484,7 +484,7 @@ function renderLayoutTemplate(layoutName) {
484
484
  <title>${layoutName}</title>
485
485
  </head>
486
486
  <body>
487
- {{ content }}
487
+ {{{ content }}}
488
488
  </body>
489
489
  </html>
490
490
  `;