@currentjs/gen 0.3.1 → 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 +8 -289
  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 +76 -47
  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
@@ -394,8 +394,7 @@ window.AppConfig = {
394
394
  target.innerHTML = html;
395
395
  // Update browser history
396
396
  window.history.pushState({}, '', url);
397
- // Re-initialize event listeners for new content
398
- initializeEventListeners();
397
+ // No need to re-initialize - delegation handles new content automatically
399
398
  })
400
399
  .catch(error => {
401
400
  console.error('Navigation failed:', error);
@@ -415,7 +414,7 @@ window.AppConfig = {
415
414
  * @returns {any} Converted value
416
415
  */
417
416
  function convertFieldValue(value, fieldType) {
418
- if (!value || value === '') {
417
+ if (value === undefined || value === null || value === '') {
419
418
  return null;
420
419
  }
421
420
 
@@ -423,6 +422,7 @@ window.AppConfig = {
423
422
  case 'number':
424
423
  case 'int':
425
424
  case 'integer':
425
+ case 'id':
426
426
  const intVal = parseInt(value, 10);
427
427
  return isNaN(intVal) ? null : intVal;
428
428
 
@@ -433,10 +433,15 @@ window.AppConfig = {
433
433
 
434
434
  case 'boolean':
435
435
  case 'bool':
436
- if (value === 'true') return true;
437
- 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;
438
438
  return Boolean(value);
439
+
440
+ case 'json':
441
+ try { return JSON.parse(value); } catch { return value; }
439
442
 
443
+ case 'datetime':
444
+ case 'date':
440
445
  case 'enum':
441
446
  case 'string':
442
447
  case 'text':
@@ -475,6 +480,30 @@ window.AppConfig = {
475
480
  jsonData[key] = convertedValue;
476
481
  }
477
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
+ }
478
507
 
479
508
  const url = form.getAttribute('data-action') || form.action;
480
509
  const method = (form.getAttribute('data-method') || form.method || 'POST').toUpperCase();
@@ -487,16 +516,20 @@ window.AppConfig = {
487
516
  'Content-Type': 'application/json',
488
517
  'Accept': 'application/json'
489
518
  }),
490
- body: JSON.stringify(jsonData)
519
+ body: JSON.stringify(finalData)
491
520
  })
492
- .then(response => {
493
- if (!response.ok) {
494
- 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;
495
531
  }
496
- return response.json();
497
- })
498
- .then(data => {
499
- handleFormSuccess(data, strategy, options);
532
+ handleFormSuccess(body, strategy, options);
500
533
  })
501
534
  .catch(error => {
502
535
  console.error('Form submission failed:', error);
@@ -616,56 +649,52 @@ window.AppConfig = {
616
649
  // ===== INITIALIZATION =====
617
650
 
618
651
  /**
619
- * Initialize event listeners for links and forms
652
+ * Initialize event delegation for links and forms
653
+ * Uses document-level listeners that work for all current and future elements
620
654
  */
621
655
  function initializeEventListeners() {
622
- // Handle internal links
623
- document.querySelectorAll('a[href]').forEach(link => {
656
+ // Only initialize once
657
+ if (window._appEventListenersInitialized) return;
658
+ window._appEventListenersInitialized = true;
659
+
660
+ // Handle all link clicks via delegation
661
+ document.addEventListener('click', function(event) {
662
+ const link = event.target.closest('a[href]');
663
+ if (!link) return;
664
+
624
665
  const href = link.getAttribute('href');
625
-
666
+
626
667
  // Skip external links, anchors, and special protocols
627
- if (!href ||
628
- href.startsWith('http://') ||
629
- href.startsWith('https://') ||
630
- href.startsWith('mailto:') ||
631
- href.startsWith('tel:') ||
668
+ if (!href ||
669
+ href.startsWith('http://') ||
670
+ href.startsWith('https://') ||
671
+ href.startsWith('mailto:') ||
672
+ href.startsWith('tel:') ||
632
673
  href.startsWith('#') ||
633
674
  href.startsWith('javascript:')) {
634
675
  return;
635
676
  }
636
-
637
- // Remove any existing event listeners and add new one
638
- link.removeEventListener('click', handleLinkClick);
639
- link.addEventListener('click', handleLinkClick);
640
- });
641
-
642
- // Handle forms with strategy
643
- document.querySelectorAll('form[data-strategy]').forEach(form => {
644
- // Remove any existing event listeners and add new one
645
- form.removeEventListener('submit', handleFormSubmit);
646
- form.addEventListener('submit', handleFormSubmit);
647
- });
648
- }
649
677
 
650
- /**
651
- * Handle link click for internal navigation
652
- * @param {Event} event - Click event
653
- */
654
- function handleLinkClick(event) {
655
- event.preventDefault();
656
- const href = event.currentTarget.getAttribute('href');
657
- if (href) {
678
+ event.preventDefault();
658
679
  navigateToPage(href);
659
- }
680
+ });
681
+
682
+ // Handle all form submissions via delegation
683
+ document.addEventListener('submit', function(event) {
684
+ const form = event.target.closest('form[data-strategy]');
685
+ if (!form) return;
686
+
687
+ event.preventDefault();
688
+ handleFormSubmit(event, form);
689
+ });
660
690
  }
661
691
 
662
692
  /**
663
693
  * Handle form submission
664
694
  * @param {Event} event - Submit event
665
695
  */
666
- function handleFormSubmit(event) {
667
- event.preventDefault();
668
- const form = event.target;
696
+ function handleFormSubmit(event, form) {
697
+ //const form = event.target.closest ? event.target.closest('form') : event.target;
669
698
 
670
699
  // Check for confirmation message
671
700
  const confirmMessage = form.getAttribute('data-confirm-message');
@@ -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";