@currentjs/gen 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -611
- package/README.md +623 -427
- package/dist/cli.js +2 -1
- package/dist/commands/commit.js +25 -42
- package/dist/commands/createApp.js +1 -0
- package/dist/commands/createModule.js +151 -45
- package/dist/commands/diff.js +27 -40
- package/dist/commands/generateAll.js +141 -291
- package/dist/commands/migrateCommit.js +6 -18
- package/dist/commands/migratePush.d.ts +1 -0
- package/dist/commands/migratePush.js +135 -0
- package/dist/commands/migrateUpdate.d.ts +1 -0
- package/dist/commands/migrateUpdate.js +147 -0
- package/dist/commands/newGenerateAll.d.ts +4 -0
- package/dist/commands/newGenerateAll.js +336 -0
- package/dist/generators/controllerGenerator.d.ts +43 -19
- package/dist/generators/controllerGenerator.js +547 -329
- package/dist/generators/domainLayerGenerator.d.ts +21 -0
- package/dist/generators/domainLayerGenerator.js +276 -0
- package/dist/generators/dtoGenerator.d.ts +21 -0
- package/dist/generators/dtoGenerator.js +518 -0
- package/dist/generators/newControllerGenerator.d.ts +55 -0
- package/dist/generators/newControllerGenerator.js +644 -0
- package/dist/generators/newServiceGenerator.d.ts +19 -0
- package/dist/generators/newServiceGenerator.js +266 -0
- package/dist/generators/newStoreGenerator.d.ts +39 -0
- package/dist/generators/newStoreGenerator.js +408 -0
- package/dist/generators/newTemplateGenerator.d.ts +29 -0
- package/dist/generators/newTemplateGenerator.js +510 -0
- package/dist/generators/serviceGenerator.d.ts +16 -51
- package/dist/generators/serviceGenerator.js +167 -586
- package/dist/generators/storeGenerator.d.ts +35 -32
- package/dist/generators/storeGenerator.js +291 -238
- package/dist/generators/storeGeneratorV2.d.ts +31 -0
- package/dist/generators/storeGeneratorV2.js +190 -0
- package/dist/generators/templateGenerator.d.ts +21 -21
- package/dist/generators/templateGenerator.js +393 -268
- package/dist/generators/templates/appTemplates.d.ts +3 -1
- package/dist/generators/templates/appTemplates.js +15 -10
- package/dist/generators/templates/data/appYamlTemplate +5 -2
- package/dist/generators/templates/data/cursorRulesTemplate +315 -221
- package/dist/generators/templates/data/frontendScriptTemplate +45 -11
- package/dist/generators/templates/data/mainViewTemplate +1 -1
- package/dist/generators/templates/data/systemTsTemplate +5 -0
- package/dist/generators/templates/index.d.ts +0 -3
- package/dist/generators/templates/index.js +0 -3
- package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
- package/dist/generators/templates/newStoreTemplates.js +141 -0
- package/dist/generators/templates/storeTemplates.d.ts +1 -5
- package/dist/generators/templates/storeTemplates.js +102 -219
- package/dist/generators/templates/viewTemplates.js +1 -1
- package/dist/generators/useCaseGenerator.d.ts +13 -0
- package/dist/generators/useCaseGenerator.js +188 -0
- package/dist/types/configTypes.d.ts +148 -0
- package/dist/types/configTypes.js +10 -0
- package/dist/utils/childEntityUtils.d.ts +18 -0
- package/dist/utils/childEntityUtils.js +78 -0
- package/dist/utils/commandUtils.d.ts +43 -0
- package/dist/utils/commandUtils.js +124 -0
- package/dist/utils/commitUtils.d.ts +4 -1
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +13 -1
- package/dist/utils/diResolver.d.ts +32 -0
- package/dist/utils/diResolver.js +204 -0
- package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
- package/dist/utils/new_parts_of_migrationUtils.js +164 -0
- package/dist/utils/typeUtils.d.ts +19 -0
- package/dist/utils/typeUtils.js +70 -0
- package/package.json +7 -3
|
@@ -37,295 +37,348 @@ exports.StoreGenerator = void 0;
|
|
|
37
37
|
const yaml_1 = require("yaml");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
-
const storeTemplates_1 = require("./templates/storeTemplates");
|
|
41
40
|
const generationRegistry_1 = require("../utils/generationRegistry");
|
|
42
41
|
const colors_1 = require("../utils/colors");
|
|
43
|
-
const
|
|
42
|
+
const configTypes_1 = require("../types/configTypes");
|
|
43
|
+
const childEntityUtils_1 = require("../utils/childEntityUtils");
|
|
44
|
+
const storeTemplates_1 = require("./templates/storeTemplates");
|
|
45
|
+
const typeUtils_1 = require("../utils/typeUtils");
|
|
44
46
|
class StoreGenerator {
|
|
45
47
|
constructor() {
|
|
46
|
-
this.
|
|
47
|
-
string: 'string',
|
|
48
|
-
number: 'number',
|
|
49
|
-
boolean: 'boolean',
|
|
50
|
-
datetime: 'Date',
|
|
51
|
-
json: 'any',
|
|
52
|
-
array: 'any[]',
|
|
53
|
-
object: 'object'
|
|
54
|
-
};
|
|
55
|
-
this.availableModels = new Set();
|
|
56
|
-
}
|
|
57
|
-
mapType(yamlType, isRelationship = false) {
|
|
58
|
-
// For relationships, we store the foreign key (number) in the database
|
|
59
|
-
if (isRelationship) {
|
|
60
|
-
return 'number';
|
|
61
|
-
}
|
|
62
|
-
// Check if this is a known model (relationship) - should use foreign key type
|
|
63
|
-
if (this.availableModels.has(yamlType)) {
|
|
64
|
-
return 'number'; // Foreign keys are numbers
|
|
65
|
-
}
|
|
66
|
-
return this.typeMapping[yamlType] || 'any';
|
|
48
|
+
this.availableValueObjects = new Map();
|
|
67
49
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.availableModels.add(model.name);
|
|
72
|
-
});
|
|
50
|
+
isValueObjectType(fieldType) {
|
|
51
|
+
const capitalizedType = (0, typeUtils_1.capitalize)(fieldType);
|
|
52
|
+
return this.availableValueObjects.has(capitalizedType);
|
|
73
53
|
}
|
|
74
|
-
|
|
75
|
-
return
|
|
54
|
+
getValueObjectName(fieldType) {
|
|
55
|
+
return (0, typeUtils_1.capitalize)(fieldType);
|
|
76
56
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return field.name + 'Id';
|
|
57
|
+
getValueObjectConfig(fieldType) {
|
|
58
|
+
return this.availableValueObjects.get(this.getValueObjectName(fieldType));
|
|
80
59
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
fields.push(fieldDef);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
const tsType = this.mapType(field.type);
|
|
97
|
-
const isOptional = !field.required && !field.auto;
|
|
98
|
-
const fieldDef = ` ${field.name}${isOptional ? '?' : ''}: ${tsType};`;
|
|
99
|
-
fields.push(fieldDef);
|
|
100
|
-
}
|
|
60
|
+
/**
|
|
61
|
+
* Sort fields the same way as the domain layer generator:
|
|
62
|
+
* Required fields first, then optional fields.
|
|
63
|
+
* This ensures rowToModel parameter order matches entity constructor.
|
|
64
|
+
*/
|
|
65
|
+
sortFieldsForConstructor(fields) {
|
|
66
|
+
return [...fields].sort((a, b) => {
|
|
67
|
+
const aRequired = a[1].required !== false && !a[1].auto;
|
|
68
|
+
const bRequired = b[1].required !== false && !b[1].auto;
|
|
69
|
+
if (aRequired === bRequired)
|
|
70
|
+
return 0;
|
|
71
|
+
return aRequired ? -1 : 1;
|
|
101
72
|
});
|
|
102
|
-
return fields.join('\n');
|
|
103
73
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Gets the type name for a value object field (for casting).
|
|
76
|
+
* For enum fields, returns the generated type name (e.g., BreedName).
|
|
77
|
+
* For other fields, returns the basic TypeScript type.
|
|
78
|
+
*/
|
|
79
|
+
getValueObjectFieldTypeName(voName, voConfig) {
|
|
80
|
+
const fields = Object.entries(voConfig.fields);
|
|
81
|
+
// Look for enum fields - they get generated type names
|
|
82
|
+
for (const [fieldName, fieldConfig] of fields) {
|
|
83
|
+
if (typeof fieldConfig === 'object' && 'values' in fieldConfig) {
|
|
84
|
+
// This is an enum field, return the generated type name
|
|
85
|
+
return `${voName}${(0, typeUtils_1.capitalize)(fieldName)}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
109
89
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.filter(field => ['string', 'number', 'boolean'].includes(field.type))
|
|
113
|
-
.map(field => `'${field.name}'`);
|
|
114
|
-
return filterableFields.join(', ');
|
|
90
|
+
mapTypeToRowType(yamlType) {
|
|
91
|
+
return (0, typeUtils_1.mapRowType)(yamlType, this.availableValueObjects);
|
|
115
92
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
93
|
+
/** Single line for serializing a value object field to DB (insert/update). */
|
|
94
|
+
generateValueObjectSerialization(fieldName, voName, voConfig) {
|
|
95
|
+
const voFields = Object.keys(voConfig.fields);
|
|
96
|
+
if (voFields.length > 1) {
|
|
97
|
+
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}) : undefined`;
|
|
98
|
+
}
|
|
99
|
+
if (voFields.length === 1) {
|
|
100
|
+
return ` ${fieldName}: entity.${fieldName}?.${voFields[0]}`;
|
|
101
|
+
}
|
|
102
|
+
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}) : undefined`;
|
|
121
103
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
104
|
+
/** Single line for deserializing a value object from row (rowToModel). */
|
|
105
|
+
generateValueObjectDeserialization(fieldName, voName, voConfig) {
|
|
106
|
+
const voFields = Object.keys(voConfig.fields);
|
|
107
|
+
if (voFields.length > 1) {
|
|
108
|
+
const voArgs = voFields.map(f => `parsed.${f}`).join(', ');
|
|
109
|
+
return ` row.${fieldName} ? (() => { const parsed = JSON.parse(row.${fieldName}); return new ${voName}(${voArgs}); })() : undefined`;
|
|
110
|
+
}
|
|
111
|
+
if (voFields.length === 1) {
|
|
112
|
+
const singleFieldType = voConfig.fields[voFields[0]];
|
|
113
|
+
const hasEnumValues = typeof singleFieldType === 'object' && 'values' in singleFieldType;
|
|
114
|
+
if (hasEnumValues) {
|
|
115
|
+
const enumTypeName = this.getValueObjectFieldTypeName(voName, voConfig);
|
|
116
|
+
return ` row.${fieldName} ? new ${voName}(row.${fieldName} as ${enumTypeName}) : undefined`;
|
|
130
117
|
}
|
|
131
|
-
return
|
|
118
|
+
return ` row.${fieldName} ? new ${voName}(row.${fieldName}) : undefined`;
|
|
119
|
+
}
|
|
120
|
+
return ` row.${fieldName} ? new ${voName}(...Object.values(JSON.parse(row.${fieldName}))) : undefined`;
|
|
121
|
+
}
|
|
122
|
+
/** Single line for datetime conversion: toDate (row->model) or toMySQL (entity->row). */
|
|
123
|
+
generateDatetimeConversion(fieldName, direction) {
|
|
124
|
+
if (direction === 'toDate') {
|
|
125
|
+
return ` row.${fieldName} ? new Date(row.${fieldName}) : undefined`;
|
|
126
|
+
}
|
|
127
|
+
return ` ${fieldName}: entity.${fieldName} ? this.toMySQLDatetime(entity.${fieldName}) : undefined`;
|
|
128
|
+
}
|
|
129
|
+
replaceTemplateVars(template, variables) {
|
|
130
|
+
let result = template;
|
|
131
|
+
Object.entries(variables).forEach(([key, value]) => {
|
|
132
|
+
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
133
|
+
result = result.replace(regex, value);
|
|
132
134
|
});
|
|
135
|
+
return result;
|
|
133
136
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
generateRowFields(fields, childInfo) {
|
|
138
|
+
const result = [];
|
|
139
|
+
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
140
|
+
result.push(` ${ownerOrParentField}: number;`);
|
|
141
|
+
fields.forEach(([fieldName, fieldConfig]) => {
|
|
142
|
+
const tsType = this.mapTypeToRowType(fieldConfig.type);
|
|
143
|
+
const isOptional = !fieldConfig.required;
|
|
144
|
+
result.push(` ${fieldName}${isOptional ? '?' : ''}: ${tsType};`);
|
|
145
|
+
});
|
|
146
|
+
return result.join('\n');
|
|
147
|
+
}
|
|
148
|
+
generateFieldNamesStr(fields, childInfo) {
|
|
149
|
+
const fieldNames = ['id'];
|
|
150
|
+
fieldNames.push(childInfo ? childInfo.parentIdField : 'ownerId');
|
|
151
|
+
fieldNames.push(...fields.map(([name]) => name));
|
|
152
|
+
return fieldNames.map(f => `\\\`${f}\\\``).join(', ');
|
|
153
|
+
}
|
|
154
|
+
generateRowToModelMapping(fields, childInfo) {
|
|
155
|
+
const result = [];
|
|
156
|
+
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
157
|
+
result.push(` row.${ownerOrParentField}`);
|
|
158
|
+
fields.forEach(([fieldName, fieldConfig]) => {
|
|
159
|
+
const fieldType = fieldConfig.type;
|
|
160
|
+
// Handle datetime/date conversion
|
|
161
|
+
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
162
|
+
result.push(this.generateDatetimeConversion(fieldName, 'toDate'));
|
|
163
|
+
return;
|
|
140
164
|
}
|
|
141
|
-
//
|
|
142
|
-
if (
|
|
143
|
-
|
|
165
|
+
// Handle boolean conversion
|
|
166
|
+
if (fieldType === 'boolean') {
|
|
167
|
+
result.push(` Boolean(row.${fieldName})`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Handle value object conversion - deserialize from JSON
|
|
171
|
+
if (this.isValueObjectType(fieldType)) {
|
|
172
|
+
const voName = this.getValueObjectName(fieldType);
|
|
173
|
+
const voConfig = this.getValueObjectConfig(fieldType);
|
|
174
|
+
if (voConfig) {
|
|
175
|
+
result.push(this.generateValueObjectDeserialization(fieldName, voName, voConfig));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
result.push(` row.${fieldName} ? new ${voName}(...Object.values(JSON.parse(row.${fieldName}))) : undefined`);
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
144
181
|
}
|
|
145
|
-
|
|
182
|
+
result.push(` row.${fieldName}`);
|
|
146
183
|
});
|
|
147
|
-
return
|
|
184
|
+
return result.join(',\n');
|
|
148
185
|
}
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
186
|
+
generateInsertDataMapping(fields, childInfo) {
|
|
187
|
+
const result = [];
|
|
188
|
+
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
189
|
+
result.push(` ${ownerOrParentField}: entity.${ownerOrParentField}`);
|
|
190
|
+
fields.forEach(([fieldName, fieldConfig]) => {
|
|
191
|
+
const fieldType = fieldConfig.type;
|
|
192
|
+
// Handle datetime/date - convert Date to MySQL DATETIME format
|
|
193
|
+
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
194
|
+
result.push(this.generateDatetimeConversion(fieldName, 'toMySQL'));
|
|
195
|
+
return;
|
|
153
196
|
}
|
|
154
|
-
//
|
|
155
|
-
if (this.
|
|
156
|
-
const
|
|
157
|
-
|
|
197
|
+
// Handle value object - serialize to JSON
|
|
198
|
+
if (this.isValueObjectType(fieldType)) {
|
|
199
|
+
const voConfig = this.getValueObjectConfig(fieldType);
|
|
200
|
+
if (voConfig) {
|
|
201
|
+
result.push(this.generateValueObjectSerialization(fieldName, this.getValueObjectName(fieldType), voConfig));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
result.push(` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}) : undefined`);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
158
207
|
}
|
|
159
|
-
|
|
208
|
+
result.push(` ${fieldName}: entity.${fieldName}`);
|
|
160
209
|
});
|
|
161
|
-
return
|
|
210
|
+
return result.join(',\n');
|
|
162
211
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
generateUpdateDataMapping(fields) {
|
|
213
|
+
return fields
|
|
214
|
+
.map(([fieldName, fieldConfig]) => {
|
|
215
|
+
const fieldType = fieldConfig.type;
|
|
216
|
+
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
217
|
+
return this.generateDatetimeConversion(fieldName, 'toMySQL');
|
|
218
|
+
}
|
|
219
|
+
if (this.isValueObjectType(fieldType)) {
|
|
220
|
+
const voConfig = this.getValueObjectConfig(fieldType);
|
|
221
|
+
if (voConfig) {
|
|
222
|
+
return this.generateValueObjectSerialization(fieldName, this.getValueObjectName(fieldType), voConfig);
|
|
223
|
+
}
|
|
224
|
+
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}) : undefined`;
|
|
225
|
+
}
|
|
226
|
+
return ` ${fieldName}: entity.${fieldName}`;
|
|
227
|
+
})
|
|
228
|
+
.join(',\n');
|
|
170
229
|
}
|
|
171
|
-
|
|
172
|
-
return
|
|
230
|
+
generateUpdateFieldsArray(fields) {
|
|
231
|
+
return JSON.stringify(fields.map(([name]) => name));
|
|
173
232
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
233
|
+
generateValueObjectImports(fields) {
|
|
234
|
+
const imports = [];
|
|
235
|
+
fields.forEach(([, fieldConfig]) => {
|
|
236
|
+
if (this.isValueObjectType(fieldConfig.type)) {
|
|
237
|
+
const voName = this.getValueObjectName(fieldConfig.type);
|
|
238
|
+
const voConfig = this.getValueObjectConfig(fieldConfig.type);
|
|
239
|
+
// Import the class
|
|
240
|
+
const importItems = [voName];
|
|
241
|
+
// Also import the type if it's an enum
|
|
242
|
+
if (voConfig) {
|
|
243
|
+
const enumTypeName = this.getValueObjectFieldTypeName(voName, voConfig);
|
|
244
|
+
if (enumTypeName) {
|
|
245
|
+
importItems.push(enumTypeName);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
imports.push(`import { ${importItems.join(', ')} } from '../../domain/valueObjects/${voName}';`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
// Dedupe imports
|
|
252
|
+
const uniqueImports = [...new Set(imports)];
|
|
253
|
+
if (uniqueImports.length === 0) {
|
|
177
254
|
return '';
|
|
178
255
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
${
|
|
196
|
-
return entity;
|
|
197
|
-
}`);
|
|
198
|
-
// Generate getByIdWithRelationships method
|
|
199
|
-
methods.push(`
|
|
200
|
-
async getByIdWithRelationships(id: number): Promise<${entityName} | null> {
|
|
201
|
-
try {
|
|
202
|
-
const query = 'SELECT * FROM ${entityName.toLowerCase()}s WHERE id = :id AND deleted_at IS NULL';
|
|
203
|
-
const result = await this.db.query(query, { id });
|
|
204
|
-
|
|
205
|
-
if (!result.success || result.data.length === 0) {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const row = result.data[0] as ${entityName}Row;
|
|
210
|
-
const entity = ${entityName}Store.rowToModel(row);
|
|
211
|
-
return await this.loadRelationships(entity, row);
|
|
212
|
-
} catch (error) {
|
|
213
|
-
if (error instanceof MySQLConnectionError) {
|
|
214
|
-
throw new Error(\`Database connection error while fetching ${entityName} with id \${id}: \${error.message}\`);
|
|
215
|
-
} else if (error instanceof MySQLQueryError) {
|
|
216
|
-
throw new Error(\`Query error while fetching ${entityName} with id \${id}: \${error.message}\`);
|
|
217
|
-
}
|
|
218
|
-
throw error;
|
|
256
|
+
return '\n' + uniqueImports.join('\n');
|
|
257
|
+
}
|
|
258
|
+
generateGetByParentIdMethod(modelName, fields, childInfo) {
|
|
259
|
+
if (!childInfo)
|
|
260
|
+
return '';
|
|
261
|
+
const fieldList = ['id', childInfo.parentIdField, ...fields.map(([name]) => name)].map(f => '\\`' + f + '\\`').join(', ');
|
|
262
|
+
const parentIdField = childInfo.parentIdField;
|
|
263
|
+
return `
|
|
264
|
+
|
|
265
|
+
async getByParentId(parentId: number): Promise<${modelName}[]> {
|
|
266
|
+
const result = await this.db.query(
|
|
267
|
+
\`SELECT ${fieldList} FROM \\\`\${this.tableName}\\\` WHERE \\\`${parentIdField}\\\` = :parentId AND deleted_at IS NULL\`,
|
|
268
|
+
{ parentId }
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (result.success && result.data) {
|
|
272
|
+
return result.data.map((row: ${modelName}Row) => this.rowToModel(row));
|
|
219
273
|
}
|
|
220
|
-
|
|
221
|
-
|
|
274
|
+
return [];
|
|
275
|
+
}`;
|
|
222
276
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
277
|
+
generateGetResourceOwnerMethod(childInfo) {
|
|
278
|
+
if (childInfo) {
|
|
279
|
+
const parentTable = childInfo.parentTableName;
|
|
280
|
+
const parentIdField = childInfo.parentIdField;
|
|
281
|
+
return `
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get the owner ID of a resource by its ID (via parent entity).
|
|
285
|
+
* Used for pre-mutation authorization checks.
|
|
286
|
+
*/
|
|
287
|
+
async getResourceOwner(id: number): Promise<number | null> {
|
|
288
|
+
const result = await this.db.query(
|
|
289
|
+
\`SELECT p.ownerId FROM \\\`\${this.tableName}\\\` c INNER JOIN \\\`${parentTable}\\\` p ON p.id = c.\\\`${parentIdField}\\\` WHERE c.id = :id AND c.deleted_at IS NULL\`,
|
|
290
|
+
{ id }
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
294
|
+
return result.data[0].ownerId as number;
|
|
232
295
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (relationshipFields.length === 0) {
|
|
236
|
-
return '';
|
|
296
|
+
return null;
|
|
297
|
+
}`;
|
|
237
298
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
299
|
+
return `
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get the owner ID of a resource by its ID.
|
|
303
|
+
* Used for pre-mutation authorization checks.
|
|
304
|
+
*/
|
|
305
|
+
async getResourceOwner(id: number): Promise<number | null> {
|
|
306
|
+
const result = await this.db.query(
|
|
307
|
+
\`SELECT ownerId FROM \\\`\${this.tableName}\\\` WHERE id = :id AND deleted_at IS NULL\`,
|
|
308
|
+
{ id }
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
312
|
+
return result.data[0].ownerId as number;
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}`;
|
|
243
316
|
}
|
|
244
|
-
generateStore(
|
|
245
|
-
const
|
|
246
|
-
const
|
|
317
|
+
generateStore(modelName, aggregateConfig, childInfo) {
|
|
318
|
+
const tableName = modelName.toLowerCase();
|
|
319
|
+
const fields = Object.entries(aggregateConfig.fields);
|
|
320
|
+
// Sort fields for rowToModel to match entity constructor order (required first, optional second)
|
|
321
|
+
const sortedFields = this.sortFieldsForConstructor(fields);
|
|
247
322
|
const variables = {
|
|
248
|
-
ENTITY_NAME:
|
|
323
|
+
ENTITY_NAME: modelName,
|
|
249
324
|
TABLE_NAME: tableName,
|
|
250
|
-
ROW_FIELDS: this.generateRowFields(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
325
|
+
ROW_FIELDS: this.generateRowFields(fields, childInfo),
|
|
326
|
+
FIELD_NAMES: this.generateFieldNamesStr(fields, childInfo),
|
|
327
|
+
ROW_TO_MODEL_MAPPING: this.generateRowToModelMapping(sortedFields, childInfo),
|
|
328
|
+
INSERT_DATA_MAPPING: this.generateInsertDataMapping(fields, childInfo),
|
|
329
|
+
UPDATE_DATA_MAPPING: this.generateUpdateDataMapping(fields),
|
|
330
|
+
UPDATE_FIELDS_ARRAY: this.generateUpdateFieldsArray(fields),
|
|
331
|
+
VALUE_OBJECT_IMPORTS: this.generateValueObjectImports(fields),
|
|
332
|
+
GET_BY_PARENT_ID_METHOD: this.generateGetByParentIdMethod(modelName, fields, childInfo),
|
|
333
|
+
GET_RESOURCE_OWNER_METHOD: this.generateGetResourceOwnerMethod(childInfo)
|
|
256
334
|
};
|
|
257
335
|
const rowInterface = this.replaceTemplateVars(storeTemplates_1.storeTemplates.rowInterface, variables);
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
...variables,
|
|
262
|
-
CONVERSION_METHODS: conversionMethods
|
|
263
|
-
});
|
|
264
|
-
// Update constructor to include foreign store dependencies
|
|
265
|
-
const constructorParams = this.generateStoreConstructorParams(modelConfig);
|
|
266
|
-
storeClass = storeClass.replace('constructor(private db: ISqlProvider) {}', `constructor(${constructorParams}) {}`);
|
|
267
|
-
// Add relationship methods before the closing brace
|
|
268
|
-
const relationshipMethods = this.generateRelationshipMethods(modelConfig);
|
|
269
|
-
if (relationshipMethods) {
|
|
270
|
-
storeClass = storeClass.replace(/}$/, `${relationshipMethods}\n}`);
|
|
271
|
-
}
|
|
272
|
-
// Add relationship store imports
|
|
273
|
-
const relationshipImports = this.generateRelationshipImports(modelConfig);
|
|
274
|
-
return this.replaceTemplateVars(storeTemplates_1.fileTemplates.storeFile, {
|
|
275
|
-
ENTITY_NAME: entityName,
|
|
336
|
+
const storeClass = this.replaceTemplateVars(storeTemplates_1.storeTemplates.storeClass, variables);
|
|
337
|
+
return this.replaceTemplateVars(storeTemplates_1.storeFileTemplate, {
|
|
338
|
+
ENTITY_NAME: modelName,
|
|
276
339
|
ROW_INTERFACE: rowInterface,
|
|
277
|
-
STORE_CLASS: storeClass
|
|
278
|
-
|
|
340
|
+
STORE_CLASS: storeClass,
|
|
341
|
+
VALUE_OBJECT_IMPORTS: variables.VALUE_OBJECT_IMPORTS
|
|
342
|
+
});
|
|
279
343
|
}
|
|
280
|
-
|
|
344
|
+
generateFromConfig(config) {
|
|
281
345
|
const result = {};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
346
|
+
// First, collect all value object names and configs
|
|
347
|
+
this.availableValueObjects.clear();
|
|
348
|
+
if (config.domain.valueObjects) {
|
|
349
|
+
Object.entries(config.domain.valueObjects).forEach(([name, voConfig]) => {
|
|
350
|
+
this.availableValueObjects.set(name, voConfig);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
// Generate a store for each aggregate
|
|
354
|
+
const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
|
|
355
|
+
if (config.domain.aggregates) {
|
|
356
|
+
Object.entries(config.domain.aggregates).forEach(([modelName, aggregateConfig]) => {
|
|
357
|
+
const childInfo = childEntityMap.get(modelName);
|
|
358
|
+
result[modelName] = this.generateStore(modelName, aggregateConfig, childInfo);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
285
361
|
return result;
|
|
286
362
|
}
|
|
287
363
|
generateFromYamlFile(yamlFilePath) {
|
|
288
364
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
289
365
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
Object.values(config.modules).forEach(moduleConfig => {
|
|
293
|
-
if (moduleConfig.models && moduleConfig.models.length > 0) {
|
|
294
|
-
// Set available models for relationship detection
|
|
295
|
-
this.setAvailableModels(moduleConfig.models);
|
|
296
|
-
const stores = this.generateStores(moduleConfig.models);
|
|
297
|
-
Object.assign(result, stores);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
else if (config.models) {
|
|
302
|
-
const module = config;
|
|
303
|
-
if (module.models && module.models.length > 0) {
|
|
304
|
-
// Set available models for relationship detection
|
|
305
|
-
this.setAvailableModels(module.models);
|
|
306
|
-
const stores = this.generateStores(module.models);
|
|
307
|
-
Object.assign(result, stores);
|
|
308
|
-
}
|
|
366
|
+
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
367
|
+
throw new Error('Configuration does not match new module format. Expected domain.aggregates structure.');
|
|
309
368
|
}
|
|
310
|
-
return
|
|
369
|
+
return this.generateFromConfig(config);
|
|
311
370
|
}
|
|
312
|
-
async generateAndSaveFiles(yamlFilePath
|
|
313
|
-
const
|
|
314
|
-
const storesDir = path.join(
|
|
315
|
-
const interfacesDir = path.join(outputDir, 'interfaces');
|
|
371
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
372
|
+
const storesByModel = this.generateFromYamlFile(yamlFilePath);
|
|
373
|
+
const storesDir = path.join(moduleDir, 'infrastructure', 'stores');
|
|
316
374
|
fs.mkdirSync(storesDir, { recursive: true });
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const interfaceFilePath = path.join(interfacesDir, constants_1.COMMON_FILES.STORE_INTERFACE);
|
|
320
|
-
await (0, generationRegistry_1.writeGeneratedFile)(interfaceFilePath, storeInterface, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
|
|
321
|
-
for (const [entityName, storeCode] of Object.entries(stores)) {
|
|
322
|
-
const fileName = `${entityName}Store.ts`;
|
|
323
|
-
const filePath = path.join(storesDir, fileName);
|
|
375
|
+
for (const [modelName, code] of Object.entries(storesByModel)) {
|
|
376
|
+
const filePath = path.join(storesDir, `${modelName}Store.ts`);
|
|
324
377
|
// eslint-disable-next-line no-await-in-loop
|
|
325
|
-
await (0, generationRegistry_1.writeGeneratedFile)(filePath,
|
|
378
|
+
await (0, generationRegistry_1.writeGeneratedFile)(filePath, code, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
|
|
326
379
|
}
|
|
327
380
|
// eslint-disable-next-line no-console
|
|
328
|
-
console.log('\n' + colors_1.colors.green('
|
|
381
|
+
console.log('\n' + colors_1.colors.green('Store files generated successfully!') + '\n');
|
|
329
382
|
}
|
|
330
383
|
}
|
|
331
384
|
exports.StoreGenerator = StoreGenerator;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface FieldConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required?: boolean;
|
|
5
|
+
unique?: boolean;
|
|
6
|
+
auto?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ModelConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
fields: FieldConfig[];
|
|
11
|
+
}
|
|
12
|
+
export declare class StoreGeneratorV2 {
|
|
13
|
+
private typeMapping;
|
|
14
|
+
private mapType;
|
|
15
|
+
private generateRowFields;
|
|
16
|
+
private generateFilterableFields;
|
|
17
|
+
private generateFilterableFieldsArray;
|
|
18
|
+
private generateUpdatableFieldsArray;
|
|
19
|
+
private generateRowToModelMapping;
|
|
20
|
+
private generateModelToRowMapping;
|
|
21
|
+
private replaceTemplateVars;
|
|
22
|
+
generateStoreInterface(): string;
|
|
23
|
+
generateStore(modelConfig: ModelConfig): string;
|
|
24
|
+
generateStores(models: ModelConfig[]): Record<string, string>;
|
|
25
|
+
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
26
|
+
generateAndSaveFiles(yamlFilePath?: string, outputDir?: string, opts?: {
|
|
27
|
+
force?: boolean;
|
|
28
|
+
skipOnConflict?: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export {};
|