@currentjs/gen 0.2.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -3,19 +3,79 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
5
 
6
+
7
+
8
+ ## [0.2.2] - 2025-10-02
9
+
10
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
11
+
12
+ ## [0.2.1] - 2025-09-18
13
+
14
+ Improve generated package.json
15
+
16
+
17
+ ## [0.2.2] - 2025-10-02
18
+
19
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
20
+
6
21
  ## [0.2.0] - 2025-09-18
7
22
 
8
23
  implement multi-model generation (controllers, services); fix service-controller interaction; update documentation: more clear & reflect important things
9
24
 
25
+
26
+
27
+ ## [0.2.2] - 2025-10-02
28
+
29
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
30
+
31
+ ## [0.2.1] - 2025-09-18
32
+
33
+ Improve generated package.json
34
+
35
+
36
+ ## [0.2.2] - 2025-10-02
37
+
38
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
39
+
10
40
  ## [0.1.2] - 2025-09-18
11
41
 
12
42
  fix: failed to generate with empty permissions actions
13
43
 
14
44
 
45
+
46
+
47
+ ## [0.2.2] - 2025-10-02
48
+
49
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
50
+
51
+ ## [0.2.1] - 2025-09-18
52
+
53
+ Improve generated package.json
54
+
55
+
56
+ ## [0.2.2] - 2025-10-02
57
+
58
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
59
+
15
60
  ## [0.2.0] - 2025-09-18
16
61
 
17
62
  implement multi-model generation (controllers, services); fix service-controller interaction; update documentation: more clear & reflect important things
18
63
 
64
+
65
+
66
+ ## [0.2.2] - 2025-10-02
67
+
68
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
69
+
70
+ ## [0.2.1] - 2025-09-18
71
+
72
+ Improve generated package.json
73
+
74
+
75
+ ## [0.2.2] - 2025-10-02
76
+
77
+ fix bug: required params after optional in the generated models; fix: views(templates) are not stored in the registry and being regenerated (overwritten); small readme fix; running 'npm i' on 'create app' and 'npm run build' on 'generate'
78
+
19
79
  ## [0.1.1] - 2025-09-17
20
80
 
21
81
  Initial release
package/README.md CHANGED
@@ -213,7 +213,6 @@ currentjs diff Blog
213
213
  # In your deployment pipeline
214
214
  git clone your-repo
215
215
  currentjs generate # Recreates all source code from YAML + patches
216
- npm run build
217
216
  npm run deploy
218
217
  ```
219
218
 
@@ -345,7 +344,6 @@ permissions: []
345
344
  ### 3. Generate everything
346
345
  ```bash
347
346
  currentjs generate
348
- npm run build
349
347
  npm start
