@currentjs/gen 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +8 -289
  2. package/README.md +623 -427
  3. package/dist/cli.js +2 -1
  4. package/dist/commands/commit.js +25 -42
  5. package/dist/commands/createApp.js +1 -0
  6. package/dist/commands/createModule.js +151 -45
  7. package/dist/commands/diff.js +27 -40
  8. package/dist/commands/generateAll.js +141 -291
  9. package/dist/commands/migrateCommit.js +6 -18
  10. package/dist/commands/migratePush.d.ts +1 -0
  11. package/dist/commands/migratePush.js +135 -0
  12. package/dist/commands/migrateUpdate.d.ts +1 -0
  13. package/dist/commands/migrateUpdate.js +147 -0
  14. package/dist/commands/newGenerateAll.d.ts +4 -0
  15. package/dist/commands/newGenerateAll.js +336 -0
  16. package/dist/generators/controllerGenerator.d.ts +43 -19
  17. package/dist/generators/controllerGenerator.js +547 -329
  18. package/dist/generators/domainLayerGenerator.d.ts +21 -0
  19. package/dist/generators/domainLayerGenerator.js +276 -0
  20. package/dist/generators/dtoGenerator.d.ts +21 -0
  21. package/dist/generators/dtoGenerator.js +518 -0
  22. package/dist/generators/newControllerGenerator.d.ts +55 -0
  23. package/dist/generators/newControllerGenerator.js +644 -0
  24. package/dist/generators/newServiceGenerator.d.ts +19 -0
  25. package/dist/generators/newServiceGenerator.js +266 -0
  26. package/dist/generators/newStoreGenerator.d.ts +39 -0
  27. package/dist/generators/newStoreGenerator.js +408 -0
  28. package/dist/generators/newTemplateGenerator.d.ts +29 -0
  29. package/dist/generators/newTemplateGenerator.js +510 -0
  30. package/dist/generators/serviceGenerator.d.ts +16 -51
  31. package/dist/generators/serviceGenerator.js +167 -586
  32. package/dist/generators/storeGenerator.d.ts +35 -32
  33. package/dist/generators/storeGenerator.js +291 -238
  34. package/dist/generators/storeGeneratorV2.d.ts +31 -0
  35. package/dist/generators/storeGeneratorV2.js +190 -0
  36. package/dist/generators/templateGenerator.d.ts +21 -21
  37. package/dist/generators/templateGenerator.js +393 -268
  38. package/dist/generators/templates/appTemplates.d.ts +3 -1
  39. package/dist/generators/templates/appTemplates.js +15 -10
  40. package/dist/generators/templates/data/appYamlTemplate +5 -2
  41. package/dist/generators/templates/data/cursorRulesTemplate +315 -221
  42. package/dist/generators/templates/data/frontendScriptTemplate +76 -47
  43. package/dist/generators/templates/data/mainViewTemplate +1 -1
  44. package/dist/generators/templates/data/systemTsTemplate +5 -0
  45. package/dist/generators/templates/index.d.ts +0 -3
  46. package/dist/generators/templates/index.js +0 -3
  47. package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
  48. package/dist/generators/templates/newStoreTemplates.js +141 -0
  49. package/dist/generators/templates/storeTemplates.d.ts +1 -5
  50. package/dist/generators/templates/storeTemplates.js +102 -219
  51. package/dist/generators/templates/viewTemplates.js +1 -1
  52. package/dist/generators/useCaseGenerator.d.ts +13 -0
  53. package/dist/generators/useCaseGenerator.js +188 -0
  54. package/dist/types/configTypes.d.ts +148 -0
  55. package/dist/types/configTypes.js +10 -0
  56. package/dist/utils/childEntityUtils.d.ts +18 -0
  57. package/dist/utils/childEntityUtils.js +78 -0
  58. package/dist/utils/commandUtils.d.ts +43 -0
  59. package/dist/utils/commandUtils.js +124 -0
  60. package/dist/utils/commitUtils.d.ts +4 -1
  61. package/dist/utils/constants.d.ts +10 -0
  62. package/dist/utils/constants.js +13 -1
  63. package/dist/utils/diResolver.d.ts +32 -0
  64. package/dist/utils/diResolver.js +204 -0
  65. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  66. package/dist/utils/new_parts_of_migrationUtils.js +164 -0
  67. package/dist/utils/typeUtils.d.ts +19 -0
  68. package/dist/utils/typeUtils.js +70 -0
  69. package/package.json +7 -3
