@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,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.mapYamlTypeToSql = mapYamlTypeToSql;
|
|
37
|
+
exports.getTableName = getTableName;
|
|
38
|
+
exports.getForeignKeyFieldName = getForeignKeyFieldName;
|
|
39
|
+
exports.isRelationshipField = isRelationshipField;
|
|
40
|
+
exports.generateCreateTableSQL = generateCreateTableSQL;
|
|
41
|
+
exports.generateDropTableSQL = generateDropTableSQL;
|
|
42
|
+
exports.generateAddColumnSQL = generateAddColumnSQL;
|
|
43
|
+
exports.generateDropColumnSQL = generateDropColumnSQL;
|
|
44
|
+
exports.generateModifyColumnSQL = generateModifyColumnSQL;
|
|
45
|
+
exports.loadSchemaState = loadSchemaState;
|
|
46
|
+
exports.saveSchemaState = saveSchemaState;
|
|
47
|
+
exports.loadMigrationLog = loadMigrationLog;
|
|
48
|
+
exports.saveMigrationLog = saveMigrationLog;
|
|
49
|
+
exports.compareSchemas = compareSchemas;
|
|
50
|
+
exports.generateTimestamp = generateTimestamp;
|
|
51
|
+
exports.getMigrationFileName = getMigrationFileName;
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const yaml_1 = require("yaml");
|
|
55
|
+
const TYPE_MAPPING = {
|
|
56
|
+
string: 'VARCHAR(255)',
|
|
57
|
+
number: 'INT',
|
|
58
|
+
boolean: 'TINYINT(1)',
|
|
59
|
+
datetime: 'DATETIME',
|
|
60
|
+
json: 'JSON',
|
|
61
|
+
array: 'JSON',
|
|
62
|
+
object: 'JSON'
|
|
63
|
+
};
|
|
64
|
+
function mapYamlTypeToSql(yamlType, availableModels) {
|
|
65
|
+
// Check if this is a relationship (foreign key)
|
|
66
|
+
if (availableModels.has(yamlType)) {
|
|
67
|
+
return 'INT'; // Foreign keys are INT
|
|
68
|
+
}
|
|
69
|
+
return TYPE_MAPPING[yamlType] || 'VARCHAR(255)';
|
|
70
|
+
}
|
|
71
|
+
function getTableName(modelName) {
|
|
72
|
+
return modelName.toLowerCase() + 's';
|
|
73
|
+
}
|
|
74
|
+
function getForeignKeyFieldName(fieldName) {
|
|
75
|
+
return fieldName + 'Id';
|
|
76
|
+
}
|
|
77
|
+
function isRelationshipField(fieldType, availableModels) {
|
|
78
|
+
return availableModels.has(fieldType);
|
|
79
|
+
}
|
|
80
|
+
function generateCreateTableSQL(model, availableModels) {
|
|
81
|
+
const tableName = getTableName(model.name);
|
|
82
|
+
const columns = [];
|
|
83
|
+
const indexes = [];
|
|
84
|
+
const foreignKeys = [];
|
|
85
|
+
// Add id column
|
|
86
|
+
columns.push(' id INT AUTO_INCREMENT PRIMARY KEY');
|
|
87
|
+
// Add model fields
|
|
88
|
+
model.fields.forEach(field => {
|
|
89
|
+
if (isRelationshipField(field.type, availableModels)) {
|
|
90
|
+
// Foreign key field
|
|
91
|
+
const foreignKeyName = getForeignKeyFieldName(field.name);
|
|
92
|
+
const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
|
|
93
|
+
columns.push(` ${foreignKeyName} INT ${nullable}`);
|
|
94
|
+
// Add index for foreign key
|
|
95
|
+
indexes.push(` INDEX idx_${tableName}_${foreignKeyName} (${foreignKeyName})`);
|
|
96
|
+
// Add foreign key constraint
|
|
97
|
+
const refTableName = getTableName(field.type);
|
|
98
|
+
foreignKeys.push(` CONSTRAINT fk_${tableName}_${foreignKeyName} \n` +
|
|
99
|
+
` FOREIGN KEY (${foreignKeyName}) \n` +
|
|
100
|
+
` REFERENCES ${refTableName}(id) \n` +
|
|
101
|
+
` ON DELETE RESTRICT \n` +
|
|
102
|
+
` ON UPDATE CASCADE`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Regular field
|
|
106
|
+
const sqlType = mapYamlTypeToSql(field.type, availableModels);
|
|
107
|
+
const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
|
|
108
|
+
columns.push(` ${field.name} ${sqlType} ${nullable}`);
|
|
109
|
+
// Add index for filterable fields
|
|
110
|
+
if (['string', 'number'].includes(field.type)) {
|
|
111
|
+
indexes.push(` INDEX idx_${tableName}_${field.name} (${field.name})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Add standard timestamp columns
|
|
116
|
+
columns.push(' created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
|
|
117
|
+
columns.push(' updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
118
|
+
columns.push(' deleted_at DATETIME NULL DEFAULT NULL');
|
|
119
|
+
// Add standard indexes
|
|
120
|
+
indexes.push(` INDEX idx_${tableName}_deleted_at (deleted_at)`);
|
|
121
|
+
indexes.push(` INDEX idx_${tableName}_created_at (created_at)`);
|
|
122
|
+
// Combine all parts
|
|
123
|
+
const allParts = [...columns, ...indexes, ...foreignKeys];
|
|
124
|
+
const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (\n${allParts.join(',\n')}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`;
|
|
125
|
+
return sql;
|
|
126
|
+
}
|
|
127
|
+
function generateDropTableSQL(tableName) {
|
|
128
|
+
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
129
|
+
}
|
|
130
|
+
function generateAddColumnSQL(tableName, field, availableModels) {
|
|
131
|
+
if (isRelationshipField(field.type, availableModels)) {
|
|
132
|
+
const foreignKeyName = getForeignKeyFieldName(field.name);
|
|
133
|
+
const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
|
|
134
|
+
const sqlType = 'INT';
|
|
135
|
+
return `ALTER TABLE ${tableName} ADD COLUMN ${foreignKeyName} ${sqlType} ${nullable};`;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const sqlType = mapYamlTypeToSql(field.type, availableModels);
|
|
139
|
+
const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
|
|
140
|
+
return `ALTER TABLE ${tableName} ADD COLUMN ${field.name} ${sqlType} ${nullable};`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function generateDropColumnSQL(tableName, columnName) {
|
|
144
|
+
return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
|
|
145
|
+
}
|
|
146
|
+
function generateModifyColumnSQL(tableName, field, availableModels) {
|
|
147
|
+
if (isRelationshipField(field.type, availableModels)) {
|
|
148
|
+
const foreignKeyName = getForeignKeyFieldName(field.name);
|
|
149
|
+
const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
|
|
150
|
+
const sqlType = 'INT';
|
|
151
|
+
return `ALTER TABLE ${tableName} MODIFY COLUMN ${foreignKeyName} ${sqlType} ${nullable};`;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const sqlType = mapYamlTypeToSql(field.type, availableModels);
|
|
155
|
+
const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
|
|
156
|
+
return `ALTER TABLE ${tableName} MODIFY COLUMN ${field.name} ${sqlType} ${nullable};`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function loadSchemaState(stateFilePath) {
|
|
160
|
+
if (!fs.existsSync(stateFilePath)) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const content = fs.readFileSync(stateFilePath, 'utf8');
|
|
164
|
+
return (0, yaml_1.parse)(content);
|
|
165
|
+
}
|
|
166
|
+
function saveSchemaState(stateFilePath, state) {
|
|
167
|
+
fs.mkdirSync(path.dirname(stateFilePath), { recursive: true });
|
|
168
|
+
fs.writeFileSync(stateFilePath, (0, yaml_1.stringify)(state));
|
|
169
|
+
}
|
|
170
|
+
function loadMigrationLog(logFilePath) {
|
|
171
|
+
if (!fs.existsSync(logFilePath)) {
|
|
172
|
+
return { appliedMigrations: [] };
|
|
173
|
+
}
|
|
174
|
+
const content = fs.readFileSync(logFilePath, 'utf8');
|
|
175
|
+
return JSON.parse(content);
|
|
176
|
+
}
|
|
177
|
+
function saveMigrationLog(logFilePath, log) {
|
|
178
|
+
fs.mkdirSync(path.dirname(logFilePath), { recursive: true });
|
|
179
|
+
fs.writeFileSync(logFilePath, JSON.stringify(log, null, 2));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Sort models by dependencies so tables are created in the right order
|
|
183
|
+
* (tables with no foreign keys first, then tables that depend on them)
|
|
184
|
+
*/
|
|
185
|
+
function sortModelsByDependencies(models, availableModels) {
|
|
186
|
+
const sorted = [];
|
|
187
|
+
const processed = new Set();
|
|
188
|
+
const addModel = (model) => {
|
|
189
|
+
if (processed.has(model.name))
|
|
190
|
+
return;
|
|
191
|
+
// Find foreign key dependencies
|
|
192
|
+
const dependencies = [];
|
|
193
|
+
model.fields.forEach(field => {
|
|
194
|
+
if (isRelationshipField(field.type, availableModels)) {
|
|
195
|
+
dependencies.push(field.type);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// Add dependencies first
|
|
199
|
+
dependencies.forEach(depName => {
|
|
200
|
+
const depModel = models.find(m => m.name === depName);
|
|
201
|
+
if (depModel && !processed.has(depName)) {
|
|
202
|
+
addModel(depModel);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
// Then add this model
|
|
206
|
+
sorted.push(model);
|
|
207
|
+
processed.add(model.name);
|
|
208
|
+
};
|
|
209
|
+
models.forEach(model => addModel(model));
|
|
210
|
+
return sorted;
|
|
211
|
+
}
|
|
212
|
+
function compareSchemas(oldState, newModels) {
|
|
213
|
+
const sqlStatements = [];
|
|
214
|
+
const availableModels = new Set(newModels.map(m => m.name));
|
|
215
|
+
if (!oldState || oldState.models.length === 0) {
|
|
216
|
+
// No previous state - generate all CREATE TABLE statements in dependency order
|
|
217
|
+
const sortedModels = sortModelsByDependencies(newModels, availableModels);
|
|
218
|
+
sortedModels.forEach(model => {
|
|
219
|
+
sqlStatements.push(`-- Create ${model.name.toLowerCase()}s table`);
|
|
220
|
+
sqlStatements.push(generateCreateTableSQL(model, availableModels));
|
|
221
|
+
sqlStatements.push('');
|
|
222
|
+
});
|
|
223
|
+
return sqlStatements;
|
|
224
|
+
}
|
|
225
|
+
// Create maps for easy lookup
|
|
226
|
+
const oldModelsMap = new Map(oldState.models.map(m => [m.name, m]));
|
|
227
|
+
const newModelsMap = new Map(newModels.map(m => [m.name, m]));
|
|
228
|
+
// Find dropped tables
|
|
229
|
+
oldState.models.forEach(oldModel => {
|
|
230
|
+
if (!newModelsMap.has(oldModel.name)) {
|
|
231
|
+
const tableName = getTableName(oldModel.name);
|
|
232
|
+
sqlStatements.push(`-- Drop ${tableName} table`);
|
|
233
|
+
sqlStatements.push(generateDropTableSQL(tableName));
|
|
234
|
+
sqlStatements.push('');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
// Find new tables and modified tables
|
|
238
|
+
newModels.forEach(newModel => {
|
|
239
|
+
const oldModel = oldModelsMap.get(newModel.name);
|
|
240
|
+
const tableName = getTableName(newModel.name);
|
|
241
|
+
if (!oldModel) {
|
|
242
|
+
// New table
|
|
243
|
+
sqlStatements.push(`-- Create ${tableName} table`);
|
|
244
|
+
sqlStatements.push(generateCreateTableSQL(newModel, availableModels));
|
|
245
|
+
sqlStatements.push('');
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Table exists - check for column changes
|
|
249
|
+
const oldFieldsMap = new Map(oldModel.fields.map(f => [f.name, f]));
|
|
250
|
+
const newFieldsMap = new Map(newModel.fields.map(f => [f.name, f]));
|
|
251
|
+
// Find dropped columns
|
|
252
|
+
oldModel.fields.forEach(oldField => {
|
|
253
|
+
if (!newFieldsMap.has(oldField.name)) {
|
|
254
|
+
const columnName = isRelationshipField(oldField.type, availableModels)
|
|
255
|
+
? getForeignKeyFieldName(oldField.name)
|
|
256
|
+
: oldField.name;
|
|
257
|
+
sqlStatements.push(`-- Drop column ${columnName} from ${tableName}`);
|
|
258
|
+
sqlStatements.push(generateDropColumnSQL(tableName, columnName));
|
|
259
|
+
sqlStatements.push('');
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
// Find new columns and modified columns
|
|
263
|
+
newModel.fields.forEach(newField => {
|
|
264
|
+
const oldField = oldFieldsMap.get(newField.name);
|
|
265
|
+
if (!oldField) {
|
|
266
|
+
// New column
|
|
267
|
+
sqlStatements.push(`-- Add column ${newField.name} to ${tableName}`);
|
|
268
|
+
sqlStatements.push(generateAddColumnSQL(tableName, newField, availableModels));
|
|
269
|
+
sqlStatements.push('');
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Check if column definition changed
|
|
273
|
+
const typeChanged = oldField.type !== newField.type;
|
|
274
|
+
const requiredChanged = oldField.required !== newField.required;
|
|
275
|
+
if (typeChanged || requiredChanged) {
|
|
276
|
+
sqlStatements.push(`-- Modify column ${newField.name} in ${tableName}`);
|
|
277
|
+
sqlStatements.push(generateModifyColumnSQL(tableName, newField, availableModels));
|
|
278
|
+
sqlStatements.push('');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
return sqlStatements;
|
|
285
|
+
}
|
|
286
|
+
function generateTimestamp() {
|
|
287
|
+
return new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, -5);
|
|
288
|
+
}
|
|
289
|
+
function getMigrationFileName(timestamp) {
|
|
290
|
+
return `${timestamp}.sql`;
|
|
291
|
+
}
|
package/howto.md
CHANGED
|
@@ -153,6 +153,110 @@ permissions: # Role-based access control
|
|
|
153
153
|
- `number` - Numeric data (integer or float)
|
|
154
154
|
- `boolean` - True/false values
|
|
155
155
|
- `datetime` - Date and time values
|
|
156
|
+
- `ModelName` - Relationship to another model (e.g., `Owner`, `User`, `Post`)
|
|
157
|
+
|
|
158
|
+
**Multi-Model Endpoint Configuration:**
|
|
159
|
+
|
|
160
|
+
When working with multiple models in a single module, you have flexible options:
|
|
161
|
+
|
|
162
|
+
**Option 1: Per-Endpoint Model Override**
|
|
163
|
+
|
|
164
|
+
Specify `model` on individual endpoints to override the section default:
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
models:
|
|
168
|
+
- name: Cat
|
|
169
|
+
- name: Person
|
|
170
|
+
# do not forget to describe fields of each model
|
|
171
|
+
|
|
172
|
+
routes:
|
|
173
|
+
prefix: /cat
|
|
174
|
+
model: Cat # Default for this section
|
|
175
|
+
endpoints:
|
|
176
|
+
- path: /create
|
|
177
|
+
view: catCreate
|
|
178
|
+
# Uses Cat model
|
|
179
|
+
|
|
180
|
+
- path: /createOwner
|
|
181
|
+
view: ownerCreate
|
|
182
|
+
model: Person # Override for this endpoint
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Option 2: Multiple API/Routes Sections**
|
|
186
|
+
|
|
187
|
+
Use arrays to organize endpoints by model:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
routes:
|
|
191
|
+
- prefix: /cat
|
|
192
|
+
model: Cat
|
|
193
|
+
endpoints: [...]
|
|
194
|
+
|
|
195
|
+
- prefix: /person
|
|
196
|
+
model: Person
|
|
197
|
+
endpoints: [...]
|
|
198
|
+
|
|
199
|
+
# Same works for api sections
|
|
200
|
+
api:
|
|
201
|
+
- prefix: /api/cat
|
|
202
|
+
model: Cat
|
|
203
|
+
endpoints: [...]
|
|
204
|
+
|
|
205
|
+
- prefix: /api/person
|
|
206
|
+
model: Person
|
|
207
|
+
endpoints: [...]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Model Resolution Priority:**
|
|
211
|
+
1. `endpoint.model` (explicit override)
|
|
212
|
+
2. Inferred from action handler (e.g., `Person:default:create`)
|
|
213
|
+
3. `api.model` or `routes.model` (section default)
|
|
214
|
+
4. First model in `models[]` array (fallback)
|
|
215
|
+
|
|
216
|
+
**🔗 Model Relationships:**
|
|
217
|
+
|
|
218
|
+
Define relationships by using another model's name as the field type:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
models:
|
|
222
|
+
- name: Owner
|
|
223
|
+
fields:
|
|
224
|
+
- name: name
|
|
225
|
+
type: string
|
|
226
|
+
required: true
|
|
227
|
+
|
|
228
|
+
- name: Cat
|
|
229
|
+
fields:
|
|
230
|
+
- name: name
|
|
231
|
+
type: string
|
|
232
|
+
required: true
|
|
233
|
+
- name: owner
|
|
234
|
+
type: Owner # Creates relationship with Owner model
|
|
235
|
+
required: true # Auto-generates foreign key: ownerId
|
|
236
|
+
displayFields: [name] # Optional: fields to show in dropdowns
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Generated Behavior:**
|
|
240
|
+
- **Domain Model**: Rich object with `owner: Owner` (full object, not just ID)
|
|
241
|
+
- **DTOs**: Use `ownerId: number` for API requests
|
|
242
|
+
- **Database**: Stores `ownerId` foreign key column (references `Owner.id`)
|
|
243
|
+
- **Store**: Automatically loads the related Owner object when fetching a Cat
|
|
244
|
+
- **HTML Forms**: Auto-generates select dropdown with "Create New" button
|
|
245
|
+
- **TypeScript**: Full type safety with proper imports
|
|
246
|
+
|
|
247
|
+
**Naming Convention:**
|
|
248
|
+
Foreign keys are automatically generated following the pattern `fieldName + 'Id'`:
|
|
249
|
+
- `owner` → `ownerId`
|
|
250
|
+
- `author` → `authorId`
|
|
251
|
+
- `parentComment` → `parentCommentId`
|
|
252
|
+
|
|
253
|
+
**Optional Configuration:**
|
|
254
|
+
```yaml
|
|
255
|
+
- name: author
|
|
256
|
+
type: User
|
|
257
|
+
required: true
|
|
258
|
+
displayFields: [name, email] # Show in dropdown (optional)
|
|
259
|
+
```
|
|
156
260
|
|
|
157
261
|
**🔄 Handler vs Action Architecture:**
|
|
158
262
|
- **Handler**: Creates a separate service method (one handler = one service method)
|
|
@@ -374,13 +478,13 @@ try {
|
|
|
374
478
|
**Basic Usage:**
|
|
375
479
|
```javascript
|
|
376
480
|
// Translate strings
|
|
377
|
-
t('Hello World') // Returns translated version or original
|
|
378
|
-
t('Save changes')
|
|
481
|
+
App.lang.t('Hello World') // Returns translated version or original
|
|
482
|
+
App.lang.t('Save changes')
|
|
379
483
|
|
|
380
484
|
// Set language
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
485
|
+
App.lang.set('pl') // Switch to Polish
|
|
486
|
+
App.lang.set('en') // Switch to English
|
|
487
|
+
App.lang.get() // Get current language code
|
|
384
488
|
```
|
|
385
489
|
|
|
386
490
|
**Translation File (web/translations.json):**
|
|
@@ -402,43 +506,35 @@ getCurrentLanguage() // Get current language code
|
|
|
402
506
|
|
|
403
507
|
**Toast Notifications:**
|
|
404
508
|
```javascript
|
|
405
|
-
showToast('Success message', 'success') // Green toast
|
|
406
|
-
showToast('Error occurred', 'error') // Red toast
|
|
407
|
-
showToast('Information', 'info') // Blue toast
|
|
408
|
-
showToast('Warning', 'warning') // Yellow toast
|
|
509
|
+
App.ui.showToast('Success message', 'success') // Green toast
|
|
510
|
+
App.ui.showToast('Error occurred', 'error') // Red toast
|
|
511
|
+
App.ui.showToast('Information', 'info') // Blue toast
|
|
512
|
+
App.ui.showToast('Warning', 'warning') // Yellow toast
|
|
409
513
|
```
|
|
410
514
|
|
|
411
515
|
**Inline Messages:**
|
|
412
516
|
```javascript
|
|
413
|
-
showMessage('messageId', 'Success!', 'success')
|
|
414
|
-
showMessage('errorContainer', 'Validation failed', 'error')
|
|
517
|
+
App.ui.showMessage('messageId', 'Success!', 'success')
|
|
518
|
+
App.ui.showMessage('errorContainer', 'Validation failed', 'error')
|
|
415
519
|
```
|
|
416
520
|
|
|
417
521
|
**Modal Dialogs:**
|
|
418
522
|
```javascript
|
|
419
|
-
showModal('confirmModal', 'Item saved successfully', 'success')
|
|
420
|
-
showModal('errorModal', 'Operation failed', 'error')
|
|
523
|
+
App.ui.showModal('confirmModal', 'Item saved successfully', 'success')
|
|
524
|
+
App.ui.showModal('errorModal', 'Operation failed', 'error')
|
|
421
525
|
```
|
|
422
526
|
|
|
423
527
|
### Navigation & Page Actions
|
|
424
528
|
|
|
425
529
|
**Navigation Functions:**
|
|
426
530
|
```javascript
|
|
427
|
-
navigateBack() // Go back in history or to home
|
|
428
|
-
redirectTo('/posts') // Safe redirect with validation
|
|
429
|
-
reloadPage() // Reload with loading indicator
|
|
430
|
-
refreshSection('#content') // Refresh specific section
|
|
431
|
-
|
|
432
531
|
// SPA-style navigation
|
|
433
|
-
|
|
434
|
-
```
|
|
532
|
+
App.nav.go('/posts/123') // Loads via AJAX, updates #main
|
|
435
533
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
updateContent('#list', itemHtml, 'prepend') // Add to beginning
|
|
441
|
-
removeElement('#item-123') // Animate and remove
|
|
534
|
+
// Or use native browser APIs directly
|
|
535
|
+
window.history.back() // Go back in history
|
|
536
|
+
window.location.href = '/posts' // Full page redirect
|
|
537
|
+
window.location.reload() // Reload page
|
|
442
538
|
```
|
|
443
539
|
|
|
444
540
|
### Form Handling & Strategy System
|
|
@@ -468,52 +564,26 @@ removeElement('#item-123') // Animate and remove
|
|
|
468
564
|
**Manual Form Submission:**
|
|
469
565
|
```javascript
|
|
470
566
|
const form = document.querySelector('#myForm');
|
|
471
|
-
|
|
567
|
+
App.nav.submit(form, ['toast', 'back'], {
|
|
472
568
|
entityName: 'Post',
|
|
473
569
|
basePath: '/posts',
|
|
474
570
|
messageId: 'form-message'
|
|
475
571
|
});
|
|
476
572
|
```
|
|
477
573
|
|
|
478
|
-
**Success Handling:**
|
|
479
|
-
```javascript
|
|
480
|
-
handleFormSuccess(response, ['toast', 'back'], {
|
|
481
|
-
entityName: 'Post',
|
|
482
|
-
basePath: '/posts',
|
|
483
|
-
messageId: 'success-msg',
|
|
484
|
-
modalId: 'success-modal'
|
|
485
|
-
});
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Form Validation & Type Conversion
|
|
489
|
-
|
|
490
|
-
**Client-side Validation Setup:**
|
|
491
|
-
```javascript
|
|
492
|
-
setupFormValidation('#createForm'); // Adds required field validation
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
**Field Type Conversion:**
|
|
496
|
-
```javascript
|
|
497
|
-
// Automatic conversion based on data-field-types
|
|
498
|
-
convertFieldValue('123', 'number') // Returns 123 (number)
|
|
499
|
-
convertFieldValue('true', 'boolean') // Returns true (boolean)
|
|
500
|
-
convertFieldValue('text', 'string') // Returns 'text' (string)
|
|
501
|
-
```
|
|
502
|
-
|
|
503
574
|
### Loading States & Utilities
|
|
504
575
|
|
|
505
576
|
**Loading Indicators:**
|
|
506
577
|
```javascript
|
|
507
|
-
showLoading('#form') // Show spinner on form
|
|
508
|
-
hideLoading('#form') // Hide spinner
|
|
509
|
-
showLoading('#main') // Show spinner on main content
|
|
578
|
+
App.ui.showLoading('#form') // Show spinner on form
|
|
579
|
+
App.ui.hideLoading('#form') // Hide spinner
|
|
580
|
+
App.ui.showLoading('#main') // Show spinner on main content
|
|
510
581
|
```
|
|
511
582
|
|
|
512
583
|
**Utility Functions:**
|
|
513
584
|
```javascript
|
|
514
|
-
debounce(searchFunction, 300) // Debounce for search inputs
|
|
515
|
-
|
|
516
|
-
clearForm('#myForm') // Reset form and clear validation
|
|
585
|
+
App.utils.debounce(searchFunction, 300) // Debounce for search inputs
|
|
586
|
+
App.utils.$('#selector') // Safe element selection
|
|
517
587
|
```
|
|
518
588
|
|
|
519
589
|
### Event Handling & SPA Integration
|
|
@@ -531,12 +601,12 @@ clearForm('#myForm') // Reset form and clear validation
|
|
|
531
601
|
**Custom Event Listeners:**
|
|
532
602
|
```javascript
|
|
533
603
|
// Re-initialize after dynamic content loading
|
|
534
|
-
initializeEventListeners();
|
|
604
|
+
App.utils.initializeEventListeners();
|
|
535
605
|
|
|
536
606
|
// Handle specific link navigation
|
|
537
607
|
document.querySelector('#myLink').addEventListener('click', (e) => {
|
|
538
608
|
e.preventDefault();
|
|
539
|
-
|
|
609
|
+
App.nav.go('/custom/path');
|
|
540
610
|
});
|
|
541
611
|
```
|
|
542
612
|
|
|
@@ -545,11 +615,33 @@ document.querySelector('#myLink').addEventListener('click', (e) => {
|
|
|
545
615
|
**Accessing Functions:**
|
|
546
616
|
```javascript
|
|
547
617
|
// All functions available under window.App
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
App.
|
|
552
|
-
App.
|
|
618
|
+
// Organized by category for better discoverability
|
|
619
|
+
|
|
620
|
+
// UI Functions
|
|
621
|
+
App.ui.showToast('Message', 'success');
|
|
622
|
+
App.ui.showMessage('elementId', 'Success!', 'success');
|
|
623
|
+
App.ui.showModal('modalId', 'Done!', 'success');
|
|
624
|
+
App.ui.showLoading('#form');
|
|
625
|
+
App.ui.hideLoading('#form');
|
|
626
|
+
|
|
627
|
+
// Navigation Functions
|
|
628
|
+
App.nav.go('/posts/123'); // SPA-style navigation with AJAX
|
|
629
|
+
App.nav.submit(formElement, ['toast', 'back'], options); // Submit form via AJAX
|
|
630
|
+
|
|
631
|
+
// Translation Functions
|
|
632
|
+
App.lang.t('Translate this'); // Translate string
|
|
633
|
+
App.lang.set('pl'); // Set language
|
|
634
|
+
App.lang.get(); // Get current language
|
|
635
|
+
|
|
636
|
+
// Utility Functions
|
|
637
|
+
App.utils.$('#selector'); // Safe element selection
|
|
638
|
+
App.utils.debounce(fn, 300); // Debounce function
|
|
639
|
+
App.utils.initializeEventListeners(); // Re-initialize after dynamic content
|
|
640
|
+
|
|
641
|
+
// Authentication Functions (JWT)
|
|
642
|
+
App.auth.setAuthToken(token); // Store JWT token
|
|
643
|
+
App.auth.clearAuthToken(); // Remove JWT token
|
|
644
|
+
App.auth.buildAuthHeaders(additionalHeaders); // Build headers with auth token
|
|
553
645
|
```
|
|
554
646
|
|
|
555
647
|
### Template Data Binding
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@currentjs/gen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI code generator",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "Konstantin Zavalny",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"CHANGELOG.md"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"build": "tsc -p tsconfig.json && npm run copy-templates",
|
|
31
|
+
"copy-templates": "mkdir -p dist/generators/templates/data && cp src/generators/templates/data/* dist/generators/templates/data/",
|
|
31
32
|
"clean": "rm -rf dist",
|
|
32
33
|
"prepack": "npm run build"
|
|
33
34
|
},
|