350
348
  ```
351
349
 
@@ -632,12 +630,6 @@ api:
632
630
  prefix: /api/posts
633
631
  endpoints: [...]
634
632
 
635
- # You can create separate API configs for other models
636
- commentApi:
637
- model: Comment # This API serves the Comment model
638
- prefix: /api/comments
639
- endpoints: [...]
640
-
641
633
  actions:
642
634
  createPost:
643
635
  handlers: [Post:default:create] # Calls PostService.create()
@@ -43,6 +43,7 @@ const validationGenerator_1 = require("../generators/validationGenerator");
43
43
  const serviceGenerator_1 = require("../generators/serviceGenerator");
44
44
  const controllerGenerator_1 = require("../generators/controllerGenerator");
45
45
  const storeGenerator_1 = require("../generators/storeGenerator");
46
+ const templateGenerator_1 = require("../generators/templateGenerator");
46
47
  const generationRegistry_1 = require("../utils/generationRegistry");
47
48
  const commitUtils_1 = require("../utils/commitUtils");
48
49
  const colors_1 = require("../utils/colors");
@@ -88,6 +89,7 @@ function handleCommit(yamlPathArg, files) {
88
89
  const svcGen = new serviceGenerator_1.ServiceGenerator();
89
90
  const ctrlGen = new controllerGenerator_1.ControllerGenerator();
90
91
  const storeGen = new storeGenerator_1.StoreGenerator();
92
+ const tplGen = new templateGenerator_1.TemplateGenerator();
91
93
  const diffs = [];
92
94
  modulesList.forEach(moduleYamlRel => {
93
95
  const moduleYamlPath = path.isAbsolute(moduleYamlRel)
@@ -109,6 +111,7 @@ function handleCommit(yamlPathArg, files) {
109
111
  const nextServices = svcGen.generateFromYamlFile(moduleYamlPath);
110
112
  const nextControllers = ctrlGen.generateFromYamlFile(moduleYamlPath);
111
113
  const nextStores = storeGen.generateFromYamlFile(moduleYamlPath);
114
+ const nextTemplates = tplGen.generateFromYamlFile(moduleYamlPath);
112
115
  // Helper to evaluate only user-changed files and compute diff vs freshly generated code
113
116
  const consider = (target, generated) => {
114
117
  if (!fs.existsSync(target))
@@ -143,6 +146,7 @@ function handleCommit(yamlPathArg, files) {
143
146
  Object.entries(nextServices).forEach(([entity, code]) => consider(path.join(appOut, 'services', `${entity}Service.ts`), code));
144
147
  Object.entries(nextControllers).forEach(([entity, code]) => consider(path.join(infraOut, 'controllers', `${entity}Controller.ts`), code));
145
148
  Object.entries(nextStores).forEach(([entity, code]) => consider(path.join(infraOut, 'stores', `${entity}Store.ts`), code));
149
+ Object.entries(nextTemplates).forEach(([, { file, contents }]) => consider(file, contents));
146
150
  });
147
151
  const commitsDir = (0, generationRegistry_1.ensureCommitsDir)();
148
152
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -61,4 +61,9 @@ function handleCreateApp(rawName) {
61
61
  (0, cliUtils_1.writeFileIfMissing)(path.join(templatesDir, appTemplates_1.DEFAULT_FILES.ERROR_TEMPLATE), appTemplates_1.errorTemplate);
62
62
  (0, cliUtils_1.writeFileIfMissing)(path.join(webDir, appTemplates_1.DEFAULT_FILES.FRONTEND_SCRIPT), appTemplates_1.frontendScriptTemplate);
63
63
  (0, cliUtils_1.writeFileIfMissing)(path.join(webDir, appTemplates_1.DEFAULT_FILES.TRANSLATIONS), appTemplates_1.translationsTemplate);
64
+ // Run npm install
65
+ (0, cliUtils_1.runCommand)('npm install', {
66
+ cwd: targetRoot,
67
+ errorMessage: '[X] Failed to install dependencies:'
68
+ });
64
69
  }
@@ -43,6 +43,7 @@ const validationGenerator_1 = require("../generators/validationGenerator");
43
43
  const serviceGenerator_1 = require("../generators/serviceGenerator");
44
44
  const controllerGenerator_1 = require("../generators/controllerGenerator");
45
45
  const storeGenerator_1 = require("../generators/storeGenerator");
46
+ const templateGenerator_1 = require("../generators/templateGenerator");
46
47
  const generationRegistry_1 = require("../utils/generationRegistry");
47
48
  const commitUtils_1 = require("../utils/commitUtils");
48
49
  const colors_1 = require("../utils/colors");
@@ -81,6 +82,7 @@ async function handleDiff(yamlPathArg, moduleName) {
81
82
  const svcGen = new serviceGenerator_1.ServiceGenerator();
82
83
  const ctrlGen = new controllerGenerator_1.ControllerGenerator();
83
84
  const storeGen = new storeGenerator_1.StoreGenerator();
85
+ const tplGen = new templateGenerator_1.TemplateGenerator();
84
86
  const results = [];
85
87
  for (const moduleYamlRel of filteredModules) {
86
88
  const moduleYamlPath = path.isAbsolute(moduleYamlRel)
@@ -97,6 +99,7 @@ async function handleDiff(yamlPathArg, moduleName) {
97
99
  const nextServices = svcGen.generateFromYamlFile(moduleYamlPath);
98
100
  const nextControllers = ctrlGen.generateFromYamlFile(moduleYamlPath);
99
101
  const nextStores = storeGen.generateFromYamlFile(moduleYamlPath);
102
+ const nextTemplates = tplGen.generateFromYamlFile(moduleYamlPath);
100
103
  const consider = (target, generated) => {
101
104
  const rel = path.relative(process.cwd(), target);
102
105
  if (!fs.existsSync(target)) {
@@ -136,6 +139,7 @@ async function handleDiff(yamlPathArg, moduleName) {
136
139
  Object.entries(nextServices).forEach(([e, code]) => consider(path.join(appOut, 'services', `${e}Service.ts`), code));
137
140
  Object.entries(nextControllers).forEach(([e, code]) => consider(path.join(infraOut, 'controllers', `${e}Controller.ts`), code));
138
141
  Object.entries(nextStores).forEach(([e, code]) => consider(path.join(infraOut, 'stores', `${e}Store.ts`), code));
142
+ Object.entries(nextTemplates).forEach(([, { file, contents }]) => consider(file, contents));
139
143
  }
140
144
  if (results.length === 0) {
141
145
  // eslint-disable-next-line no-console
@@ -302,4 +302,10 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
302
302
  console.warn(colors_1.colors.yellow(`Could not update app.ts with controllers: ${e instanceof Error ? e.message : String(e)}`));
303
303
  }
304
304
  }
305
+ // Run npm run build
306
+ (0, cliUtils_1.runCommand)('npm run build', {
307
+ infoMessage: '\nBuilding...',
308
+ successMessage: '[v] Build completed successfully',
309
+ errorMessage: '[x] Build failed:'
310
+ });
305
311
  }
@@ -21,6 +21,7 @@ export declare class DomainModelGenerator {
21
21
  private mapType;
22
22
  private generateConstructorParameter;
23
23
  private generateSetterMethods;
24
+ private sortFieldsByRequired;
24
25
  generateModel(modelConfig: ModelConfig): string;
25
26
  generateModels(models: ModelConfig[]): string;
26
27
  generateFromYamlFile(yamlFilePath: string): Record<string, string>;
@@ -102,12 +102,25 @@ class DomainModelGenerator {
102
102
  });
103
103
  return setterMethods.join('\n');
104
104
  }
105
+ sortFieldsByRequired(fields) {
106
+ // Sort fields: required fields first, then optional fields
107
+ return [...fields].sort((a, b) => {
108
+ const aRequired = a.required !== false && !a.auto;
109
+ const bRequired = b.required !== false && !b.auto;
110
+ if (aRequired === bRequired) {
111
+ return 0; // Keep original order if both have same required status
112
+ }
113
+ return aRequired ? -1 : 1; // Required fields come first
114
+ });
115
+ }
105
116
  generateModel(modelConfig) {
106
117
  const className = modelConfig.name;
107
118
  // Always add id field first
108
119
  const constructorParams = ['public id: number'];
120
+ // Sort fields to put required fields before optional ones
121
+ const sortedFields = this.sortFieldsByRequired(modelConfig.fields);
109
122
  // Process other fields
110
- modelConfig.fields.forEach(field => {
123
+ sortedFields.forEach(field => {
111
124
  constructorParams.push(this.generateConstructorParameter(field));
112
125
  });
113
126
  const constructorParamsStr = constructorParams.join(',\n ');
@@ -24,6 +24,7 @@ export declare class ServiceGenerator {
24
24
  private generateMethodImplementation;
25
25
  private extractFunctionName;
26
26
  private getMethodCallParams;
27
+ private sortFieldsByRequired;
27
28
  private generateConstructorArgs;
28
29
  private generateUpdateSetterCalls;
29
30
  private replaceTemplateVars;
@@ -288,6 +288,18 @@ class ServiceGenerator {
288
288
  return '/* custom params */';
289
289
  }
290
290
  }
291
+ sortFieldsByRequired(fields) {
292
+ // Sort fields: required fields first, then optional fields
293
+ // This must match the order used in domainModelGenerator
294
+ return [...fields].sort((a, b) => {
295
+ const aRequired = a.required !== false && !a.auto;
296
+ const bRequired = b.required !== false && !b.auto;
297
+ if (aRequired === bRequired) {
298
+ return 0; // Keep original order if both have same required status
299
+ }
300
+ return aRequired ? -1 : 1; // Required fields come first
301
+ });
302
+ }
291
303
  generateConstructorArgs(moduleConfig, entityName) {
292
304
  if (!moduleConfig.models || moduleConfig.models.length === 0) {
293
305
  return '';
@@ -295,7 +307,9 @@ class ServiceGenerator {
295
307
  // Find the correct model by entityName instead of always using first model
296
308
  const model = moduleConfig.models.find(m => m.name === entityName) || moduleConfig.models[0];
297
309
  const entityLower = entityName.toLowerCase();
298
- return model.fields
310
+ // Sort fields to match the constructor parameter order
311
+ const sortedFields = this.sortFieldsByRequired(model.fields);
312
+ return sortedFields
299
313
  .filter(field => !field.auto && field.name !== 'id')
300
314
  .map(field => `${entityLower}Data.${field.name}`)
301
315
  .join(', ');
@@ -16,6 +16,7 @@ export declare class StoreGenerator {
16
16
  private generateFilterableFields;
17
17
  private generateFilterableFieldsArray;
18
18
  private generateUpdatableFieldsArray;
19
+ private sortFieldsByRequired;
19
20
  private generateRowToModelMapping;
20
21
  private generateModelToRowMapping;
21
22
  private replaceTemplateVars;
@@ -87,8 +87,22 @@ class StoreGenerator {
87
87
  .map(field => `'${field.name}'`);
88
88
  return updatableFields.join(', ');
89
89
  }
90
+ sortFieldsByRequired(fields) {
91
+ // Sort fields: required fields first, then optional fields
92
+ // This must match the order used in domainModelGenerator
93
+ return [...fields].sort((a, b) => {
94
+ const aRequired = a.required !== false && !a.auto;
95
+ const bRequired = b.required !== false && !b.auto;
96
+ if (aRequired === bRequired) {
97
+ return 0; // Keep original order if both have same required status
98
+ }
99
+ return aRequired ? -1 : 1; // Required fields come first
100
+ });
101
+ }
90
102
  generateRowToModelMapping(modelConfig) {
91
- const mappings = modelConfig.fields.map(field => {
103
+ // Sort fields to match the constructor parameter order
104
+ const sortedFields = this.sortFieldsByRequired(modelConfig.fields);
105
+ const mappings = sortedFields.map(field => {
92
106
  if (field.name === 'createdAt') {
93
107
  return ' row.created_at';
94
108
  }
@@ -6,7 +6,7 @@ export declare const mainViewTemplate = "<!-- @template name=\"main_view\" -->\n
6
6
  export declare const errorTemplate = "<!-- @template name=\"error\" -->\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Error - Your App</title>\n <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\">\n <script src=\"/app.js\"></script>\n</head>\n<body>\n <div class=\"container-fluid\">\n <div class=\"row justify-content-center\">\n <div class=\"col-md-6\">\n <div class=\"text-center mt-5\">\n <div class=\"display-1 text-danger fw-bold mb-3\">{{ statusCode }}</div>\n <h2 class=\"mb-3\">Oops! Something went wrong</h2>\n <p class=\"text-muted mb-4\">{{ error }}</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button class=\"btn btn-primary\" onclick=\"window.history.back()\">Go Back</button>\n <a href=\"/\" class=\"btn btn-outline-secondary\">Home Page</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n</body>\n</html>\n";
7
7
  export declare const frontendScriptTemplate = "/**\n * Common Frontend Functions for Generated Apps\n * This script provides utilities for UI feedback, navigation, form handling, and SPA-like behavior\n */\n\n// Global configuration\nwindow.AppConfig = {\n toastDuration: 3000,\n modalDuration: 1200,\n animationDuration: 300,\n debounceDelay: 300,\n translations: {},\n currentLang: null\n};\n\n// ===== TRANSLATION FUNCTIONS =====\n\n/**\n * Get current language\n * Priority: localStorage -> navigator.language -> 'en'\n * @returns {string} Current language code\n */\nfunction getCurrentLanguage() {\n if (window.AppConfig.currentLang) {\n return window.AppConfig.currentLang;\n }\n \n // 1. Check localStorage\n const storedLang = localStorage.getItem('lang');\n if (storedLang) {\n window.AppConfig.currentLang = storedLang;\n return storedLang;\n }\n \n // 2. Check browser language (Accept-Language equivalent)\n const browserLang = navigator.language || navigator.languages?.[0];\n if (browserLang) {\n // Extract language code (e.g., 'en-US' -> 'en')\n const langCode = browserLang.split('-')[0];\n window.AppConfig.currentLang = langCode;\n return langCode;\n }\n \n // 3. Default fallback\n window.AppConfig.currentLang = 'en';\n return 'en';\n}\n\n/**\n * Translate string to current language\n * @param {string} str - String in default language to translate\n * @returns {string} Translated string or original if translation not found\n */\nfunction t(str) {\n if (!str || typeof str !== 'string') return str;\n \n const currentLang = getCurrentLanguage();\n \n // If current language is the default or no translations loaded, return original\n if (currentLang === 'en' || !window.AppConfig.translations || !window.AppConfig.translations[currentLang]) {\n return str;\n }\n \n const translation = window.AppConfig.translations[currentLang][str];\n return translation || str;\n}\n\n/**\n * Set current language and save to localStorage\n * @param {string} langKey - Language code (e.g., 'en', 'ru', 'pl')\n */\nfunction setLang(langKey) {\n if (!langKey || typeof langKey !== 'string') return;\n \n window.AppConfig.currentLang = langKey;\n localStorage.setItem('lang', langKey);\n \n // Optionally reload page to apply translations\n // Uncomment the next line if you want automatic page reload on language change\n // window.location.reload();\n}\n\n/**\n * Load translations from JSON file\n * @param {string} url - URL to translations JSON file (default: '/translations.json')\n */\nfunction loadTranslations(url = '/translations.json') {\n fetch(url)\n .then(response => {\n if (!response.ok) {\n console.warn('Translations file not found:', url);\n return {};\n }\n return response.json();\n })\n .then(translations => {\n window.AppConfig.translations = translations || {};\n })\n .catch(error => {\n console.warn('Failed to load translations:', error);\n window.AppConfig.translations = {};\n });\n}\n\n// ===== UI FEEDBACK & NOTIFICATIONS =====\n\n/**\n * Show a toast notification\n * @param {string} message - The message to display\n * @param {string} type - 'success', 'error', 'info', 'warning'\n */\nfunction showToast(message, type = 'info') {\n // Translate the message\n message = t(message);\n const toast = document.createElement('div');\n toast.className = 'app-toast app-toast-' + type;\n toast.textContent = message;\n toast.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 24px;\n border-radius: 4px;\n color: white;\n font-weight: 500;\n z-index: 10000;\n max-width: 300px;\n word-wrap: break-word;\n transition: all ${window.AppConfig.animationDuration}ms ease;\n transform: translateX(100%);\n opacity: 0;\n `;\n \n // Type-specific styling\n const colors = {\n success: '#10b981',\n error: '#ef4444',\n warning: '#f59e0b',\n info: '#3b82f6'\n };\n toast.style.backgroundColor = colors[type] || colors.info;\n \n document.body.appendChild(toast);\n \n // Animate in\n setTimeout(() => {\n toast.style.transform = 'translateX(0)';\n toast.style.opacity = '1';\n }, 10);\n \n // Auto remove\n setTimeout(() => {\n toast.style.transform = 'translateX(100%)';\n toast.style.opacity = '0';\n setTimeout(() => {\n if (toast.parentNode) {\n toast.parentNode.removeChild(toast);\n }\n }, window.AppConfig.animationDuration);\n }, window.AppConfig.toastDuration);\n}\n\n/**\n * Display inline message in specific container\n * @param {string} elementId - ID of the target element\n * @param {string} message - The message to display\n * @param {string} type - 'success', 'error', 'info', 'warning'\n */\nfunction showMessage(elementId, message, type = 'info') {\n const element = getElementSafely('#' + elementId);\n if (!element) return;\n \n // Translate the message\n message = t(message);\n element.textContent = message;\n element.className = 'app-message app-message-' + type;\n element.style.cssText = `\n padding: 8px 12px;\n border-radius: 4px;\n margin: 8px 0;\n font-size: 14px;\n display: block;\n `;\n \n // Type-specific styling\n const styles = {\n success: 'background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0;',\n error: 'background: #fee2e2; color: #991b1b; border: 1px solid #fca5a5;',\n warning: 'background: #fef3c7; color: #92400e; border: 1px solid #fcd34d;',\n info: 'background: #dbeafe; color: #1e40af; border: 1px solid #93c5fd;'\n };\n element.style.cssText += styles[type] || styles.info;\n}\n\n/**\n * Show modal dialog\n * @param {string} modalId - ID of the modal element\n * @param {string} message - The message to display\n * @param {string} type - 'success', 'error', 'info', 'warning'\n */\nfunction showModal(modalId, message, type = 'info') {\n let modal = getElementSafely('#' + modalId);\n \n if (!modal) {\n // Create modal if it doesn't exist\n modal = document.createElement('dialog');\n modal.id = modalId;\n modal.innerHTML = `\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <button class=\"modal-close\" onclick=\"this.closest('dialog').close()\">&times;</button>\n </div>\n <div class=\"modal-body\"></div>\n </div>\n `;\n modal.style.cssText = `\n border: none;\n border-radius: 8px;\n padding: 0;\n max-width: 400px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);\n `;\n document.body.appendChild(modal);\n }\n \n const content = modal.querySelector('.modal-body');\n if (content) {\n // Translate the message\n message = t(message);\n content.textContent = message;\n content.className = 'modal-body modal-' + type;\n content.style.cssText = 'padding: 20px; text-align: center;';\n }\n \n if (modal.showModal) {\n modal.showModal();\n setTimeout(() => {\n try { modal.close(); } catch(e) {}\n }, window.AppConfig.modalDuration);\n }\n}\n\n// ===== NAVIGATION & PAGE ACTIONS =====\n\n/**\n * Enhanced history.back() with fallback\n */\nfunction navigateBack() {\n if (window.history.length > 1) {\n window.history.back();\n } else {\n window.location.href = '/';\n }\n}\n\n/**\n * Safe redirect with validation\n * @param {string} url - The URL to redirect to\n */\nfunction redirectTo(url) {\n if (!url || typeof url !== 'string') return;\n // Basic URL validation\n if (url.startsWith('/') || url.startsWith('http')) {\n window.location.href = url;\n }\n}\n\n/**\n * Page reload with loading indication\n */\nfunction reloadPage() {\n showToast('Reloading page...', 'info');\n setTimeout(() => {\n window.location.reload();\n }, 500);\n}\n\n/**\n * Refresh specific page sections by reloading their content\n * @param {string} selector - CSS selector for the section to refresh\n */\nfunction refreshSection(selector) {\n const element = getElementSafely(selector);\n if (!element) return;\n \n // If element has data-refresh-url, use that to reload content\n const refreshUrl = element.getAttribute('data-refresh-url');\n if (refreshUrl) {\n fetch(refreshUrl)\n .then(response => response.text())\n .then(html => {\n element.innerHTML = html;\n })\n .catch(error => {\n console.error('Failed to refresh section:', error);\n showToast('Failed to refresh content', 'error');\n });\n }\n}\n\n// ===== FORM & CONTENT MANAGEMENT =====\n\n/**\n * Safe element removal with animation\n * @param {string} selector - CSS selector for element to remove\n */\nfunction removeElement(selector) {\n const element = getElementSafely(selector);\n if (!element) return;\n \n element.style.transition = `opacity ${window.AppConfig.animationDuration}ms ease`;\n element.style.opacity = '0';\n \n setTimeout(() => {\n if (element.parentNode) {\n element.parentNode.removeChild(element);\n }\n }, window.AppConfig.animationDuration);\n}\n\n/**\n * Update content in element\n * @param {string} selector - CSS selector for target element\n * @param {string} content - New content\n * @param {string} mode - 'replace', 'append', 'prepend'\n */\nfunction updateContent(selector, content, mode = 'replace') {\n const element = getElementSafely(selector);\n if (!element) return;\n \n switch (mode) {\n case 'append':\n element.innerHTML += content;\n break;\n case 'prepend':\n element.innerHTML = content + element.innerHTML;\n break;\n case 'replace':\n default:\n element.innerHTML = content;\n break;\n }\n}\n\n/**\n * Reset form fields and validation states\n * @param {string} formSelector - CSS selector for the form\n */\nfunction clearForm(formSelector) {\n const form = getElementSafely(formSelector);\n if (!form) return;\n \n if (form.reset) {\n form.reset();\n }\n \n // Clear validation messages\n const messages = form.querySelectorAll('.app-message');\n messages.forEach(msg => msg.remove());\n}\n\n// ===== CUSTOM SPA INTEGRATION =====\n\n/**\n * Centralized success handler that executes strategy array\n * @param {object} response - The response object\n * @param {string[]} strategy - Array of strategy actions\n * @param {object} options - Additional options (basePath, entityName, etc.)\n */\nfunction handleFormSuccess(response, strategy = ['back', 'toast'], options = {}) {\n const { basePath, entityName = 'Item' } = options;\n \n strategy.forEach(action => {\n switch (action) {\n case 'toast':\n showToast(`${entityName} saved successfully`, 'success');\n break;\n case 'message':\n if (options.messageId) {\n showMessage(options.messageId, `${entityName} saved successfully`, 'success');\n }\n break;\n case 'modal':\n if (options.modalId) {\n showModal(options.modalId, `${entityName} saved successfully`, 'success');\n }\n break;\n case 'remove':\n // If targetSelector is specified, remove that element instead of the form\n if (options.targetSelector) {\n removeElement(options.targetSelector);\n } else if (options.formSelector) {\n removeElement(options.formSelector);\n }\n break;\n case 'redirect':\n if (basePath) {\n redirectTo(basePath);\n }\n break;\n case 'back':\n navigateBack();\n break;\n case 'reload':\n case 'refresh':\n reloadPage();\n break;\n }\n });\n}\n\n/**\n * Handle internal link navigation via fetch\n * @param {string} url - The URL to navigate to\n * @param {Element} targetElement - Element to update with new content (default: #main)\n */\nfunction navigateToPage(url, targetElement = null) {\n const target = targetElement || document.querySelector('#main');\n if (!target) return;\n \n showLoading('#main');\n \n fetch(url, {\n headers: {\n 'Accept': 'text/html',\n 'X-Partial-Content': 'true'\n }\n })\n .then(response => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return response.text();\n })\n .then(html => {\n target.innerHTML = html;\n // Update browser history\n window.history.pushState({}, '', url);\n // Re-initialize event listeners for new content\n initializeEventListeners();\n })\n .catch(error => {\n console.error('Navigation failed:', error);\n showToast('Failed to load page', 'error');\n // Fallback to normal navigation\n window.location.href = url;\n })\n .finally(() => {\n hideLoading('#main');\n });\n}\n\n/**\n * Convert form value to appropriate type based on field type\n * @param {string} value - Raw form value\n * @param {string} fieldType - Field type (number, boolean, etc.)\n * @returns {any} Converted value\n */\nfunction convertFieldValue(value, fieldType) {\n if (!value || value === '') {\n return null;\n }\n \n switch (fieldType.toLowerCase()) {\n case 'number':\n case 'int':\n case 'integer':\n const intVal = parseInt(value, 10);\n return isNaN(intVal) ? null : intVal;\n \n case 'float':\n case 'decimal':\n const floatVal = parseFloat(value);\n return isNaN(floatVal) ? null : floatVal;\n \n case 'boolean':\n case 'bool':\n if (value === 'true') return true;\n if (value === 'false') return false;\n return Boolean(value);\n \n case 'enum':\n case 'string':\n case 'text':\n default:\n return value;\n }\n}\n\n/**\n * Handle form submission via fetch with JSON data\n * @param {HTMLFormElement} form - The form element\n * @param {string[]} strategy - Strategy actions to execute on success\n * @param {object} options - Additional options\n */\nfunction submitForm(form, strategy = ['back', 'toast'], options = {}) {\n const formData = new FormData(form);\n const jsonData = {};\n \n // Get field types from form data attribute\n const fieldTypesAttr = form.getAttribute('data-field-types');\n const fieldTypes = fieldTypesAttr ? JSON.parse(fieldTypesAttr) : {};\n \n // Convert FormData to JSON with proper typing\n for (const [key, value] of formData.entries()) {\n const fieldType = fieldTypes[key] || 'string';\n const convertedValue = convertFieldValue(value, fieldType);\n \n if (jsonData[key]) {\n // Handle multiple values (e.g., checkboxes)\n if (Array.isArray(jsonData[key])) {\n jsonData[key].push(convertedValue);\n } else {\n jsonData[key] = [jsonData[key], convertedValue];\n }\n } else {\n jsonData[key] = convertedValue;\n }\n }\n \n const url = form.getAttribute('data-action') || form.action;\n const method = (form.getAttribute('data-method') || form.method || 'POST').toUpperCase();\n \n showLoading(form);\n \n fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n },\n body: JSON.stringify(jsonData)\n })\n .then(response => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return response.json();\n })\n .then(data => {\n handleFormSuccess(data, strategy, options);\n })\n .catch(error => {\n console.error('Form submission failed:', error);\n handleFormError({ message: error.message || 'Form submission failed' }, options);\n })\n .finally(() => {\n hideLoading(form);\n });\n}\n\n/**\n * Standardized error handling for forms\n * @param {object} response - The error response\n * @param {object} options - Options including target elements\n */\nfunction handleFormError(response, options = {}) {\n const message = response.message || 'An error occurred';\n \n if (options.messageId) {\n showMessage(options.messageId, message, 'error');\n } else {\n showToast(message, 'error');\n }\n}\n\n/**\n * Add client-side validation helpers\n * @param {string} formSelector - CSS selector for the form\n */\nfunction setupFormValidation(formSelector) {\n const form = getElementSafely(formSelector);\n if (!form) return;\n \n // Add basic required field validation\n const requiredFields = form.querySelectorAll('[required]');\n requiredFields.forEach(field => {\n field.addEventListener('blur', function() {\n if (!this.value.trim()) {\n showMessage(this.id + '-error', 'This field is required', 'error');\n } else {\n const errorEl = getElementSafely('#' + this.id + '-error');\n if (errorEl) errorEl.textContent = '';\n }\n });\n });\n}\n\n// ===== UTILITY FUNCTIONS =====\n\n/**\n * Safe element selection with error handling\n * @param {string} selector - CSS selector\n * @returns {Element|null}\n */\nfunction getElementSafely(selector) {\n try {\n return document.querySelector(selector);\n } catch (e) {\n console.warn('Invalid selector:', selector);\n return null;\n }\n}\n\n/**\n * Debounce utility for search/input handlers\n * @param {Function} fn - Function to debounce\n * @param {number} delay - Delay in milliseconds\n * @returns {Function}\n */\nfunction debounce(fn, delay = window.AppConfig.debounceDelay) {\n let timeoutId;\n return function(...args) {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => fn.apply(this, args), delay);\n };\n}\n\n/**\n * Show loading state for target element\n * @param {string} target - CSS selector for target element\n */\nfunction showLoading(target) {\n const element = getElementSafely(target);\n if (!element) return;\n \n element.style.position = 'relative';\n element.style.pointerEvents = 'none';\n element.style.opacity = '0.6';\n \n const loader = document.createElement('div');\n loader.className = 'app-loader';\n loader.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 20px;\n height: 20px;\n border: 2px solid #f3f3f3;\n border-top: 2px solid #3498db;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n z-index: 1000;\n `;\n \n element.appendChild(loader);\n \n // Add CSS animation if not exists\n if (!document.querySelector('#app-loader-styles')) {\n const style = document.createElement('style');\n style.id = 'app-loader-styles';\n style.textContent = `\n @keyframes spin {\n 0% { transform: translate(-50%, -50%) rotate(0deg); }\n 100% { transform: translate(-50%, -50%) rotate(360deg); }\n }\n `;\n document.head.appendChild(style);\n }\n}\n\n/**\n * Hide loading state for target element\n * @param {string} target - CSS selector for target element\n */\nfunction hideLoading(target) {\n const element = getElementSafely(target);\n if (!element) return;\n \n element.style.pointerEvents = '';\n element.style.opacity = '';\n \n const loader = element.querySelector('.app-loader');\n if (loader) {\n loader.remove();\n }\n}\n\n// ===== INITIALIZATION =====\n\n/**\n * Initialize event listeners for links and forms\n */\nfunction initializeEventListeners() {\n // Handle internal links\n document.querySelectorAll('a[href]').forEach(link => {\n const href = link.getAttribute('href');\n \n // Skip external links, anchors, and special protocols\n if (!href || \n href.startsWith('http://') || \n href.startsWith('https://') || \n href.startsWith('mailto:') || \n href.startsWith('tel:') || \n href.startsWith('#') ||\n href.startsWith('javascript:')) {\n return;\n }\n \n // Remove any existing event listeners and add new one\n link.removeEventListener('click', handleLinkClick);\n link.addEventListener('click', handleLinkClick);\n });\n \n // Handle forms with strategy\n document.querySelectorAll('form[data-strategy]').forEach(form => {\n // Remove any existing event listeners and add new one\n form.removeEventListener('submit', handleFormSubmit);\n form.addEventListener('submit', handleFormSubmit);\n });\n}\n\n/**\n * Handle link click for internal navigation\n * @param {Event} event - Click event\n */\nfunction handleLinkClick(event) {\n event.preventDefault();\n const href = event.currentTarget.getAttribute('href');\n if (href) {\n navigateToPage(href);\n }\n}\n\n/**\n * Handle form submission\n * @param {Event} event - Submit event\n */\nfunction handleFormSubmit(event) {\n event.preventDefault();\n const form = event.target;\n \n // Check for confirmation message\n const confirmMessage = form.getAttribute('data-confirm-message');\n if (confirmMessage) {\n const confirmed = confirm(t(confirmMessage));\n if (!confirmed) {\n return; // User cancelled\n }\n }\n \n const strategyAttr = form.getAttribute('data-strategy');\n const strategy = strategyAttr ? JSON.parse(strategyAttr) : ['back', 'toast'];\n \n // Extract options from form data attributes\n const options = {\n basePath: form.getAttribute('data-base-path') || '',\n entityName: form.getAttribute('data-entity-name') || 'Item',\n messageId: form.getAttribute('data-message-id'),\n modalId: form.getAttribute('data-modal-id'),\n formSelector: `form[data-template=\"${form.getAttribute('data-template')}\"]`,\n targetSelector: form.getAttribute('data-target-selector')\n };\n \n submitForm(form, strategy, options);\n}\n\n// Set up event handlers on page load\ndocument.addEventListener('DOMContentLoaded', function() {\n // Initialize event listeners\n initializeEventListeners();\n \n // Handle browser back/forward buttons\n window.addEventListener('popstate', function(event) {\n // Reload the page content for the current URL\n navigateToPage(window.location.pathname);\n });\n \n // Load translations on page load\n loadTranslations();\n});\n\n// Expose functions globally\nwindow.App = {\n // Translation functions\n t,\n setLang,\n getCurrentLanguage,\n loadTranslations,\n showToast,\n showMessage,\n showModal,\n navigateBack,\n redirectTo,\n reloadPage,\n refreshSection,\n removeElement,\n updateContent,\n clearForm,\n handleFormSuccess,\n handleFormError,\n setupFormValidation,\n getElementSafely,\n debounce,\n showLoading,\n hideLoading,\n // New SPA functions\n navigateToPage,\n submitForm,\n initializeEventListeners,\n // Type conversion\n convertFieldValue\n};\n";