@@ -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 constants_1 = require("../utils/constants");
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.typeMapping = {
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
- setAvailableModels(models) {
69
- this.availableModels.clear();
70
- models.forEach(model => {
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
- isRelationshipField(field) {
75
- return this.availableModels.has(field.type);
54
+ getValueObjectName(fieldType) {
55
+ return (0, typeUtils_1.capitalize)(fieldType);
76
56
  }
77
- getForeignKeyFieldName(field) {
78
- // Convention: fieldName + 'Id' (e.g., owner -> ownerId)
79
- return field.name + 'Id';
57
+ getValueObjectConfig(fieldType) {
58
+ return this.availableValueObjects.get(this.getValueObjectName(fieldType));
80
59
  }
81
- generateRowFields(modelConfig) {
82
- const fields = [];
83
- modelConfig.fields.forEach(field => {
84
- if (field.name === 'createdAt') {
85
- return;
86
- }
87
- // For relationship fields, store the foreign key instead
88
- if (this.isRelationshipField(field)) {
89
- const foreignKeyName = this.getForeignKeyFieldName(field);
90
- const tsType = 'number'; // Foreign keys are always numbers
91
- const isOptional = !field.required && !field.auto;
92
- const fieldDef = ` ${foreignKeyName}${isOptional ? '?' : ''}: ${tsType};`;
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
- generateFilterableFields(modelConfig) {
105
- const filterableFields = modelConfig.fields
106
- .filter(field => ['string', 'number', 'boolean'].includes(field.type))
107
- .map(field => `'${field.name}'`);
108
- return filterableFields.join(' | ');
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
- generateFilterableFieldsArray(modelConfig) {
111
- const filterableFields = modelConfig.fields
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
- generateUpdatableFieldsArray(modelConfig) {
117
- const updatableFields = modelConfig.fields
118
- .filter(field => field.name !== 'id' && field.name !== 'createdAt')
119
- .map(field => `'${field.name}'`);
120
- return updatableFields.join(', ');
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
- sortFieldsByRequired(fields) {
123
- // Sort fields: required fields first, then optional fields
124
- // This must match the order used in domainModelGenerator
125
- return [...fields].sort((a, b) => {
126
- const aRequired = a.required !== false && !a.auto;
127
- const bRequired = b.required !== false && !b.auto;
128
- if (aRequired === bRequired) {
129
- return 0; // Keep original order if both have same required status
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 aRequired ? -1 : 1; // Required fields come first
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
- generateRowToModelMapping(modelConfig) {
135
- // Sort fields to match the constructor parameter order
136
- const sortedFields = this.sortFieldsByRequired(modelConfig.fields);
137
- const mappings = sortedFields.map(field => {
138
- if (field.name === 'createdAt') {
139
- return ' row.created_at';
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
- // For relationship fields, we pass null - will be loaded separately
142
- if (this.isRelationshipField(field)) {
143
- return ' null as any'; // Placeholder, loaded via loadRelationships
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
- return ` row.${field.name}`;
182
+ result.push(` row.${fieldName}`);
146
183
  });
147
- return mappings.join(',\n');
184
+ return result.join(',\n');
148
185
  }
149
- generateModelToRowMapping(modelConfig) {
150
- const mappings = modelConfig.fields.map(field => {
151
- if (field.name === 'createdAt') {
152
- return ' created_at: model.createdAt';
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
- // For relationship fields, extract ID from the object to store as FK
155
- if (this.isRelationshipField(field)) {
156
- const foreignKeyName = this.getForeignKeyFieldName(field);
157
- return ` ${foreignKeyName}: model.${field.name}?.id`;
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
- return ` ${field.name}: model.${field.name}`;
208
+ result.push(` ${fieldName}: entity.${fieldName}`);
160
209
  });
161
- return mappings.join(',\n');
210
+ return result.join(',\n');
162
211
  }
163
- replaceTemplateVars(template, variables) {
164
- let result = template;
165
- Object.entries(variables).forEach(([key, value]) => {
166
- const regex = new RegExp(`{{${key}}}`, 'g');
167
- result = result.replace(regex, value);
168
- });
169
- return result;
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
- generateStoreInterface() {
172
- return storeTemplates_1.fileTemplates.storeInterface;
230
+ generateUpdateFieldsArray(fields) {
231
+ return JSON.stringify(fields.map(([name]) => name));
173
232
  }
174
- generateRelationshipMethods(modelConfig) {
175
- const relationshipFields = modelConfig.fields.filter(f => this.isRelationshipField(f));
176
- if (relationshipFields.length === 0) {
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
- const entityName = modelConfig.name;
180
- const methods = [];
181
- // Generate loadRelationships method
182
- const loadCalls = relationshipFields.map(field => {
183
- const foreignKeyName = this.getForeignKeyFieldName(field);
184
- const relatedModel = field.type;
185
- const relatedModelLower = relatedModel.toLowerCase();
186
- return ` if (entity.${field.name} === null && row.${foreignKeyName}) {
187
- const ${field.name} = await this.${relatedModelLower}Store.getById(row.${foreignKeyName});
188
- if (${field.name}) {
189
- entity.set${field.name.charAt(0).toUpperCase() + field.name.slice(1)}(${field.name});
190
- }
191
- }`;
192
- }).join('\n');
193
- methods.push(`
194
- async loadRelationships(entity: ${entityName}, row: ${entityName}Row): Promise<${entityName}> {
195
- ${loadCalls}
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
- return methods.join('\n');
274
+ return [];
275
+ }`;
222
276
  }
223
- generateStoreConstructorParams(modelConfig) {
224
- const relationshipFields = modelConfig.fields.filter(f => this.isRelationshipField(f));
225
- const params = ['private db: ISqlProvider'];
226
- relationshipFields.forEach(field => {
227
- const relatedModel = field.type;
228
- const relatedModelLower = relatedModel.toLowerCase();
229
- params.push(`private ${relatedModelLower}Store: ${relatedModel}Store`);
230
- });
231
- return params.join(', ');
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
- generateRelationshipImports(modelConfig) {
234
- const relationshipFields = modelConfig.fields.filter(f => this.isRelationshipField(f));
235
- if (relationshipFields.length === 0) {
236
- return '';
296
+ return null;
297
+ }`;
237
298
  }
238
- const imports = relationshipFields.map(field => {
239
- const relatedModel = field.type;
240
- return `import { ${relatedModel}Store } from './${relatedModel}Store';`;
241
- });
242
- return '\n' + imports.join('\n');
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(modelConfig) {
245
- const entityName = modelConfig.name;
246
- const tableName = entityName.toLowerCase() + 's';
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: entityName,
323
+ ENTITY_NAME: modelName,
249
324
  TABLE_NAME: tableName,
250
- ROW_FIELDS: this.generateRowFields(modelConfig),
251
- FILTERABLE_FIELDS: this.generateFilterableFields(modelConfig),
252
- FILTERABLE_FIELDS_ARRAY: this.generateFilterableFieldsArray(modelConfig),
253
- UPDATABLE_FIELDS_ARRAY: this.generateUpdatableFieldsArray(modelConfig),
254
- ROW_TO_MODEL_MAPPING: this.generateRowToModelMapping(modelConfig),
255
- MODEL_TO_ROW_MAPPING: this.generateModelToRowMapping(modelConfig)
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 conversionMethods = this.replaceTemplateVars(storeTemplates_1.storeTemplates.conversionMethods, variables);
259
- // Replace constructor in storeClass template
260
- let storeClass = this.replaceTemplateVars(storeTemplates_1.storeTemplates.storeClass, {
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
- }) + relationshipImports;
340
+ STORE_CLASS: storeClass,
341
+ VALUE_OBJECT_IMPORTS: variables.VALUE_OBJECT_IMPORTS
342
+ });
279
343
  }
280
- generateStores(models) {
344
+ generateFromConfig(config) {
281
345
  const result = {};
282
- models.forEach(model => {
283
- result[model.name] = this.generateStore(model);
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
- const result = {};
291
- if (config.modules) {
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 result;
369
+ return this.generateFromConfig(config);
311
370
  }
312
- async generateAndSaveFiles(yamlFilePath = constants_1.COMMON_FILES.APP_YAML, outputDir = 'infrastructure', opts) {
313
- const stores = this.generateFromYamlFile(yamlFilePath);
314
- const storesDir = path.join(outputDir, 'stores');
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
- fs.mkdirSync(interfacesDir, { recursive: true });
318
- const storeInterface = this.generateStoreInterface();
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, storeCode, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
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('All store files generated successfully!') + '\n');
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 {};