@currentjs/gen 0.2.2 → 0.3.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.
- package/CHANGELOG.md +80 -0
- package/README.md +256 -0
- package/dist/cli.js +26 -0
- package/dist/commands/createApp.js +2 -0
- package/dist/commands/generateAll.js +153 -29
- package/dist/commands/migrateCommit.d.ts +1 -0
- package/dist/commands/migrateCommit.js +201 -0
- package/dist/generators/controllerGenerator.d.ts +7 -0
- package/dist/generators/controllerGenerator.js +60 -29
- package/dist/generators/domainModelGenerator.d.ts +7 -0
- package/dist/generators/domainModelGenerator.js +57 -3
- package/dist/generators/serviceGenerator.d.ts +16 -1
- package/dist/generators/serviceGenerator.js +125 -12
- package/dist/generators/storeGenerator.d.ts +8 -0
- package/dist/generators/storeGenerator.js +133 -7
- package/dist/generators/templateGenerator.d.ts +19 -0
- package/dist/generators/templateGenerator.js +216 -11
- package/dist/generators/templates/appTemplates.d.ts +8 -7
- package/dist/generators/templates/appTemplates.js +11 -1572
- package/dist/generators/templates/data/appTsTemplate +39 -0
- package/dist/generators/templates/data/appYamlTemplate +4 -0
- package/dist/generators/templates/data/cursorRulesTemplate +671 -0
- package/dist/generators/templates/data/errorTemplate +28 -0
- package/dist/generators/templates/data/frontendScriptTemplate +739 -0
- package/dist/generators/templates/data/mainViewTemplate +16 -0
- package/dist/generators/templates/data/translationsTemplate +68 -0
- package/dist/generators/templates/data/tsConfigTemplate +19 -0
- package/dist/generators/templates/viewTemplates.d.ts +10 -1
- package/dist/generators/templates/viewTemplates.js +138 -6
- package/dist/generators/validationGenerator.d.ts +5 -0
- package/dist/generators/validationGenerator.js +51 -0
- package/dist/utils/constants.d.ts +3 -0
- package/dist/utils/constants.js +5 -2
- package/dist/utils/migrationUtils.d.ts +49 -0
- package/dist/utils/migrationUtils.js +291 -0
- package/howto.md +157 -65
- package/package.json +3 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @template name="main_view" -->
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Your App</title>
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
|
9
|
+
<script src="/app.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class="container-fluid">
|
|
13
|
+
<div id="main">{{ content }}</div>
|
|
14
|
+
</div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ru": {
|
|
3
|
+
"error": "ошибка",
|
|
4
|
+
"success": "успешно",
|
|
5
|
+
"Reloading page...": "Перезагрузка страницы...",
|
|
6
|
+
"Request failed. Please try again.": "Запрос не выполнен. Попробуйте еще раз.",
|
|
7
|
+
"This field is required": "Это поле обязательно",
|
|
8
|
+
"An error occurred": "Произошла ошибка",
|
|
9
|
+
"saved successfully": "успешно сохранено",
|
|
10
|
+
"updated successfully": "успешно обновлено",
|
|
11
|
+
"deleted successfully": "успешно удалено"
|
|
12
|
+
},
|
|
13
|
+
"pl": {
|
|
14
|
+
"error": "błąd",
|
|
15
|
+
"success": "sukces",
|
|
16
|
+
"Reloading page...": "Przeładowywanie strony...",
|
|
17
|
+
"Request failed. Please try again.": "Żądanie nie powiodło się. Spróbuj ponownie.",
|
|
18
|
+
"This field is required": "To pole jest wymagane",
|
|
19
|
+
"An error occurred": "Wystąpił błąd",
|
|
20
|
+
"saved successfully": "zapisano pomyślnie",
|
|
21
|
+
"updated successfully": "zaktualizowano pomyślnie",
|
|
22
|
+
"deleted successfully": "usunięto pomyślnie"
|
|
23
|
+
},
|
|
24
|
+
"es": {
|
|
25
|
+
"error": "error",
|
|
26
|
+
"success": "éxito",
|
|
27
|
+
"Reloading page...": "Recargando página...",
|
|
28
|
+
"Request failed. Please try again.": "La solicitud falló. Por favor, inténtelo de nuevo.",
|
|
29
|
+
"This field is required": "Este campo es obligatorio",
|
|
30
|
+
"An error occurred": "Ha ocurrido un error",
|
|
31
|
+
"saved successfully": "guardado exitosamente",
|
|
32
|
+
"updated successfully": "actualizado exitosamente",
|
|
33
|
+
"deleted successfully": "eliminado exitosamente"
|
|
34
|
+
},
|
|
35
|
+
"de": {
|
|
36
|
+
"error": "Fehler",
|
|
37
|
+
"success": "Erfolg",
|
|
38
|
+
"Reloading page...": "Seite wird neu geladen...",
|
|
39
|
+
"Request failed. Please try again.": "Anfrage fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
|
40
|
+
"This field is required": "Dieses Feld ist erforderlich",
|
|
41
|
+
"An error occurred": "Ein Fehler ist aufgetreten",
|
|
42
|
+
"saved successfully": "erfolgreich gespeichert",
|
|
43
|
+
"updated successfully": "erfolgreich aktualisiert",
|
|
44
|
+
"deleted successfully": "erfolgreich gelöscht"
|
|
45
|
+
},
|
|
46
|
+
"pt": {
|
|
47
|
+
"error": "erro",
|
|
48
|
+
"success": "sucesso",
|
|
49
|
+
"Reloading page...": "Recarregando página...",
|
|
50
|
+
"Request failed. Please try again.": "A solicitação falhou. Por favor, tente novamente.",
|
|
51
|
+
"This field is required": "Este campo é obrigatório",
|
|
52
|
+
"An error occurred": "Ocorreu um erro",
|
|
53
|
+
"saved successfully": "salvo com sucesso",
|
|
54
|
+
"updated successfully": "atualizado com sucesso",
|
|
55
|
+
"deleted successfully": "excluído com sucesso"
|
|
56
|
+
},
|
|
57
|
+
"zh": {
|
|
58
|
+
"error": "错误",
|
|
59
|
+
"success": "成功",
|
|
60
|
+
"Reloading page...": "正在重新加载页面...",
|
|
61
|
+
"Request failed. Please try again.": "请求失败。请再试一次。",
|
|
62
|
+
"This field is required": "此字段为必填项",
|
|
63
|
+
"An error occurred": "发生错误",
|
|
64
|
+
"saved successfully": "保存成功",
|
|
65
|
+
"updated successfully": "更新成功",
|
|
66
|
+
"deleted successfully": "删除成功"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "es2020",
|
|
4
|
+
"target": "es2020",
|
|
5
|
+
"sourceMap": false,
|
|
6
|
+
"experimentalDecorators": true,
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"baseUrl": "./src",
|
|
9
|
+
"outDir": "./build",
|
|
10
|
+
"types": ["node"],
|
|
11
|
+
"moduleResolution": "Node",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": false
|
|
14
|
+
},
|
|
15
|
+
"exclude": [
|
|
16
|
+
"node_modules"
|
|
17
|
+
],
|
|
18
|
+
"type": "module"
|
|
19
|
+
}
|
|
@@ -5,12 +5,21 @@ type FieldConfig = {
|
|
|
5
5
|
auto?: boolean;
|
|
6
6
|
unique?: boolean;
|
|
7
7
|
enum?: string[];
|
|
8
|
+
displayFields?: string[];
|
|
8
9
|
};
|
|
10
|
+
type RelationshipContext = {
|
|
11
|
+
routePaths: Map<string, string>;
|
|
12
|
+
apiPaths: Map<string, string>;
|
|
13
|
+
};
|
|
14
|
+
declare function setAvailableModels(models: string[]): void;
|
|
15
|
+
declare function setRelationshipContext(context: RelationshipContext): void;
|
|
16
|
+
declare function isRelationshipField(field: FieldConfig): boolean;
|
|
17
|
+
declare function getForeignKeyFieldName(field: FieldConfig): string;
|
|
9
18
|
export declare function toFileNameFromTemplateName(name: string): string;
|
|
19
|
+
export { setAvailableModels, setRelationshipContext, isRelationshipField, getForeignKeyFieldName };
|
|
10
20
|
export declare function renderListTemplate(entityName: string, templateName: string, basePath: string, fields: FieldConfig[], apiBase?: string): string;
|
|
11
21
|
export declare function renderDetailTemplate(entityName: string, templateName: string, fields: FieldConfig[]): string;
|
|
12
22
|
export declare function renderCreateTemplate(entityName: string, templateName: string, apiBase: string, fields: FieldConfig[], strategy?: string[], basePath?: string): string;
|
|
13
23
|
export declare function renderUpdateTemplate(entityName: string, templateName: string, apiBase: string, fields: FieldConfig[], strategy?: string[], basePath?: string): string;
|
|
14
24
|
export declare function renderDeleteTemplate(entityName: string, templateName: string, apiBase: string, strategy?: string[], basePath?: string): string;
|
|
15
25
|
export declare function renderLayoutTemplate(layoutName: string): string;
|
|
16
|
-
export {};
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toFileNameFromTemplateName = toFileNameFromTemplateName;
|
|
4
|
+
exports.setAvailableModels = setAvailableModels;
|
|
5
|
+
exports.setRelationshipContext = setRelationshipContext;
|
|
6
|
+
exports.isRelationshipField = isRelationshipField;
|
|
7
|
+
exports.getForeignKeyFieldName = getForeignKeyFieldName;
|
|
4
8
|
exports.renderListTemplate = renderListTemplate;
|
|
5
9
|
exports.renderDetailTemplate = renderDetailTemplate;
|
|
6
10
|
exports.renderCreateTemplate = renderCreateTemplate;
|
|
7
11
|
exports.renderUpdateTemplate = renderUpdateTemplate;
|
|
8
12
|
exports.renderDeleteTemplate = renderDeleteTemplate;
|
|
9
13
|
exports.renderLayoutTemplate = renderLayoutTemplate;
|
|
14
|
+
// Helper to check if a field type is a known model (relationship)
|
|
15
|
+
let availableModels = new Set();
|
|
16
|
+
let relationshipContext = {
|
|
17
|
+
routePaths: new Map(),
|
|
18
|
+
apiPaths: new Map()
|
|
19
|
+
};
|
|
20
|
+
function setAvailableModels(models) {
|
|
21
|
+
availableModels = new Set(models);
|
|
22
|
+
}
|
|
23
|
+
function setRelationshipContext(context) {
|
|
24
|
+
relationshipContext = context;
|
|
25
|
+
}
|
|
26
|
+
function isRelationshipField(field) {
|
|
27
|
+
return availableModels.has(field.type);
|
|
28
|
+
}
|
|
29
|
+
function getForeignKeyFieldName(field) {
|
|
30
|
+
// Convention: fieldName + 'Id' (e.g., owner -> ownerId)
|
|
31
|
+
return field.name + 'Id';
|
|
32
|
+
}
|
|
10
33
|
function toFileNameFromTemplateName(name) {
|
|
11
34
|
const last = name.split('/').pop() || 'template';
|
|
12
35
|
let base = last
|
|
@@ -23,6 +46,51 @@ function generateFormInput(field) {
|
|
|
23
46
|
if (field.auto) {
|
|
24
47
|
return '';
|
|
25
48
|
}
|
|
49
|
+
// Handle relationship fields with a select dropdown
|
|
50
|
+
if (isRelationshipField(field)) {
|
|
51
|
+
const foreignKeyName = getForeignKeyFieldName(field);
|
|
52
|
+
const relatedModel = field.type;
|
|
53
|
+
const relatedModelLower = relatedModel.toLowerCase();
|
|
54
|
+
const displayField = (field.displayFields && field.displayFields.length > 0) ? field.displayFields[0] : 'name';
|
|
55
|
+
// Get actual paths from context, or fallback to defaults
|
|
56
|
+
const routePath = relationshipContext.routePaths.get(relatedModel) || `/${relatedModelLower}/create`;
|
|
57
|
+
const apiPath = relationshipContext.apiPaths.get(relatedModel) || `/api/${relatedModelLower}`;
|
|
58
|
+
return ` <div class="mb-3">
|
|
59
|
+
<label for="${foreignKeyName}" class="form-label">${fieldName}</label>
|
|
60
|
+
<div class="input-group">
|
|
61
|
+
<select id="${foreignKeyName}" name="${foreignKeyName}" class="form-select"${requiredAttr} data-relationship="${relatedModel}">
|
|
62
|
+
<option value="">-- Select ${fieldName} --</option>
|
|
63
|
+
<!-- Options will be populated via JavaScript from ${apiPath} -->
|
|
64
|
+
</select>
|
|
65
|
+
<button type="button" class="btn btn-outline-secondary" onclick="window.open('${routePath}', '${relatedModel}Create', 'width=600,height=400')">
|
|
66
|
+
<i class="bi bi-plus-circle"></i> New
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
<small class="form-text text-muted">Select an existing ${fieldName} or create a new one</small>
|
|
70
|
+
</div>
|
|
71
|
+
<script>
|
|
72
|
+
// Load ${relatedModel} options
|
|
73
|
+
(async () => {
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch('${apiPath}', {
|
|
76
|
+
headers: App.auth.buildAuthHeaders()
|
|
77
|
+
});
|
|
78
|
+
const data = await response.json();
|
|
79
|
+
const select = document.getElementById('${foreignKeyName}');
|
|
80
|
+
if (Array.isArray(data)) {
|
|
81
|
+
data.forEach(item => {
|
|
82
|
+
const option = document.createElement('option');
|
|
83
|
+
option.value = item.id;
|
|
84
|
+
option.textContent = item.${displayField} || item.id;
|
|
85
|
+
select.appendChild(option);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to load ${relatedModel} options:', error);
|
|
90
|
+
}
|
|
91
|
+
})();
|
|
92
|
+
</script>`;
|
|
93
|
+
}
|
|
26
94
|
switch (field.type.toLowerCase()) {
|
|
27
95
|
case 'number':
|
|
28
96
|
case 'int':
|
|
@@ -79,6 +147,55 @@ function generateUpdateFormInput(field) {
|
|
|
79
147
|
if (field.auto) {
|
|
80
148
|
return '';
|
|
81
149
|
}
|
|
150
|
+
// Handle relationship fields with a select dropdown
|
|
151
|
+
if (isRelationshipField(field)) {
|
|
152
|
+
const foreignKeyName = getForeignKeyFieldName(field);
|
|
153
|
+
const relatedModel = field.type;
|
|
154
|
+
const relatedModelLower = relatedModel.toLowerCase();
|
|
155
|
+
const displayField = (field.displayFields && field.displayFields.length > 0) ? field.displayFields[0] : 'name';
|
|
156
|
+
// Get actual paths from context, or fallback to defaults
|
|
157
|
+
const routePath = relationshipContext.routePaths.get(relatedModel) || `/${relatedModelLower}/create`;
|
|
158
|
+
const apiPath = relationshipContext.apiPaths.get(relatedModel) || `/api/${relatedModelLower}`;
|
|
159
|
+
return ` <div class="mb-3">
|
|
160
|
+
<label for="${foreignKeyName}" class="form-label">${fieldName}</label>
|
|
161
|
+
<div class="input-group">
|
|
162
|
+
<select id="${foreignKeyName}" name="${foreignKeyName}" class="form-select"${requiredAttr} data-relationship="${relatedModel}" data-current-value="{{ $root.${foreignKeyName} }}">
|
|
163
|
+
<option value="">-- Select ${fieldName} --</option>
|
|
164
|
+
<!-- Options will be populated via JavaScript from ${apiPath} -->
|
|
165
|
+
</select>
|
|
166
|
+
<button type="button" class="btn btn-outline-secondary" onclick="window.open('${routePath}', '${relatedModel}Create', 'width=600,height=400')">
|
|
167
|
+
<i class="bi bi-plus-circle"></i> New
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
<small class="form-text text-muted">Select an existing ${fieldName} or create a new one</small>
|
|
171
|
+
</div>
|
|
172
|
+
<script>
|
|
173
|
+
// Load ${relatedModel} options
|
|
174
|
+
(async () => {
|
|
175
|
+
try {
|
|
176
|
+
const response = await fetch('${apiPath}', {
|
|
177
|
+
headers: App.auth.buildAuthHeaders()
|
|
178
|
+
});
|
|
179
|
+
const data = await response.json();
|
|
180
|
+
const select = document.getElementById('${foreignKeyName}');
|
|
181
|
+
const currentValue = select.getAttribute('data-current-value');
|
|
182
|
+
if (Array.isArray(data)) {
|
|
183
|
+
data.forEach(item => {
|
|
184
|
+
const option = document.createElement('option');
|
|
185
|
+
option.value = item.id;
|
|
186
|
+
option.textContent = item.${displayField} || item.id;
|
|
187
|
+
if (currentValue && item.id == currentValue) {
|
|
188
|
+
option.selected = true;
|
|
189
|
+
}
|
|
190
|
+
select.appendChild(option);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Failed to load ${relatedModel} options:', error);
|
|
195
|
+
}
|
|
196
|
+
})();
|
|
197
|
+
</script>`;
|
|
198
|
+
}
|
|
82
199
|
switch (field.type.toLowerCase()) {
|
|
83
200
|
case 'number':
|
|
84
201
|
case 'int':
|
|
@@ -127,11 +244,19 @@ ${options}
|
|
|
127
244
|
}
|
|
128
245
|
}
|
|
129
246
|
function renderListTemplate(entityName, templateName, basePath, fields, apiBase) {
|
|
130
|
-
const safeFields = fields.length > 0 ? fields
|
|
131
|
-
const headers = ['ID', ...safeFields.filter(f => f !== 'id').map(f => f.charAt(0).toUpperCase() + f.slice(1).replace(/_/g, ' '))].map(f => `<th scope="col">${f}</th>`).join('');
|
|
247
|
+
const safeFields = fields.length > 0 ? fields : [{ name: 'id', type: 'number' }, { name: 'name', type: 'string' }];
|
|
248
|
+
const headers = ['ID', ...safeFields.filter(f => f.name !== 'id').map(f => f.name.charAt(0).toUpperCase() + f.name.slice(1).replace(/_/g, ' '))].map(f => `<th scope="col">${f}</th>`).join('');
|
|
249
|
+
// Generate cells with relationship field handling
|
|
132
250
|
const cells = [
|
|
133
251
|
'<td>{{ row.id }}</td>',
|
|
134
|
-
...safeFields.filter(f => f !== 'id').map(f =>
|
|
252
|
+
...safeFields.filter(f => f.name !== 'id').map(f => {
|
|
253
|
+
// For relationship fields, show a specific field from the related object
|
|
254
|
+
if (isRelationshipField(f)) {
|
|
255
|
+
const displayField = (f.displayFields && f.displayFields.length > 0) ? f.displayFields[0] : 'name';
|
|
256
|
+
return `<td>{{ row.${f.name}.${displayField} }}</td>`;
|
|
257
|
+
}
|
|
258
|
+
return `<td>{{ row.${f.name} }}</td>`;
|
|
259
|
+
})
|
|
135
260
|
].join('\n ');
|
|
136
261
|
const deleteAction = apiBase ? `${apiBase}/{{ row.id }}` : `${basePath}/api/{{ row.id }}`;
|
|
137
262
|
return `<!-- @template name="${templateName}" -->
|
|
@@ -187,10 +312,17 @@ function renderListTemplate(entityName, templateName, basePath, fields, apiBase)
|
|
|
187
312
|
`;
|
|
188
313
|
}
|
|
189
314
|
function renderDetailTemplate(entityName, templateName, fields) {
|
|
190
|
-
const safeFields = fields.length > 0 ? fields
|
|
315
|
+
const safeFields = fields.length > 0 ? fields : [{ name: 'id', type: 'number' }, { name: 'name', type: 'string' }];
|
|
191
316
|
const rows = safeFields.map(f => {
|
|
192
|
-
const fieldName = f.charAt(0).toUpperCase() + f.slice(1).replace(/_/g, ' ');
|
|
193
|
-
|
|
317
|
+
const fieldName = f.name.charAt(0).toUpperCase() + f.name.slice(1).replace(/_/g, ' ');
|
|
318
|
+
// For relationship fields, show specific fields from the related object
|
|
319
|
+
if (isRelationshipField(f)) {
|
|
320
|
+
const displayFields = (f.displayFields && f.displayFields.length > 0) ? f.displayFields : ['name'];
|
|
321
|
+
// Show all displayFields separated by comma
|
|
322
|
+
const fieldAccess = displayFields.map(df => `{{ $root.${f.name}.${df} }}`).join(', ');
|
|
323
|
+
return ` <tr><th scope="row" class="w-25">${fieldName}</th><td>${fieldAccess}</td></tr>`;
|
|
324
|
+
}
|
|
325
|
+
return ` <tr><th scope="row" class="w-25">${fieldName}</th><td>{{ $root.${f.name} }}</td></tr>`;
|
|
194
326
|
}).join('\n');
|
|
195
327
|
return `<!-- @template name="${templateName}" -->
|
|
196
328
|
<div class="container-fluid py-4">
|
|
@@ -4,8 +4,13 @@ interface FieldConfig {
|
|
|
4
4
|
required?: boolean;
|
|
5
5
|
auto?: boolean;
|
|
6
6
|
unique?: boolean;
|
|
7
|
+
displayFields?: string[];
|
|
7
8
|
}
|
|
8
9
|
export declare class ValidationGenerator {
|
|
10
|
+
private availableModels;
|
|
11
|
+
private setAvailableModels;
|
|
12
|
+
private isRelationshipField;
|
|
13
|
+
private getForeignKeyFieldName;
|
|
9
14
|
private replaceTemplateVars;
|
|
10
15
|
private getTypeScriptType;
|
|
11
16
|
private generateInterfaceField;
|
|
@@ -42,6 +42,22 @@ const generationRegistry_1 = require("../utils/generationRegistry");
|
|
|
42
42
|
const colors_1 = require("../utils/colors");
|
|
43
43
|
const constants_1 = require("../utils/constants");
|
|
44
44
|
class ValidationGenerator {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.availableModels = new Set();
|
|
47
|
+
}
|
|
48
|
+
setAvailableModels(models) {
|
|
49
|
+
this.availableModels.clear();
|
|
50
|
+
models.forEach(model => {
|
|
51
|
+
this.availableModels.add(model.name);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
isRelationshipField(field) {
|
|
55
|
+
return this.availableModels.has(field.type);
|
|
56
|
+
}
|
|
57
|
+
getForeignKeyFieldName(field) {
|
|
58
|
+
// Convention: fieldName + 'Id' (e.g., owner -> ownerId)
|
|
59
|
+
return field.name + 'Id';
|
|
60
|
+
}
|
|
45
61
|
replaceTemplateVars(template, variables) {
|
|
46
62
|
let result = template;
|
|
47
63
|
Object.entries(variables).forEach(([key, value]) => {
|
|
@@ -54,6 +70,18 @@ class ValidationGenerator {
|
|
|
54
70
|
return validationTemplates_1.typeMapping[yamlType] || 'any';
|
|
55
71
|
}
|
|
56
72
|
generateInterfaceField(field, isCreate) {
|
|
73
|
+
// For relationship fields, use the foreign key field instead
|
|
74
|
+
if (this.isRelationshipField(field)) {
|
|
75
|
+
const foreignKeyName = this.getForeignKeyFieldName(field);
|
|
76
|
+
const tsType = 'number'; // Foreign keys are always numbers
|
|
77
|
+
if (isCreate) {
|
|
78
|
+
const optional = !field.required ? '?' : '';
|
|
79
|
+
return ` ${foreignKeyName}${optional}: ${tsType};`;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
return ` ${foreignKeyName}?: ${tsType};`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
57
85
|
const tsType = this.getTypeScriptType(field.type);
|
|
58
86
|
if (isCreate) {
|
|
59
87
|
if (field.auto || field.name === 'id') {
|
|
@@ -70,6 +98,13 @@ class ValidationGenerator {
|
|
|
70
98
|
}
|
|
71
99
|
}
|
|
72
100
|
generateValidationLogic(field, isCreate) {
|
|
101
|
+
// For relationship fields, validate the foreign key
|
|
102
|
+
if (this.isRelationshipField(field)) {
|
|
103
|
+
const foreignKeyName = this.getForeignKeyFieldName(field);
|
|
104
|
+
const isRequired = isCreate && field.required;
|
|
105
|
+
const template = isRequired ? validationTemplates_1.validationTemplates.requiredNumberValidation : validationTemplates_1.validationTemplates.optionalNumberValidation;
|
|
106
|
+
return this.replaceTemplateVars(template, { FIELD_NAME: foreignKeyName });
|
|
107
|
+
}
|
|
73
108
|
const fieldName = field.name;
|
|
74
109
|
if (field.auto || field.name === 'id') {
|
|
75
110
|
return '';
|
|
@@ -96,6 +131,18 @@ class ValidationGenerator {
|
|
|
96
131
|
return this.replaceTemplateVars(template, { FIELD_NAME: fieldName });
|
|
97
132
|
}
|
|
98
133
|
generateDtoField(field, isCreate) {
|
|
134
|
+
// For relationship fields, use the foreign key field instead
|
|
135
|
+
if (this.isRelationshipField(field)) {
|
|
136
|
+
const foreignKeyName = this.getForeignKeyFieldName(field);
|
|
137
|
+
const tsType = 'number'; // Foreign keys are always numbers
|
|
138
|
+
if (isCreate) {
|
|
139
|
+
const optional = !field.required ? '?' : '';
|
|
140
|
+
return ` ${foreignKeyName}${optional}: ${tsType};`;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
return ` ${foreignKeyName}?: ${tsType};`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
99
146
|
const tsType = this.getTypeScriptType(field.type);
|
|
100
147
|
if (isCreate) {
|
|
101
148
|
if (field.auto || field.name === 'id') {
|
|
@@ -163,6 +210,8 @@ class ValidationGenerator {
|
|
|
163
210
|
if (config.modules) {
|
|
164
211
|
Object.values(config.modules).forEach(moduleConfig => {
|
|
165
212
|
if (moduleConfig.models && moduleConfig.models.length > 0) {
|
|
213
|
+
// Set available models for relationship detection
|
|
214
|
+
this.setAvailableModels(moduleConfig.models);
|
|
166
215
|
moduleConfig.models.forEach(model => {
|
|
167
216
|
const validationCode = this.generateValidation(model.name, model.fields);
|
|
168
217
|
validations[model.name] = validationCode;
|
|
@@ -173,6 +222,8 @@ class ValidationGenerator {
|
|
|
173
222
|
else if (config.models) {
|
|
174
223
|
const module = config;
|
|
175
224
|
if (module.models) {
|
|
225
|
+
// Set available models for relationship detection
|
|
226
|
+
this.setAvailableModels(module.models);
|
|
176
227
|
module.models.forEach(model => {
|
|
177
228
|
const validationCode = this.generateValidation(model.name, model.fields);
|
|
178
229
|
validations[model.name] = validationCode;
|
|
@@ -9,6 +9,8 @@ export declare const COMMON_FILES: {
|
|
|
9
9
|
readonly APP_TS: "app.ts";
|
|
10
10
|
readonly REGISTRY_JSON: "registry.json";
|
|
11
11
|
readonly STORE_INTERFACE: "StoreInterface.ts";
|
|
12
|
+
readonly SCHEMA_STATE: "schema_state.yaml";
|
|
13
|
+
readonly MIGRATION_LOG: "migration_log.json";
|
|
12
14
|
};
|
|
13
15
|
export declare const GENERATOR_MARKERS: {
|
|
14
16
|
readonly CONTROLLERS_START: "// currentjs:controllers:start";
|
|
@@ -26,6 +28,7 @@ export declare const PATH_PATTERNS: {
|
|
|
26
28
|
readonly STORES: "stores";
|
|
27
29
|
readonly SERVICES: "services";
|
|
28
30
|
readonly CONTROLLERS: "controllers";
|
|
31
|
+
readonly MIGRATIONS: "migrations";
|
|
29
32
|
};
|
|
30
33
|
export declare const DEFAULTS: {
|
|
31
34
|
readonly SERVER_PORT: 3000;
|
package/dist/utils/constants.js
CHANGED
|
@@ -14,7 +14,9 @@ exports.COMMON_FILES = {
|
|
|
14
14
|
APP_YAML: 'app.yaml',
|
|
15
15
|
APP_TS: 'app.ts',
|
|
16
16
|
REGISTRY_JSON: 'registry.json',
|
|
17
|
-
STORE_INTERFACE: 'StoreInterface.ts'
|
|
17
|
+
STORE_INTERFACE: 'StoreInterface.ts',
|
|
18
|
+
SCHEMA_STATE: 'schema_state.yaml',
|
|
19
|
+
MIGRATION_LOG: 'migration_log.json'
|
|
18
20
|
};
|
|
19
21
|
// Generator markers
|
|
20
22
|
exports.GENERATOR_MARKERS = {
|
|
@@ -33,7 +35,8 @@ exports.PATH_PATTERNS = {
|
|
|
33
35
|
ENTITIES: 'entities',
|
|
34
36
|
STORES: 'stores',
|
|
35
37
|
SERVICES: 'services',
|
|
36
|
-
CONTROLLERS: 'controllers'
|
|
38
|
+
CONTROLLERS: 'controllers',
|
|
39
|
+
MIGRATIONS: 'migrations'
|
|
37
40
|
};
|
|
38
41
|
// Default values
|
|
39
42
|
exports.DEFAULTS = {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface FieldConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required?: boolean;
|
|
5
|
+
unique?: boolean;
|
|
6
|
+
auto?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ModelConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
fields: FieldConfig[];
|
|
11
|
+
}
|
|
12
|
+
export interface SchemaState {
|
|
13
|
+
models: ModelConfig[];
|
|
14
|
+
version: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MigrationLog {
|
|
18
|
+
appliedMigrations: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface ColumnInfo {
|
|
21
|
+
Field: string;
|
|
22
|
+
Type: string;
|
|
23
|
+
Null: string;
|
|
24
|
+
Key: string;
|
|
25
|
+
Default: string | null;
|
|
26
|
+
Extra: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ForeignKeyInfo {
|
|
29
|
+
CONSTRAINT_NAME: string;
|
|
30
|
+
COLUMN_NAME: string;
|
|
31
|
+
REFERENCED_TABLE_NAME: string;
|
|
32
|
+
REFERENCED_COLUMN_NAME: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function mapYamlTypeToSql(yamlType: string, availableModels: Set<string>): string;
|
|
35
|
+
export declare function getTableName(modelName: string): string;
|
|
36
|
+
export declare function getForeignKeyFieldName(fieldName: string): string;
|
|
37
|
+
export declare function isRelationshipField(fieldType: string, availableModels: Set<string>): boolean;
|
|
38
|
+
export declare function generateCreateTableSQL(model: ModelConfig, availableModels: Set<string>): string;
|
|
39
|
+
export declare function generateDropTableSQL(tableName: string): string;
|
|
40
|
+
export declare function generateAddColumnSQL(tableName: string, field: FieldConfig, availableModels: Set<string>): string;
|
|
41
|
+
export declare function generateDropColumnSQL(tableName: string, columnName: string): string;
|
|
42
|
+
export declare function generateModifyColumnSQL(tableName: string, field: FieldConfig, availableModels: Set<string>): string;
|
|
43
|
+
export declare function loadSchemaState(stateFilePath: string): SchemaState | null;
|
|
44
|
+
export declare function saveSchemaState(stateFilePath: string, state: SchemaState): void;
|
|
45
|
+
export declare function loadMigrationLog(logFilePath: string): MigrationLog;
|
|
46
|
+
export declare function saveMigrationLog(logFilePath: string, log: MigrationLog): void;
|
|
47
|
+
export declare function compareSchemas(oldState: SchemaState | null, newModels: ModelConfig[]): string[];
|
|
48
|
+
export declare function generateTimestamp(): string;
|
|
49
|
+
export declare function getMigrationFileName(timestamp: string): string;
|