8
8
  export declare const translationsTemplate = "{\n \"ru\": {\n \"error\": \"\u043E\u0448\u0438\u0431\u043A\u0430\",\n \"success\": \"\u0443\u0441\u043F\u0435\u0448\u043D\u043E\",\n \"Reloading page...\": \"\u041F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B...\",\n \"Request failed. Please try again.\": \"\u0417\u0430\u043F\u0440\u043E\u0441 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.\",\n \"This field is required\": \"\u042D\u0442\u043E \u043F\u043E\u043B\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\",\n \"An error occurred\": \"\u041F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u0430 \u043E\u0448\u0438\u0431\u043A\u0430\",\n \"saved successfully\": \"\u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E\",\n \"updated successfully\": \"\u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E\",\n \"deleted successfully\": \"\u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0443\u0434\u0430\u043B\u0435\u043D\u043E\"\n },\n \"pl\": {\n \"error\": \"b\u0142\u0105d\",\n \"success\": \"sukces\",\n \"Reloading page...\": \"Prze\u0142adowywanie strony...\",\n \"Request failed. Please try again.\": \"\u017B\u0105danie nie powiod\u0142o si\u0119. Spr\u00F3buj ponownie.\",\n \"This field is required\": \"To pole jest wymagane\",\n \"An error occurred\": \"Wyst\u0105pi\u0142 b\u0142\u0105d\",\n \"saved successfully\": \"zapisano pomy\u015Blnie\",\n \"updated successfully\": \"zaktualizowano pomy\u015Blnie\",\n \"deleted successfully\": \"usuni\u0119to pomy\u015Blnie\"\n },\n \"es\": {\n \"error\": \"error\",\n \"success\": \"\u00E9xito\",\n \"Reloading page...\": \"Recargando p\u00E1gina...\",\n \"Request failed. Please try again.\": \"La solicitud fall\u00F3. Por favor, int\u00E9ntelo de nuevo.\",\n \"This field is required\": \"Este campo es obligatorio\",\n \"An error occurred\": \"Ha ocurrido un error\",\n \"saved successfully\": \"guardado exitosamente\",\n \"updated successfully\": \"actualizado exitosamente\",\n \"deleted successfully\": \"eliminado exitosamente\"\n },\n \"de\": {\n \"error\": \"Fehler\",\n \"success\": \"Erfolg\",\n \"Reloading page...\": \"Seite wird neu geladen...\",\n \"Request failed. Please try again.\": \"Anfrage fehlgeschlagen. Bitte versuchen Sie es erneut.\",\n \"This field is required\": \"Dieses Feld ist erforderlich\",\n \"An error occurred\": \"Ein Fehler ist aufgetreten\",\n \"saved successfully\": \"erfolgreich gespeichert\",\n \"updated successfully\": \"erfolgreich aktualisiert\",\n \"deleted successfully\": \"erfolgreich gel\u00F6scht\"\n },\n \"pt\": {\n \"error\": \"erro\",\n \"success\": \"sucesso\",\n \"Reloading page...\": \"Recarregando p\u00E1gina...\",\n \"Request failed. Please try again.\": \"A solicita\u00E7\u00E3o falhou. Por favor, tente novamente.\",\n \"This field is required\": \"Este campo \u00E9 obrigat\u00F3rio\",\n \"An error occurred\": \"Ocorreu um erro\",\n \"saved successfully\": \"salvo com sucesso\",\n \"updated successfully\": \"atualizado com sucesso\",\n \"deleted successfully\": \"exclu\u00EDdo com sucesso\"\n },\n \"zh\": {\n \"error\": \"\u9519\u8BEF\",\n \"success\": \"\u6210\u529F\",\n \"Reloading page...\": \"\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u9875\u9762...\",\n \"Request failed. Please try again.\": \"\u8BF7\u6C42\u5931\u8D25\u3002\u8BF7\u518D\u8BD5\u4E00\u6B21\u3002\",\n \"This field is required\": \"\u6B64\u5B57\u6BB5\u4E3A\u5FC5\u586B\u9879\",\n \"An error occurred\": \"\u53D1\u751F\u9519\u8BEF\",\n \"saved successfully\": \"\u4FDD\u5B58\u6210\u529F\",\n \"updated successfully\": \"\u66F4\u65B0\u6210\u529F\",\n \"deleted successfully\": \"\u5220\u9664\u6210\u529F\"\n }\n}";
9
- export declare const cursorRulesTemplate = "# CurrentJS Framework Rules\n\n## Architecture Overview\nThis is a CurrentJS framework application using clean architecture principles with the following layers:\n- **Controllers**: Handle HTTP requests/responses and route handling\n- **Services**: Contain business logic and orchestrate operations\n- **Stores**: Provide data access layer and database operations\n- **Domain Entities**: Core business models\n- **Views**: HTML templates for server-side rendering\n\n## Commands\n\n```bash\ncurrent create module Modulename # Creates \"Modulename\" with default structure and yaml file\n```\n```bash\ncurrent generate Modulename # Generates all TypeScript files based on the module's yaml\ncurrent generate # Does the same as above, but for all modules\n```\n```bash\ncurrent commit [files...] # Commits all changes in the code, so they won't be overwritten after regeneration\ncurrent diff [module] # Show differences between generated and current code\n```\n\n## The flow\n1. Create an empty app (`current create app`) \u2013 this step is already done.\n2. Create a new module: `current create module Name`\n3. In the module's yaml file, define module's:\n - model(s)\n - routes & actions\n - permissions\n4. Generate TypeScript files: `current generate Name`\n5. If required, make changes in the:\n - model (i.e. some specific business rules or validations)\n - views\n6. Commit those changes: `current commit`\n\n--- If needed more than CRUD: ---\n\n7. Define action in the service by creating a method\n8. Describe this action in the module's yaml. Additionaly, you may define routes and permissions.\n9. `current generate Modulename`\n10. commit changes: `current commit`\n\n## Configuration Files\n\n### Application Configuration (app.yaml)\n\n**Do not modify this file**\n\n### Module Configuration (modulename.yaml)\n\n**The most work must be done in these files**\n\n**Complete Module Example:**\n```yaml\nmodels:\n - name: Post # Entity name (capitalized)\n fields:\n - name: title # Field name\n type: string # Field type: string, number, boolean, datetime\n required: true # Validation requirement\n - name: content\n type: string\n required: true\n - name: authorId\n type: number\n required: true\n - name: publishedAt\n type: datetime\n required: false\n - name: status\n type: string\n required: true\n\napi: # REST API configuration\n prefix: /api/posts # Base URL for API endpoints\n model: Post # Optional: which model this API serves (defaults to first model)\n endpoints:\n - method: GET # HTTP method\n path: / # Relative path (becomes /api/posts/)\n action: list # Action name (references actions section)\n - method: GET\n path: /:id # Path parameter\n action: get\n - method: POST\n path: /\n action: create\n - method: PUT\n path: /:id\n action: update\n - method: DELETE\n path: /:id\n action: delete\n - method: POST # Custom endpoint\n path: /:id/publish\n action: publish\n model: Post # Optional: override model for specific endpoint\n\nroutes: # Web interface configuration\n prefix: /posts # Base URL for web pages\n model: Post # Optional: which model this route serves (defaults to first model)\n strategy: [toast, back] # Default success strategies for forms\n endpoints:\n - path: / # List page\n action: list\n view: postList # Template name\n - path: /:id # Detail page\n action: get\n view: postDetail\n - path: /create # Create form page\n action: empty # No data loading action\n view: postCreate\n - path: /:id/edit # Edit form page\n action: get # Load existing data\n view: postUpdate\n model: Post # Optional: override model for specific endpoint\n\nactions: # Business logic mapping\n list:\n handlers: [Post:default:list] # Use built-in list handler\n get:\n handlers: [Post:default:get] # Use built-in get handler\n create:\n handlers: [Post:default:create] # Built-in create\n update:\n handlers: [Post:default:update]\n delete:\n handlers: [ # Chain multiple handlers\n Post:checkCanDelete, # Custom business logic\n Post:default:delete\n ]\n publish: # Custom action\n handlers: [\n Post:default:get, # Fetch entity\n Post:validateForPublish, # Custom validation\n Post:updatePublishStatus # Custom update logic\n ]\n\npermissions: # Role-based access control\n - role: all\n actions: [list, get] # Anyone (including anonymous)\n - role: authenticated\n actions: [create] # Must be logged in\n - role: owner\n actions: [update, publish] # Entity owner permissions\n - role: admin\n actions: [update, delete, publish] # Admin role permissions\n - role: editor\n actions: [publish] # Editor role permissions\n```\n\n**Make sure no `ID`/`owner id`/`is deleted` fields are present in the model definition, since it's added automatically**\n\n**Field Types:**\n- `string` - Text data\n- `number` - Numeric data (integer or float)\n- `boolean` - True/false values\n- `datetime` - Date and time values\n\n**\uD83D\uDD04 Handler vs Action Architecture:**\n- **Handler**: Creates a separate service method (one handler = one service method)\n- **Action**: Virtual controller concept that calls handler methods step-by-step\n\n**Built-in Action Handlers:**\n- `ModelName:default:list` - Creates service method with pagination parameters\n- `ModelName:default:get` - Creates service method named `get` with ID parameter\n- `ModelName:default:create` - Creates service method with DTO parameter\n- `ModelName:default:update` - Creates service method with ID and DTO parameters\n- `ModelName:default:delete` - Creates service method with ID parameter\n\n**Custom Action Handlers:**\n- `ModelName:customMethodName` - Creates service method that accepts `result, context` parameters\n- `result`: Result from previous handler (or `null` if it's the first handler)\n- `context`: The request context object\n- Each handler generates a separate method in the service\n- User can customize the implementation after generation\n\n**\uD83D\uDD17 Multiple Handlers per Action:**\nWhen an action has multiple handlers, each handler generates a separate service method, and the controller action calls them sequentially. The action returns the result from the last handler.\n\n**Parameter Passing Rules:**\n- **Default handlers** (`:default:`): Receive standard parameters (id, pagination, DTO, etc.)\n- **Custom handlers**: Receive `(result, context)` where:\n - `result`: Result from previous handler, or `null` if it's the first handler\n - `context`: Request context object\n\n**Handler Format Examples:**\n- `Post:default:list` - Creates Post service method `list(page, limit)`\n- `Post:default:get` - Creates Post service method `get(id)`\n- `Post:validateContent` - Creates Post service method `validateContent(result, context)`\n- `Comment:notifySubscribers` - Creates Comment service method `notifySubscribers(result, context)`\n\n**Strategy Options (for forms):**\n- `toast` - Success toast notification\n- `back` - Navigate back in browser history\n- `message` - Inline success message\n- `modal` - Modal success dialog\n- `redirect` - Redirect to specific URL\n- `refresh` - Reload current page\n\n**Permission Roles:**\n- `all` - Anyone (including anonymous users)\n- `authenticated` - Any logged-in user\n- `owner` - User who created the entity\n- `admin`, `editor`, `user` - Custom roles from JWT token\n- Multiple roles can be specified for each action\n\n**Generated Files from Configuration:**\n- Domain entity class (one per model)\n- Service class (one per model)\n- API controller with REST endpoints (one per model)\n- Web controller with page rendering (one per model)\n- Store class with database operations (one per model)\n- HTML templates for all views\n- TypeScript interfaces and DTOs\n\n**Multi-Model Support:**\n- Each model gets its own service, controller, and store\n- Use `model` parameter in `api` and `routes` to specify which model to use (defaults to first model)\n- Use `model` parameter on individual endpoints to override model for specific endpoints\n- Action handlers use `modelname:action` format to specify which model's service method to call\n- Controllers and services are generated per model, not per module\n\n## Module Structure\n```\nsrc/modules/ModuleName/\n\u251C\u2500\u2500 application/\n\u2502 \u251C\u2500\u2500 services/ModuleService.ts # Business logic\n\u2502 \u2514\u2500\u2500 validation/ModuleValidation.ts # DTOs and validation\n\u251C\u2500\u2500 domain/\n\u2502 \u2514\u2500\u2500 entities/Module.ts # Domain model\n\u251C\u2500\u2500 infrastructure/\n\u2502 \u251C\u2500\u2500 controllers/\n\u2502 \u2502 \u251C\u2500\u2500 ModuleApiController.ts # REST API endpoints\n\u2502 \u2502 \u2514\u2500\u2500 ModuleWebController.ts # Web page controllers\n\u2502 \u251C\u2500\u2500 interfaces/StoreInterface.ts # Data access interface\n\u2502 \u2514\u2500\u2500 stores/ModuleStore.ts # Data access implementation\n\u251C\u2500\u2500 views/\n\u2502 \u251C\u2500\u2500 modulelist.html # List view template\n\u2502 \u251C\u2500\u2500 moduledetail.html # Detail view template\n\u2502 \u251C\u2500\u2500 modulecreate.html # Create form template\n\u2502 \u2514\u2500\u2500 moduleupdate.html # Update form template\n\u2514\u2500\u2500 module.yaml # Module configuration\n```\n\n## Best Practices\n\n- Use Domain Driven Design and Clean Architecture (kind of).\n- Prefer declarative configuration over imperative programming: when possible, change yamls instead of writing the code. Write the code only when it's really neccessary.\n- CRUD operation are autogenerated (first in module's yaml, then in the generated code).\n- If some custom action is needed, then it has to be defined in the **Service** then just put this action to the module's yaml. You may also define new methods in the *Store* and its interface (if needed).\n- Business rules must be defined only in models. That also applies to business rules validations (in contrast to *just* validations: e.g. if field exists and is of needed type \u2013 then validators are in use)\n\n## Core Package APIs (For Generated Code)\n\n### @currentjs/router - Controller Decorators & Context\n\n**Decorators (in controllers):**\n```typescript\n@Controller('/api/posts') // Base path for controller\n@Get('/') // GET endpoint\n@Get('/:id') // GET with path parameter\n@Post('/') // POST endpoint\n@Put('/:id') // PUT endpoint\n@Delete('/:id') // DELETE endpoint\n@Render('template', 'layout') // For web controllers\n```\n\n**Context Object (in route handlers):**\n```typescript\ninterface IContext {\n request: {\n url: string;\n path: string;\n method: string;\n parameters: Record<string, string | number>; // Path params + query params\n body: any; // Parsed JSON or raw string\n headers: Record<string, string | string[]>;\n user?: AuthenticatedUser; // Parsed JWT user if authenticated\n };\n response: Record<string, any>;\n}\n\n// Usage examples\nconst id = parseInt(ctx.request.parameters.id as string);\nconst page = parseInt(ctx.request.parameters.page as string) || 1;\nconst payload = ctx.request.body;\nconst user = ctx.request.user; // If authenticated\n```\n\n**Authentication Support:**\n- JWT tokens parsed automatically from `Authorization: Bearer <token>` header\n- User object available at `ctx.request.user` with `id`, `email`, `role` fields\n- No manual setup required in generated code\n\n### @currentjs/templating - Template Syntax\n\n**Variables & Data Access:**\n```html\n{{ variableName }}\n{{ object.property }}\n{{ $root.arrayData }} <!-- Access root data -->\n{{ $index }} <!-- Loop index -->\n```\n\n**Control Structures:**\n```html\n<!-- Loops -->\n<tbody x-for=\"$root\" x-row=\"item\">\n <tr>\n <td>{{ item.name }}</td>\n <td>{{ $index }}</td>\n </tr>\n</tbody>\n\n<!-- Conditionals -->\n<div x-if=\"user.isAdmin\">Admin content</div>\n<span x-if=\"errors.name\">{{ errors.name }}</span>\n\n<!-- Template includes -->\n<userCard name=\"{{ user.name }}\" role=\"admin\" />\n```\n\n**Layout Integration:**\n- Templates use `<!-- @template name=\"templateName\" -->` header\n- Main layout gets `{{ content }}` variable\n- Forms use `{{ formData.field || '' }}` for default values\n\n### @currentjs/provider-mysql - Database Operations\n\n**Query Execution (in stores):**\n```typescript\n// Named parameters (preferred)\nconst result = await this.db.query(\n 'SELECT * FROM users WHERE status = :status AND age > :minAge',\n { status: 'active', minAge: 18 }\n);\n\n// Result handling\nif (result.success && result.data.length > 0) {\n return result.data.map(row => this.rowToModel(row));\n}\n```\n\n**Common Query Patterns:**\n```typescript\n// Insert with auto-generated fields\nconst row = { ...data, created_at: new Date(), updated_at: new Date() };\nconst result = await this.db.query(\n `INSERT INTO table_name (${fields.join(', ')}) VALUES (${placeholders})`,\n row\n);\nconst newId = result.insertId;\n\n// Update with validation\nconst query = `UPDATE table_name SET ${updateFields.join(', ')}, updated_at = :updated_at WHERE id = :id`;\nawait this.db.query(query, { ...updateData, updated_at: new Date(), id });\n\n// Soft delete\nawait this.db.query(\n 'UPDATE table_name SET deleted_at = :deleted_at WHERE id = :id',\n { deleted_at: new Date(), id }\n);\n```\n\n**Error Handling:**\n```typescript\ntry {\n const result = await this.db.query(query, params);\n} catch (error) {\n if (error instanceof MySQLConnectionError) {\n throw new Error(`Database connection error: ${error.message}`);\n } else if (error instanceof MySQLQueryError) {\n throw new Error(`Query error: ${error.message}`);\n }\n throw error;\n}\n```\n\n## Frontend System (web/app.js)\n\n### Translation System\n\n**Basic Usage:**\n```javascript\n// Translate strings\nt('Hello World') // Returns translated version or original\nt('Save changes')\n\n// Set language\nsetLang('pl') // Switch to Polish\nsetLang('en') // Switch to English\ngetCurrentLanguage() // Get current language code\n```\n\n**Translation File (web/translations.json):**\n```json\n{\n \"pl\": {\n \"Hello World\": \"Witaj \u015Awiecie\",\n \"Save changes\": \"Zapisz zmiany\",\n \"Delete\": \"Usu\u0144\"\n },\n \"ru\": {\n \"Hello World\": \"\u041F\u0440\u0438\u0432\u0435\u0442 \u043C\u0438\u0440\",\n \"Save changes\": \"\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F\"\n }\n}\n```\n\n### UI Feedback & Notifications\n\n**Toast Notifications:**\n```javascript\nshowToast('Success message', 'success') // Green toast\nshowToast('Error occurred', 'error') // Red toast \nshowToast('Information', 'info') // Blue toast\nshowToast('Warning', 'warning') // Yellow toast\n```\n\n**Inline Messages:**\n```javascript\nshowMessage('messageId', 'Success!', 'success')\nshowMessage('errorContainer', 'Validation failed', 'error')\n```\n\n**Modal Dialogs:**\n```javascript\nshowModal('confirmModal', 'Item saved successfully', 'success')\nshowModal('errorModal', 'Operation failed', 'error')\n```\n\n### Navigation & Page Actions\n\n**Navigation Functions:**\n```javascript\nnavigateBack() // Go back in history or to home\nredirectTo('/posts') // Safe redirect with validation\nreloadPage() // Reload with loading indicator\nrefreshSection('#content') // Refresh specific section\n\n// SPA-style navigation\nnavigateToPage('/posts/123') // Loads via AJAX, updates #main\n```\n\n**Content Management:**\n```javascript\nupdateContent('#results', newHtml, 'replace') // Replace content\nupdateContent('#list', itemHtml, 'append') // Add to end\nupdateContent('#list', itemHtml, 'prepend') // Add to beginning\nremoveElement('#item-123') // Animate and remove\n```\n\n### Form Handling & Strategy System\n\n**Form Strategy Configuration:**\n```html\n<!-- Form with strategy attributes -->\n<form data-strategy='[\"toast\", \"back\"]' \n data-entity-name=\"Post\"\n data-field-types='{\"age\": \"number\", \"active\": \"boolean\"}'>\n <input name=\"title\" type=\"text\" required>\n <input name=\"age\" type=\"number\">\n <input name=\"active\" type=\"checkbox\">\n <button type=\"submit\">Save</button>\n</form>\n```\n\n**Available Strategies:**\n- `toast` - Show success toast notification\n- `back` - Navigate back using browser history\n- `message` - Show inline message in specific element\n- `modal` - Show modal dialog\n- `redirect` - Redirect to specific URL\n- `refresh` - Reload the page\n- `remove` - Remove form element\n\n**Manual Form Submission:**\n```javascript\nconst form = document.querySelector('#myForm');\nsubmitForm(form, ['toast', 'back'], {\n entityName: 'Post',\n basePath: '/posts',\n messageId: 'form-message'\n});\n```\n\n**Success Handling:**\n```javascript\nhandleFormSuccess(response, ['toast', 'back'], {\n entityName: 'Post',\n basePath: '/posts',\n messageId: 'success-msg',\n modalId: 'success-modal'\n});\n```\n\n### Form Validation & Type Conversion\n\n**Client-side Validation Setup:**\n```javascript\nsetupFormValidation('#createForm'); // Adds required field validation\n```\n\n**Field Type Conversion:**\n```javascript\n// Automatic conversion based on data-field-types\nconvertFieldValue('123', 'number') // Returns 123 (number)\nconvertFieldValue('true', 'boolean') // Returns true (boolean)\nconvertFieldValue('text', 'string') // Returns 'text' (string)\n```\n\n### Loading States & Utilities\n\n**Loading Indicators:**\n```javascript\nshowLoading('#form') // Show spinner on form\nhideLoading('#form') // Hide spinner\nshowLoading('#main') // Show spinner on main content\n```\n\n**Utility Functions:**\n```javascript\ndebounce(searchFunction, 300) // Debounce for search inputs\ngetElementSafely('#selector') // Safe element selection\nclearForm('#myForm') // Reset form and clear validation\n```\n\n### Event Handling & SPA Integration\n\n**Automatic Link Handling:**\n- Internal links automatically use AJAX navigation\n- External links work normally\n- Links with `data-external` skip AJAX handling\n\n**Automatic Form Handling:**\n- Forms with `data-strategy` use AJAX submission\n- Regular forms work normally\n- Automatic JSON conversion from FormData\n\n**Custom Event Listeners:**\n```javascript\n// Re-initialize after dynamic content loading\ninitializeEventListeners();\n\n// Handle specific link navigation\ndocument.querySelector('#myLink').addEventListener('click', (e) => {\n e.preventDefault();\n navigateToPage('/custom/path');\n});\n```\n\n### Global App Object\n\n**Accessing Functions:**\n```javascript\n// All functions available under window.App\nApp.showToast('Message', 'success');\nApp.navigateBack();\nApp.t('Translate this');\nApp.setLang('pl');\nApp.showLoading('#content');\n```\n\n### Template Data Binding\n```html\n<!-- List with pagination -->\n<tbody x-for=\"modules\" x-row=\"module\">\n <tr>\n <td>{{ module.name }}</td>\n <td><a href=\"/module/{{ module.id }}\">View</a></td>\n </tr>\n</tbody>\n\n<!-- Form with validation errors -->\n<div x-if=\"errors.name\" class=\"text-danger\">{{ errors.name }}</div>\n<input type=\"text\" name=\"name\" value=\"{{ formData.name || '' }}\" class=\"form-control\">\n\n<!-- Form with strategy attributes -->\n<form data-strategy='[\"toast\", \"back\"]' \n data-entity-name=\"Module\"\n data-field-types='{\"count\": \"number\", \"active\": \"boolean\"}'>\n <!-- form fields -->\n</form>\n```\n";
9
+ export declare const cursorRulesTemplate = "# CurrentJS Framework Rules\n\n## Architecture Overview\nThis is a CurrentJS framework application using clean architecture principles with the following layers:\n- **Controllers**: Handle HTTP requests/responses and route handling\n- **Services**: Contain business logic and orchestrate operations\n- **Stores**: Provide data access layer and database operations\n- **Domain Entities**: Core business models\n- **Views**: HTML templates for server-side rendering\n\n## Commands\n\n```bash\ncurrent create module Modulename # Creates \"Modulename\" with default structure and yaml file\n```\n```bash\ncurrent generate Modulename # Generates all TypeScript files based on the module's yaml, and runs \"npm run build\"\ncurrent generate # Does the same as above, but for all modules\n```\n```bash\ncurrent commit [files...] # Commits all changes in the code, so they won't be overwritten after regeneration\ncurrent diff [module] # Show differences between generated and current code\n```\n\n## The flow\n1. Create an empty app (`current create app`) \u2013 this step is already done.\n2. Create a new module: `current create module Name`\n3. In the module's yaml file, define module's:\n - model(s)\n - routes & actions\n - permissions\n4. Generate TypeScript files: `current generate Name`\n5. If required, make changes in the:\n - model (i.e. some specific business rules or validations)\n - views\n6. Commit those changes: `current commit`\n\n--- If needed more than CRUD: ---\n\n7. Define action in the service by creating a method\n8. Describe this action in the module's yaml. Additionaly, you may define routes and permissions.\n9. `current generate Modulename`\n10. commit changes: `current commit`\n\n## Configuration Files\n\n### Application Configuration (app.yaml)\n\n**Do not modify this file**\n\n### Module Configuration (modulename.yaml)\n\n**The most work must be done in these files**\n\n**Complete Module Example:**\n```yaml\nmodels:\n - name: Post # Entity name (capitalized)\n fields:\n - name: title # Field name\n type: string # Field type: string, number, boolean, datetime\n required: true # Validation requirement\n - name: content\n type: string\n required: true\n - name: authorId\n type: number\n required: true\n - name: publishedAt\n type: datetime\n required: false\n - name: status\n type: string\n required: true\n\napi: # REST API configuration\n prefix: /api/posts # Base URL for API endpoints\n model: Post # Optional: which model this API serves (defaults to first model)\n endpoints:\n - method: GET # HTTP method\n path: / # Relative path (becomes /api/posts/)\n action: list # Action name (references actions section)\n - method: GET\n path: /:id # Path parameter\n action: get\n - method: POST\n path: /\n action: create\n - method: PUT\n path: /:id\n action: update\n - method: DELETE\n path: /:id\n action: delete\n - method: POST # Custom endpoint\n path: /:id/publish\n action: publish\n model: Post # Optional: override model for specific endpoint\n\nroutes: # Web interface configuration\n prefix: /posts # Base URL for web pages\n model: Post # Optional: which model this route serves (defaults to first model)\n strategy: [toast, back] # Default success strategies for forms\n endpoints:\n - path: / # List page\n action: list\n view: postList # Template name\n - path: /:id # Detail page\n action: get\n view: postDetail\n - path: /create # Create form page\n action: empty # No data loading action\n view: postCreate\n - path: /:id/edit # Edit form page\n action: get # Load existing data\n view: postUpdate\n model: Post # Optional: override model for specific endpoint\n\nactions: # Business logic mapping\n list:\n handlers: [Post:default:list] # Use built-in list handler\n get:\n handlers: [Post:default:get] # Use built-in get handler\n create:\n handlers: [Post:default:create] # Built-in create\n update:\n handlers: [Post:default:update]\n delete:\n handlers: [ # Chain multiple handlers\n Post:checkCanDelete, # Custom business logic\n Post:default:delete\n ]\n publish: # Custom action\n handlers: [\n Post:default:get, # Fetch entity\n Post:validateForPublish, # Custom validation\n Post:updatePublishStatus # Custom update logic\n ]\n\npermissions: # Role-based access control\n - role: all\n actions: [list, get] # Anyone (including anonymous)\n - role: authenticated\n actions: [create] # Must be logged in\n - role: owner\n actions: [update, publish] # Entity owner permissions\n - role: admin\n actions: [update, delete, publish] # Admin role permissions\n - role: editor\n actions: [publish] # Editor role permissions\n```\n\n**Make sure no `ID`/`owner id`/`is deleted` fields are present in the model definition, since it's added automatically**\n\n**Field Types:**\n- `string` - Text data\n- `number` - Numeric data (integer or float)\n- `boolean` - True/false values\n- `datetime` - Date and time values\n\n**\uD83D\uDD04 Handler vs Action Architecture:**\n- **Handler**: Creates a separate service method (one handler = one service method)\n- **Action**: Virtual controller concept that calls handler methods step-by-step\n\n**Built-in Action Handlers:**\n- `ModelName:default:list` - Creates service method with pagination parameters\n- `ModelName:default:get` - Creates service method named `get` with ID parameter\n- `ModelName:default:create` - Creates service method with DTO parameter\n- `ModelName:default:update` - Creates service method with ID and DTO parameters\n- `ModelName:default:delete` - Creates service method with ID parameter\n\n**Custom Action Handlers:**\n- `ModelName:customMethodName` - Creates service method that accepts `result, context` parameters\n- `result`: Result from previous handler (or `null` if it's the first handler)\n- `context`: The request context object\n- Each handler generates a separate method in the service\n- User can customize the implementation after generation\n\n**\uD83D\uDD17 Multiple Handlers per Action:**\nWhen an action has multiple handlers, each handler generates a separate service method, and the controller action calls them sequentially. The action returns the result from the last handler.\n\n**Parameter Passing Rules:**\n- **Default handlers** (`:default:`): Receive standard parameters (id, pagination, DTO, etc.)\n- **Custom handlers**: Receive `(result, context)` where:\n - `result`: Result from previous handler, or `null` if it's the first handler\n - `context`: Request context object\n\n**Handler Format Examples:**\n- `Post:default:list` - Creates Post service method `list(page, limit)`\n- `Post:default:get` - Creates Post service method `get(id)`\n- `Post:validateContent` - Creates Post service method `validateContent(result, context)`\n- `Comment:notifySubscribers` - Creates Comment service method `notifySubscribers(result, context)`\n\n**Strategy Options (for forms):**\n- `toast` - Success toast notification\n- `back` - Navigate back in browser history\n- `message` - Inline success message\n- `modal` - Modal success dialog\n- `redirect` - Redirect to specific URL\n- `refresh` - Reload current page\n\n**Permission Roles:**\n- `all` - Anyone (including anonymous users)\n- `authenticated` - Any logged-in user\n- `owner` - User who created the entity\n- `admin`, `editor`, `user` - Custom roles from JWT token\n- Multiple roles can be specified for each action\n\n**Generated Files from Configuration:**\n- Domain entity class (one per model)\n- Service class (one per model)\n- API controller with REST endpoints (one per model)\n- Web controller with page rendering (one per model)\n- Store class with database operations (one per model)\n- HTML templates for all views\n- TypeScript interfaces and DTOs\n\n**Multi-Model Support:**\n- Each model gets its own service, controller, and store\n- Use `model` parameter in `api` and `routes` to specify which model to use (defaults to first model)\n- Use `model` parameter on individual endpoints to override model for specific endpoints\n- Action handlers use `modelname:action` format to specify which model's service method to call\n- Controllers and services are generated per model, not per module\n\n## Module Structure\n```\nsrc/modules/ModuleName/\n\u251C\u2500\u2500 application/\n\u2502 \u251C\u2500\u2500 services/ModuleService.ts # Business logic\n\u2502 \u2514\u2500\u2500 validation/ModuleValidation.ts # DTOs and validation\n\u251C\u2500\u2500 domain/\n\u2502 \u2514\u2500\u2500 entities/Module.ts # Domain model\n\u251C\u2500\u2500 infrastructure/\n\u2502 \u251C\u2500\u2500 controllers/\n\u2502 \u2502 \u251C\u2500\u2500 ModuleApiController.ts # REST API endpoints\n\u2502 \u2502 \u2514\u2500\u2500 ModuleWebController.ts # Web page controllers\n\u2502 \u251C\u2500\u2500 interfaces/StoreInterface.ts # Data access interface\n\u2502 \u2514\u2500\u2500 stores/ModuleStore.ts # Data access implementation\n\u251C\u2500\u2500 views/\n\u2502 \u251C\u2500\u2500 modulelist.html # List view template\n\u2502 \u251C\u2500\u2500 moduledetail.html # Detail view template\n\u2502 \u251C\u2500\u2500 modulecreate.html # Create form template\n\u2502 \u2514\u2500\u2500 moduleupdate.html # Update form template\n\u2514\u2500\u2500 module.yaml # Module configuration\n```\n\n## Best Practices\n\n- Use Domain Driven Design and Clean Architecture (kind of).\n- Prefer declarative configuration over imperative programming: when possible, change yamls instead of writing the code. Write the code only when it's really neccessary.\n- CRUD operation are autogenerated (first in module's yaml, then in the generated code).\n- If some custom action is needed, then it has to be defined in the **Service** then just put this action to the module's yaml. You may also define new methods in the *Store* and its interface (if needed).\n- Business rules must be defined only in models. That also applies to business rules validations (in contrast to *just* validations: e.g. if field exists and is of needed type \u2013 then validators are in use)\n\n## Core Package APIs (For Generated Code)\n\n### @currentjs/router - Controller Decorators & Context\n\n**Decorators (in controllers):**\n```typescript\n@Controller('/api/posts') // Base path for controller\n@Get('/') // GET endpoint\n@Get('/:id') // GET with path parameter\n@Post('/') // POST endpoint\n@Put('/:id') // PUT endpoint\n@Delete('/:id') // DELETE endpoint\n@Render('template', 'layout') // For web controllers\n```\n\n**Context Object (in route handlers):**\n```typescript\ninterface IContext {\n request: {\n url: string;\n path: string;\n method: string;\n parameters: Record<string, string | number>; // Path params + query params\n body: any; // Parsed JSON or raw string\n headers: Record<string, string | string[]>;\n user?: AuthenticatedUser; // Parsed JWT user if authenticated\n };\n response: Record<string, any>;\n}\n\n// Usage examples\nconst id = parseInt(ctx.request.parameters.id as string);\nconst page = parseInt(ctx.request.parameters.page as string) || 1;\nconst payload = ctx.request.body;\nconst user = ctx.request.user; // If authenticated\n```\n\n**Authentication Support:**\n- JWT tokens parsed automatically from `Authorization: Bearer <token>` header\n- User object available at `ctx.request.user` with `id`, `email`, `role` fields\n- No manual setup required in generated code\n\n### @currentjs/templating - Template Syntax\n\n**Variables & Data Access:**\n```html\n{{ variableName }}\n{{ object.property }}\n{{ $root.arrayData }} <!-- Access root data -->\n{{ $index }} <!-- Loop index -->\n```\n\n**Control Structures:**\n```html\n<!-- Loops -->\n<tbody x-for=\"$root\" x-row=\"item\">\n <tr>\n <td>{{ item.name }}</td>\n <td>{{ $index }}</td>\n </tr>\n</tbody>\n\n<!-- Conditionals -->\n<div x-if=\"user.isAdmin\">Admin content</div>\n<span x-if=\"errors.name\">{{ errors.name }}</span>\n\n<!-- Template includes -->\n<userCard name=\"{{ user.name }}\" role=\"admin\" />\n```\n\n**Layout Integration:**\n- Templates use `<!-- @template name=\"templateName\" -->` header\n- Main layout gets `{{ content }}` variable\n- Forms use `{{ formData.field || '' }}` for default values\n\n### @currentjs/provider-mysql - Database Operations\n\n**Query Execution (in stores):**\n```typescript\n// Named parameters (preferred)\nconst result = await this.db.query(\n 'SELECT * FROM users WHERE status = :status AND age > :minAge',\n { status: 'active', minAge: 18 }\n);\n\n// Result handling\nif (result.success && result.data.length > 0) {\n return result.data.map(row => this.rowToModel(row));\n}\n```\n\n**Common Query Patterns:**\n```typescript\n// Insert with auto-generated fields\nconst row = { ...data, created_at: new Date(), updated_at: new Date() };\nconst result = await this.db.query(\n `INSERT INTO table_name (${fields.join(', ')}) VALUES (${placeholders})`,\n row\n);\nconst newId = result.insertId;\n\n// Update with validation\nconst query = `UPDATE table_name SET ${updateFields.join(', ')}, updated_at = :updated_at WHERE id = :id`;\nawait this.db.query(query, { ...updateData, updated_at: new Date(), id });\n\n// Soft delete\nawait this.db.query(\n 'UPDATE table_name SET deleted_at = :deleted_at WHERE id = :id',\n { deleted_at: new Date(), id }\n);\n```\n\n**Error Handling:**\n```typescript\ntry {\n const result = await this.db.query(query, params);\n} catch (error) {\n if (error instanceof MySQLConnectionError) {\n throw new Error(`Database connection error: ${error.message}`);\n } else if (error instanceof MySQLQueryError) {\n throw new Error(`Query error: ${error.message}`);\n }\n throw error;\n}\n```\n\n## Frontend System (web/app.js)\n\n### Translation System\n\n**Basic Usage:**\n```javascript\n// Translate strings\nt('Hello World') // Returns translated version or original\nt('Save changes')\n\n// Set language\nsetLang('pl') // Switch to Polish\nsetLang('en') // Switch to English\ngetCurrentLanguage() // Get current language code\n```\n\n**Translation File (web/translations.json):**\n```json\n{\n \"pl\": {\n \"Hello World\": \"Witaj \u015Awiecie\",\n \"Save changes\": \"Zapisz zmiany\",\n \"Delete\": \"Usu\u0144\"\n },\n \"ru\": {\n \"Hello World\": \"\u041F\u0440\u0438\u0432\u0435\u0442 \u043C\u0438\u0440\",\n \"Save changes\": \"\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F\"\n }\n}\n```\n\n### UI Feedback & Notifications\n\n**Toast Notifications:**\n```javascript\nshowToast('Success message', 'success') // Green toast\nshowToast('Error occurred', 'error') // Red toast \nshowToast('Information', 'info') // Blue toast\nshowToast('Warning', 'warning') // Yellow toast\n```\n\n**Inline Messages:**\n```javascript\nshowMessage('messageId', 'Success!', 'success')\nshowMessage('errorContainer', 'Validation failed', 'error')\n```\n\n**Modal Dialogs:**\n```javascript\nshowModal('confirmModal', 'Item saved successfully', 'success')\nshowModal('errorModal', 'Operation failed', 'error')\n```\n\n### Navigation & Page Actions\n\n**Navigation Functions:**\n```javascript\nnavigateBack() // Go back in history or to home\nredirectTo('/posts') // Safe redirect with validation\nreloadPage() // Reload with loading indicator\nrefreshSection('#content') // Refresh specific section\n\n// SPA-style navigation\nnavigateToPage('/posts/123') // Loads via AJAX, updates #main\n```\n\n**Content Management:**\n```javascript\nupdateContent('#results', newHtml, 'replace') // Replace content\nupdateContent('#list', itemHtml, 'append') // Add to end\nupdateContent('#list', itemHtml, 'prepend') // Add to beginning\nremoveElement('#item-123') // Animate and remove\n```\n\n### Form Handling & Strategy System\n\n**Form Strategy Configuration:**\n```html\n<!-- Form with strategy attributes -->\n<form data-strategy='[\"toast\", \"back\"]' \n data-entity-name=\"Post\"\n data-field-types='{\"age\": \"number\", \"active\": \"boolean\"}'>\n <input name=\"title\" type=\"text\" required>\n <input name=\"age\" type=\"number\">\n <input name=\"active\" type=\"checkbox\">\n <button type=\"submit\">Save</button>\n</form>\n```\n\n**Available Strategies:**\n- `toast` - Show success toast notification\n- `back` - Navigate back using browser history\n- `message` - Show inline message in specific element\n- `modal` - Show modal dialog\n- `redirect` - Redirect to specific URL\n- `refresh` - Reload the page\n- `remove` - Remove form element\n\n**Manual Form Submission:**\n```javascript\nconst form = document.querySelector('#myForm');\nsubmitForm(form, ['toast', 'back'], {\n entityName: 'Post',\n basePath: '/posts',\n messageId: 'form-message'\n});\n```\n\n**Success Handling:**\n```javascript\nhandleFormSuccess(response, ['toast', 'back'], {\n entityName: 'Post',\n basePath: '/posts',\n messageId: 'success-msg',\n modalId: 'success-modal'\n});\n```\n\n### Form Validation & Type Conversion\n\n**Client-side Validation Setup:**\n```javascript\nsetupFormValidation('#createForm'); // Adds required field validation\n```\n\n**Field Type Conversion:**\n```javascript\n// Automatic conversion based on data-field-types\nconvertFieldValue('123', 'number') // Returns 123 (number)\nconvertFieldValue('true', 'boolean') // Returns true (boolean)\nconvertFieldValue('text', 'string') // Returns 'text' (string)\n```\n\n### Loading States & Utilities\n\n**Loading Indicators:**\n```javascript\nshowLoading('#form') // Show spinner on form\nhideLoading('#form') // Hide spinner\nshowLoading('#main') // Show spinner on main content\n```\n\n**Utility Functions:**\n```javascript\ndebounce(searchFunction, 300) // Debounce for search inputs\ngetElementSafely('#selector') // Safe element selection\nclearForm('#myForm') // Reset form and clear validation\n```\n\n### Event Handling & SPA Integration\n\n**Automatic Link Handling:**\n- Internal links automatically use AJAX navigation\n- External links work normally\n- Links with `data-external` skip AJAX handling\n\n**Automatic Form Handling:**\n- Forms with `data-strategy` use AJAX submission\n- Regular forms work normally\n- Automatic JSON conversion from FormData\n\n**Custom Event Listeners:**\n```javascript\n// Re-initialize after dynamic content loading\ninitializeEventListeners();\n\n// Handle specific link navigation\ndocument.querySelector('#myLink').addEventListener('click', (e) => {\n e.preventDefault();\n navigateToPage('/custom/path');\n});\n```\n\n### Global App Object\n\n**Accessing Functions:**\n```javascript\n// All functions available under window.App\nApp.showToast('Message', 'success');\nApp.navigateBack();\nApp.t('Translate this');\nApp.setLang('pl');\nApp.showLoading('#content');\n```\n\n### Template Data Binding\n```html\n<!-- List with pagination -->\n<tbody x-for=\"modules\" x-row=\"module\">\n <tr>\n <td>{{ module.name }}</td>\n <td><a href=\"/module/{{ module.id }}\">View</a></td>\n </tr>\n</tbody>\n\n<!-- Form with validation errors -->\n<div x-if=\"errors.name\" class=\"text-danger\">{{ errors.name }}</div>\n<input type=\"text\" name=\"name\" value=\"{{ formData.name || '' }}\" class=\"form-control\">\n\n<!-- Form with strategy attributes -->\n<form data-strategy='[\"toast\", \"back\"]' \n data-entity-name=\"Module\"\n data-field-types='{\"count\": \"number\", \"active\": \"boolean\"}'>\n <!-- form fields -->\n</form>\n```\n";
10
10
  export declare const DEFAULT_DIRECTORIES: {
11
11
  readonly SRC: "src";
12
12
  readonly DIST: "build";
@@ -46,15 +46,18 @@ const packageJsonTemplate = (appName) => JSON.stringify({
46
46
  start: 'node build/app.js',
47
47
  clean: 'rm -rf build',
48
48
  dev: 'ts-node src/app.ts',
49
- devstand: 'npm i && npm link @currentjs/router @currentjs/templating @currentjs/provider-mysql && npm run build'
49
+ // uncomment this if you want to use the devstand script
50
+ // devstand: 'npm i && npm link @currentjs/router @currentjs/templating @currentjs/provider-mysql && npm run build'
51
+ },
52
+ dependencies: {
53
+ '@currentjs/router': 'latest',
54
+ '@currentjs/templating': 'latest',
55
+ '@currentjs/provider-mysql': 'latest',
50
56
  },
51
- dependencies: {},
52
57
  devDependencies: {
53
58
  typescript: '^5.6.3',
54
59
  '@types/node': '^22.7.4',
55
60
  '@koz1024/path-fixer': '^0.2.1',
56
- // '@currentjs/router': '^0.1.0',
57
- // '@currentjs/templating': '^0.1.0'
58
61
  },
59
62
  type: 'module'
60
63
  }, null, 2);
@@ -1058,7 +1061,7 @@ This is a CurrentJS framework application using clean architecture principles wi
1058
1061
  current create module Modulename # Creates "Modulename" with default structure and yaml file
1059
1062
  \`\`\`
1060
1063
  \`\`\`bash
1061
- current generate Modulename # Generates all TypeScript files based on the module's yaml
1064
+ current generate Modulename # Generates all TypeScript files based on the module's yaml, and runs "npm run build"
1062
1065
  current generate # Does the same as above, but for all modules
1063
1066
  \`\`\`
1064
1067
  \`\`\`bash
@@ -4,3 +4,9 @@ export declare function ensureDir(dirPath: string): void;
4
4
  export declare function writeFileIfMissing(targetPath: string, contents: string): void;
5
5
  export declare function fileExists(targetPath: string): boolean;
6
6
  export declare function toAbsolute(targetPath: string): string;
7
+ export declare function runCommand(command: string, options?: {
8
+ cwd?: string;
9
+ successMessage?: string;
10
+ errorMessage?: string;
11
+ infoMessage?: string;
12
+ }): boolean;
@@ -39,9 +39,12 @@ exports.ensureDir = ensureDir;
39
39
  exports.writeFileIfMissing = writeFileIfMissing;
40
40
  exports.fileExists = fileExists;
41
41
  exports.toAbsolute = toAbsolute;
42
+ exports.runCommand = runCommand;
42
43
  const fs = __importStar(require("fs"));
43
44
  const path = __importStar(require("path"));
45
+ const child_process_1 = require("child_process");
44
46
  const constants_1 = require("./constants");
47
+ const colors_1 = require("./colors");
45
48
  function resolveYamlPath(provided) {
46
49
  const candidate = provided !== null && provided !== void 0 ? provided : constants_1.COMMON_FILES.APP_YAML;
47
50
  const abs = path.isAbsolute(candidate) ? candidate : path.resolve(process.cwd(), candidate);
@@ -69,3 +72,24 @@ function fileExists(targetPath) {
69
72
  function toAbsolute(targetPath) {
70
73
  return path.isAbsolute(targetPath) ? targetPath : path.resolve(process.cwd(), targetPath);
71
74
  }
75
+ function runCommand(command, options = {}) {
76
+ const { cwd = process.cwd(), successMessage, errorMessage, infoMessage } = options;
77
+ if (infoMessage) {
78
+ // eslint-disable-next-line no-console
79
+ console.log(colors_1.colors.cyan(infoMessage));
80
+ }
81
+ try {
82
+ (0, child_process_1.execSync)(command, { cwd, stdio: 'inherit' });
83
+ if (successMessage) {
84
+ // eslint-disable-next-line no-console
85
+ console.log(colors_1.colors.green(successMessage));
86
+ }
87
+ return true;
88
+ }
89
+ catch (error) {
90
+ const errMsg = errorMessage || `Command failed: ${command}`;
91
+ // eslint-disable-next-line no-console
92
+ console.error(colors_1.colors.red(errMsg), error instanceof Error ? error.message : String(error));
93
+ return false;
94
+ }
95
+ }
package/howto.md CHANGED
@@ -14,7 +14,7 @@ This is a CurrentJS framework application using clean architecture principles wi
14
14
  current create module Modulename # Creates "Modulename" with default structure and yaml file
15
15
  ```
16
16
  ```bash
17
- current generate Modulename # Generates all TypeScript files based on the module's yaml
17
+ current generate Modulename # Generates all TypeScript files based on the module's yaml, and runs "npm run build"
18
18
  current generate # Does the same as above, but for all modules
19
19
  ```
20
20
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@currentjs/gen",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "CLI code generator",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "Konstantin Zavalny",