@currentjs/gen 0.3.2 → 0.5.1
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 +18 -609
- 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/generators/controllerGenerator.d.ts +50 -19
- package/dist/generators/controllerGenerator.js +588 -331
- package/dist/generators/domainLayerGenerator.d.ts +21 -0
- package/dist/generators/domainLayerGenerator.js +286 -0
- package/dist/generators/dtoGenerator.d.ts +21 -0
- package/dist/generators/dtoGenerator.js +523 -0
- package/dist/generators/serviceGenerator.d.ts +22 -51
- package/dist/generators/serviceGenerator.js +345 -568
- package/dist/generators/storeGenerator.d.ts +39 -32
- package/dist/generators/storeGenerator.js +396 -236
- 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 +16 -11
- package/dist/generators/templates/data/appYamlTemplate +5 -2
- package/dist/generators/templates/data/cursorRulesTemplate +315 -221
- package/dist/generators/templates/data/frontendScriptTemplate +56 -15
- package/dist/generators/templates/data/mainViewTemplate +2 -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/storeTemplates.d.ts +1 -5
- package/dist/generators/templates/storeTemplates.js +84 -224
- package/dist/generators/useCaseGenerator.d.ts +13 -0
- package/dist/generators/useCaseGenerator.js +191 -0
- package/dist/types/configTypes.d.ts +149 -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/typeUtils.d.ts +23 -0
- package/dist/utils/typeUtils.js +77 -0
- package/package.json +7 -3
- package/dist/generators/domainModelGenerator.d.ts +0 -41
- package/dist/generators/domainModelGenerator.js +0 -242
- package/dist/generators/templates/controllerTemplates.d.ts +0 -43
- package/dist/generators/templates/controllerTemplates.js +0 -82
- package/dist/generators/templates/serviceTemplates.d.ts +0 -16
- package/dist/generators/templates/serviceTemplates.js +0 -59
- package/dist/generators/templates/validationTemplates.d.ts +0 -25
- package/dist/generators/templates/validationTemplates.js +0 -66
- package/dist/generators/templates/viewTemplates.d.ts +0 -25
- package/dist/generators/templates/viewTemplates.js +0 -491
- package/dist/generators/validationGenerator.d.ts +0 -29
- package/dist/generators/validationGenerator.js +0 -250
|
@@ -0,0 +1,523 @@
|
|
|
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.DtoGenerator = void 0;
|
|
37
|
+
const yaml_1 = require("yaml");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const generationRegistry_1 = require("../utils/generationRegistry");
|
|
41
|
+
const colors_1 = require("../utils/colors");
|
|
42
|
+
const configTypes_1 = require("../types/configTypes");
|
|
43
|
+
const childEntityUtils_1 = require("../utils/childEntityUtils");
|
|
44
|
+
const typeUtils_1 = require("../utils/typeUtils");
|
|
45
|
+
class DtoGenerator {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.availableAggregates = new Map();
|
|
48
|
+
this.availableValueObjects = new Map();
|
|
49
|
+
}
|
|
50
|
+
mapType(yamlType) {
|
|
51
|
+
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates, this.availableValueObjects);
|
|
52
|
+
}
|
|
53
|
+
isValueObjectType(yamlType) {
|
|
54
|
+
const capitalizedType = (0, typeUtils_1.capitalize)(yamlType);
|
|
55
|
+
return this.availableValueObjects.has(capitalizedType);
|
|
56
|
+
}
|
|
57
|
+
getValidationCode(fieldName, fieldType, isRequired) {
|
|
58
|
+
const checks = [];
|
|
59
|
+
if (isRequired) {
|
|
60
|
+
if (fieldType === 'string') {
|
|
61
|
+
checks.push(` if (typeof b.${fieldName} !== 'string' || !b.${fieldName}) {
|
|
62
|
+
throw new Error('${fieldName} is required');
|
|
63
|
+
}`);
|
|
64
|
+
}
|
|
65
|
+
else if (fieldType === 'number' || fieldType === 'integer' || fieldType === 'decimal' || fieldType === 'id') {
|
|
66
|
+
checks.push(` if (b.${fieldName} === undefined || b.${fieldName} === null) {
|
|
67
|
+
throw new Error('${fieldName} is required');
|
|
68
|
+
}`);
|
|
69
|
+
}
|
|
70
|
+
else if (fieldType === 'boolean') {
|
|
71
|
+
checks.push(` if (typeof b.${fieldName} !== 'boolean') {
|
|
72
|
+
throw new Error('${fieldName} is required');
|
|
73
|
+
}`);
|
|
74
|
+
}
|
|
75
|
+
else if (fieldType === 'datetime' || fieldType === 'date') {
|
|
76
|
+
checks.push(` if (!b.${fieldName}) {
|
|
77
|
+
throw new Error('${fieldName} is required');
|
|
78
|
+
}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
checks.push(` if (b.${fieldName} === undefined) {
|
|
82
|
+
throw new Error('${fieldName} is required');
|
|
83
|
+
}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return checks;
|
|
87
|
+
}
|
|
88
|
+
getTransformCode(fieldName, fieldType) {
|
|
89
|
+
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
90
|
+
return `b.${fieldName} ? new Date(b.${fieldName} as string) : undefined`;
|
|
91
|
+
}
|
|
92
|
+
else if (fieldType === 'number' || fieldType === 'integer' || fieldType === 'decimal' || fieldType === 'id') {
|
|
93
|
+
return `typeof b.${fieldName} === 'string' ? parseFloat(b.${fieldName}) : b.${fieldName} as number`;
|
|
94
|
+
}
|
|
95
|
+
else if (fieldType === 'boolean') {
|
|
96
|
+
return `Boolean(b.${fieldName})`;
|
|
97
|
+
}
|
|
98
|
+
return `b.${fieldName} as ${this.mapType(fieldType)}`;
|
|
99
|
+
}
|
|
100
|
+
generateInputDto(modelName, actionName, inputConfig, aggregateConfig, childInfo) {
|
|
101
|
+
var _a, _b, _c, _d;
|
|
102
|
+
const className = `${modelName}${(0, typeUtils_1.capitalize)(actionName)}Input`;
|
|
103
|
+
if (!inputConfig) {
|
|
104
|
+
return `export class ${className} {
|
|
105
|
+
private constructor() {}
|
|
106
|
+
|
|
107
|
+
static parse(body: unknown): ${className} {
|
|
108
|
+
return new ${className}();
|
|
109
|
+
}
|
|
110
|
+
}`;
|
|
111
|
+
}
|
|
112
|
+
const fieldDeclarations = [];
|
|
113
|
+
const constructorParams = [];
|
|
114
|
+
const constructorAssignments = [];
|
|
115
|
+
const validationChecks = [];
|
|
116
|
+
const fieldTransforms = [];
|
|
117
|
+
// Handle identifier (for get, update, delete)
|
|
118
|
+
if (inputConfig.identifier) {
|
|
119
|
+
const fieldName = inputConfig.identifier;
|
|
120
|
+
fieldDeclarations.push(` readonly ${fieldName}: number;`);
|
|
121
|
+
constructorParams.push(`${fieldName}: number`);
|
|
122
|
+
constructorAssignments.push(` this.${fieldName} = ${fieldName};`);
|
|
123
|
+
validationChecks.push(` if (b.${fieldName} === undefined || b.${fieldName} === null) {
|
|
124
|
+
throw new Error('${fieldName} is required');
|
|
125
|
+
}`);
|
|
126
|
+
fieldTransforms.push(` ${fieldName}: typeof b.${fieldName} === 'string' ? parseInt(b.${fieldName}, 10) : b.${fieldName} as number`);
|
|
127
|
+
}
|
|
128
|
+
// Handle pagination
|
|
129
|
+
if (inputConfig.pagination) {
|
|
130
|
+
fieldDeclarations.push(` readonly page: number;`);
|
|
131
|
+
fieldDeclarations.push(` readonly limit: number;`);
|
|
132
|
+
constructorParams.push(`page: number`);
|
|
133
|
+
constructorParams.push(`limit: number`);
|
|
134
|
+
constructorAssignments.push(` this.page = page;`);
|
|
135
|
+
constructorAssignments.push(` this.limit = limit;`);
|
|
136
|
+
const defaultLimit = ((_a = inputConfig.pagination.defaults) === null || _a === void 0 ? void 0 : _a.limit) || 20;
|
|
137
|
+
const maxLimit = ((_b = inputConfig.pagination.defaults) === null || _b === void 0 ? void 0 : _b.maxLimit) || 100;
|
|
138
|
+
fieldTransforms.push(` page: typeof b.page === 'string' ? parseInt(b.page, 10) : (b.page as number || 1)`);
|
|
139
|
+
fieldTransforms.push(` limit: Math.min(typeof b.limit === 'string' ? parseInt(b.limit, 10) : (b.limit as number || ${defaultLimit}), ${maxLimit})`);
|
|
140
|
+
if (inputConfig.pagination.type === 'cursor') {
|
|
141
|
+
fieldDeclarations.push(` readonly cursor?: string;`);
|
|
142
|
+
constructorParams.push(`cursor?: string`);
|
|
143
|
+
constructorAssignments.push(` this.cursor = cursor;`);
|
|
144
|
+
fieldTransforms.push(` cursor: b.cursor as string | undefined`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Handle fields from aggregate (pick/omit/add)
|
|
148
|
+
if (inputConfig.from) {
|
|
149
|
+
const aggregateFields = Object.entries(aggregateConfig.fields);
|
|
150
|
+
let fieldsToInclude = aggregateFields;
|
|
151
|
+
// Apply pick
|
|
152
|
+
if (inputConfig.pick && inputConfig.pick.length > 0) {
|
|
153
|
+
fieldsToInclude = fieldsToInclude.filter(([fieldName]) => inputConfig.pick.includes(fieldName));
|
|
154
|
+
}
|
|
155
|
+
// Apply omit
|
|
156
|
+
if (inputConfig.omit && inputConfig.omit.length > 0) {
|
|
157
|
+
fieldsToInclude = fieldsToInclude.filter(([fieldName]) => !inputConfig.omit.includes(fieldName));
|
|
158
|
+
}
|
|
159
|
+
const isCreateAction = !inputConfig.identifier && !inputConfig.partial;
|
|
160
|
+
if (isCreateAction) {
|
|
161
|
+
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
162
|
+
fieldDeclarations.push(` readonly ${ownerOrParentField}: number;`);
|
|
163
|
+
constructorParams.push(`${ownerOrParentField}: number`);
|
|
164
|
+
constructorAssignments.push(` this.${ownerOrParentField} = ${ownerOrParentField};`);
|
|
165
|
+
validationChecks.push(` if (b.${ownerOrParentField} === undefined || b.${ownerOrParentField} === null) {
|
|
166
|
+
throw new Error('${ownerOrParentField} is required');
|
|
167
|
+
}`);
|
|
168
|
+
fieldTransforms.push(` ${ownerOrParentField}: typeof b.${ownerOrParentField} === 'string' ? parseInt(b.${ownerOrParentField}, 10) : b.${ownerOrParentField} as number`);
|
|
169
|
+
}
|
|
170
|
+
// Add fields
|
|
171
|
+
fieldsToInclude.forEach(([fieldName, fieldConfig]) => {
|
|
172
|
+
if (fieldName === 'id' || fieldConfig.auto)
|
|
173
|
+
return;
|
|
174
|
+
const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
|
|
175
|
+
const tsType = isAggRef ? 'number' : this.mapType(fieldConfig.type);
|
|
176
|
+
const effectiveFieldType = isAggRef ? 'number' : fieldConfig.type;
|
|
177
|
+
// Aggregate references are always optional in DTOs; other fields default to required
|
|
178
|
+
const isRequired = !isAggRef && !inputConfig.partial && fieldConfig.required !== false;
|
|
179
|
+
const optional = isRequired ? '' : '?';
|
|
180
|
+
fieldDeclarations.push(` readonly ${fieldName}${optional}: ${tsType};`);
|
|
181
|
+
constructorParams.push(`${fieldName}${optional}: ${tsType}`);
|
|
182
|
+
constructorAssignments.push(` this.${fieldName} = ${fieldName};`);
|
|
183
|
+
validationChecks.push(...this.getValidationCode(fieldName, effectiveFieldType, isRequired));
|
|
184
|
+
fieldTransforms.push(` ${fieldName}: ${this.getTransformCode(fieldName, effectiveFieldType)}`);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Handle filters
|
|
188
|
+
if (inputConfig.filters) {
|
|
189
|
+
Object.entries(inputConfig.filters).forEach(([filterName, filterConfig]) => {
|
|
190
|
+
const tsType = this.mapType(filterConfig.type);
|
|
191
|
+
const isRequired = !filterConfig.optional;
|
|
192
|
+
const optional = isRequired ? '' : '?';
|
|
193
|
+
fieldDeclarations.push(` readonly ${filterName}${optional}: ${tsType};`);
|
|
194
|
+
constructorParams.push(`${filterName}${optional}: ${tsType}`);
|
|
195
|
+
constructorAssignments.push(` this.${filterName} = ${filterName};`);
|
|
196
|
+
if (isRequired) {
|
|
197
|
+
validationChecks.push(...this.getValidationCode(filterName, filterConfig.type, true));
|
|
198
|
+
}
|
|
199
|
+
fieldTransforms.push(` ${filterName}: ${this.getTransformCode(filterName, filterConfig.type)}`);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// Handle sorting
|
|
203
|
+
if (inputConfig.sorting) {
|
|
204
|
+
fieldDeclarations.push(` readonly sortBy?: string;`);
|
|
205
|
+
fieldDeclarations.push(` readonly sortOrder?: 'asc' | 'desc';`);
|
|
206
|
+
constructorParams.push(`sortBy?: string`);
|
|
207
|
+
constructorParams.push(`sortOrder?: 'asc' | 'desc'`);
|
|
208
|
+
constructorAssignments.push(` this.sortBy = sortBy;`);
|
|
209
|
+
constructorAssignments.push(` this.sortOrder = sortOrder;`);
|
|
210
|
+
const allowedFields = inputConfig.sorting.allow.map(f => `'${f}'`).join(', ');
|
|
211
|
+
const defaultField = ((_c = inputConfig.sorting.default) === null || _c === void 0 ? void 0 : _c.field) || inputConfig.sorting.allow[0];
|
|
212
|
+
const defaultOrder = ((_d = inputConfig.sorting.default) === null || _d === void 0 ? void 0 : _d.order) || 'asc';
|
|
213
|
+
fieldTransforms.push(` sortBy: [${allowedFields}].includes(b.sortBy as string) ? b.sortBy as string : '${defaultField}'`);
|
|
214
|
+
fieldTransforms.push(` sortOrder: b.sortOrder === 'desc' ? 'desc' : '${defaultOrder}'`);
|
|
215
|
+
}
|
|
216
|
+
const fieldsStr = fieldDeclarations.length > 0 ? fieldDeclarations.join('\n') : ' // No fields';
|
|
217
|
+
const paramsStr = constructorParams.join(', ');
|
|
218
|
+
const assignmentsStr = constructorAssignments.join('\n');
|
|
219
|
+
const validationsStr = validationChecks.length > 0 ? '\n' + validationChecks.join('\n') + '\n' : '';
|
|
220
|
+
const transformsStr = fieldTransforms.join(',\n');
|
|
221
|
+
return `export class ${className} {
|
|
222
|
+
${fieldsStr}
|
|
223
|
+
|
|
224
|
+
private constructor(data: { ${constructorParams.map(p => p.replace('readonly ', '')).join('; ')} }) {
|
|
225
|
+
${constructorAssignments.map(a => a.replace('this.', 'this.').replace(' = ', ' = data.')).join('\n').replace(/= data\./g, '= data.').split('\n').map(line => {
|
|
226
|
+
const match = line.match(/this\.(\w+) = data\.(\w+)/);
|
|
227
|
+
if (match) {
|
|
228
|
+
return ` this.${match[1]} = data.${match[1]};`;
|
|
229
|
+
}
|
|
230
|
+
return line;
|
|
231
|
+
}).join('\n')}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static parse(body: unknown): ${className} {
|
|
235
|
+
if (!body || typeof body !== 'object') {
|
|
236
|
+
throw new Error('Invalid request body');
|
|
237
|
+
}
|
|
238
|
+
const b = body as Record<string, unknown>;
|
|
239
|
+
${validationsStr}
|
|
240
|
+
return new ${className}({
|
|
241
|
+
${transformsStr}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}`;
|
|
245
|
+
}
|
|
246
|
+
generateOutputDto(modelName, actionName, outputConfig, aggregateConfig, allAggregates) {
|
|
247
|
+
const className = `${modelName}${(0, typeUtils_1.capitalize)(actionName)}Output`;
|
|
248
|
+
if (outputConfig === 'void') {
|
|
249
|
+
return `export type ${className} = void;`;
|
|
250
|
+
}
|
|
251
|
+
const fieldDeclarations = [];
|
|
252
|
+
const constructorParams = [];
|
|
253
|
+
const fromMappings = [];
|
|
254
|
+
// Always include id for entity outputs
|
|
255
|
+
if (outputConfig.from) {
|
|
256
|
+
fieldDeclarations.push(` readonly id: number;`);
|
|
257
|
+
constructorParams.push(`id: number`);
|
|
258
|
+
fromMappings.push(` id: entity.id`);
|
|
259
|
+
}
|
|
260
|
+
// Handle fields from aggregate (pick)
|
|
261
|
+
if (outputConfig.from) {
|
|
262
|
+
const aggregateFields = Object.entries(aggregateConfig.fields);
|
|
263
|
+
let fieldsToInclude = aggregateFields;
|
|
264
|
+
// Apply pick
|
|
265
|
+
if (outputConfig.pick && outputConfig.pick.length > 0) {
|
|
266
|
+
fieldsToInclude = fieldsToInclude.filter(([fieldName]) => outputConfig.pick.includes(fieldName));
|
|
267
|
+
}
|
|
268
|
+
// Add fields
|
|
269
|
+
fieldsToInclude.forEach(([fieldName, fieldConfig]) => {
|
|
270
|
+
if (fieldName === 'id')
|
|
271
|
+
return;
|
|
272
|
+
const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
|
|
273
|
+
const tsType = isAggRef ? 'number' : this.mapType(fieldConfig.type);
|
|
274
|
+
const isOptional = fieldConfig.required === false || isAggRef;
|
|
275
|
+
const optional = isOptional ? '?' : '';
|
|
276
|
+
fieldDeclarations.push(` readonly ${fieldName}${optional}: ${tsType};`);
|
|
277
|
+
constructorParams.push(`${fieldName}${optional}: ${tsType}`);
|
|
278
|
+
fromMappings.push(isAggRef
|
|
279
|
+
? ` ${fieldName}: entity.${fieldName}?.id`
|
|
280
|
+
: ` ${fieldName}: entity.${fieldName}`);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// Handle includes (nested objects)
|
|
284
|
+
if (outputConfig.include) {
|
|
285
|
+
Object.entries(outputConfig.include).forEach(([includeName, includeConfig]) => {
|
|
286
|
+
const relatedAggregate = allAggregates.get(includeConfig.from);
|
|
287
|
+
if (relatedAggregate && includeConfig.pick && includeConfig.pick.length > 0) {
|
|
288
|
+
// Filter out 'id' from picked fields since we add it separately
|
|
289
|
+
const pickedFieldsFiltered = includeConfig.pick.filter(f => f !== 'id');
|
|
290
|
+
const pickedFields = pickedFieldsFiltered.map(fieldName => {
|
|
291
|
+
const fieldConfig = relatedAggregate.fields[fieldName];
|
|
292
|
+
if (fieldConfig) {
|
|
293
|
+
const tsType = this.mapType(fieldConfig.type);
|
|
294
|
+
return `${fieldName}: ${tsType}`;
|
|
295
|
+
}
|
|
296
|
+
return `${fieldName}: any`;
|
|
297
|
+
}).join('; ');
|
|
298
|
+
const fieldsStr = pickedFields ? `id: number; ${pickedFields}` : 'id: number';
|
|
299
|
+
fieldDeclarations.push(` readonly ${includeName}?: { ${fieldsStr} };`);
|
|
300
|
+
constructorParams.push(`${includeName}?: { ${fieldsStr} }`);
|
|
301
|
+
fromMappings.push(` ${includeName}: (entity as any).${includeName}`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
fieldDeclarations.push(` readonly ${includeName}?: ${includeConfig.from};`);
|
|
305
|
+
constructorParams.push(`${includeName}?: ${includeConfig.from}`);
|
|
306
|
+
fromMappings.push(` ${includeName}: (entity as any).${includeName}`);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// Handle additional fields
|
|
311
|
+
if (outputConfig.add) {
|
|
312
|
+
Object.entries(outputConfig.add).forEach(([fieldName, fieldDef]) => {
|
|
313
|
+
const tsType = this.mapType(fieldDef.type);
|
|
314
|
+
fieldDeclarations.push(` readonly ${fieldName}: ${tsType};`);
|
|
315
|
+
constructorParams.push(`${fieldName}: ${tsType}`);
|
|
316
|
+
fromMappings.push(` ${fieldName}: (entity as any).${fieldName}`);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const fieldsStr = fieldDeclarations.length > 0 ? fieldDeclarations.join('\n') : ' // No fields';
|
|
320
|
+
const paramsStr = constructorParams.map(p => p.replace('readonly ', '')).join('; ');
|
|
321
|
+
const mappingsStr = fromMappings.join(',\n');
|
|
322
|
+
// Wrap in pagination if needed
|
|
323
|
+
if (outputConfig.pagination) {
|
|
324
|
+
return `export class ${className}Item {
|
|
325
|
+
${fieldsStr}
|
|
326
|
+
|
|
327
|
+
private constructor(data: { ${paramsStr} }) {
|
|
328
|
+
${fieldDeclarations.map(d => {
|
|
329
|
+
const match = d.match(/readonly (\w+)/);
|
|
330
|
+
if (match)
|
|
331
|
+
return ` this.${match[1]} = data.${match[1]};`;
|
|
332
|
+
return '';
|
|
333
|
+
}).filter(Boolean).join('\n')}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static from(entity: ${modelName}): ${className}Item {
|
|
337
|
+
return new ${className}Item({
|
|
338
|
+
${mappingsStr}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export class ${className} {
|
|
344
|
+
readonly items: ${className}Item[];
|
|
345
|
+
readonly total: number;
|
|
346
|
+
readonly page?: number;
|
|
347
|
+
readonly limit?: number;
|
|
348
|
+
|
|
349
|
+
private constructor(data: { items: ${className}Item[]; total: number; page?: number; limit?: number }) {
|
|
350
|
+
this.items = data.items;
|
|
351
|
+
this.total = data.total;
|
|
352
|
+
this.page = data.page;
|
|
353
|
+
this.limit = data.limit;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
static from(data: { items: ${modelName}[]; total: number; page?: number; limit?: number }): ${className} {
|
|
357
|
+
return new ${className}({
|
|
358
|
+
items: data.items.map(item => ${className}Item.from(item)),
|
|
359
|
+
total: data.total,
|
|
360
|
+
page: data.page,
|
|
361
|
+
limit: data.limit
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}`;
|
|
365
|
+
}
|
|
366
|
+
// List action without pagination: still wrap items in a list container
|
|
367
|
+
if (actionName === 'list') {
|
|
368
|
+
return `export class ${className}Item {
|
|
369
|
+
${fieldsStr}
|
|
370
|
+
|
|
371
|
+
private constructor(data: { ${paramsStr} }) {
|
|
372
|
+
${fieldDeclarations.map(d => {
|
|
373
|
+
const match = d.match(/readonly (\w+)/);
|
|
374
|
+
if (match)
|
|
375
|
+
return ` this.${match[1]} = data.${match[1]};`;
|
|
376
|
+
return '';
|
|
377
|
+
}).filter(Boolean).join('\n')}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static from(entity: ${modelName}): ${className}Item {
|
|
381
|
+
return new ${className}Item({
|
|
382
|
+
${mappingsStr}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export class ${className} {
|
|
388
|
+
readonly items: ${className}Item[];
|
|
389
|
+
|
|
390
|
+
private constructor(data: { items: ${className}Item[] }) {
|
|
391
|
+
this.items = data.items;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
static from(data: { items: ${modelName}[] }): ${className} {
|
|
395
|
+
return new ${className}({
|
|
396
|
+
items: data.items.map(item => ${className}Item.from(item))
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}`;
|
|
400
|
+
}
|
|
401
|
+
return `export class ${className} {
|
|
402
|
+
${fieldsStr}
|
|
403
|
+
|
|
404
|
+
private constructor(data: { ${paramsStr} }) {
|
|
405
|
+
${fieldDeclarations.map(d => {
|
|
406
|
+
const match = d.match(/readonly (\w+)/);
|
|
407
|
+
if (match)
|
|
408
|
+
return ` this.${match[1]} = data.${match[1]};`;
|
|
409
|
+
return '';
|
|
410
|
+
}).filter(Boolean).join('\n')}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
static from(entity: ${modelName}): ${className} {
|
|
414
|
+
return new ${className}({
|
|
415
|
+
${mappingsStr}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}`;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Collect types that need to be imported for a use case DTO.
|
|
422
|
+
*/
|
|
423
|
+
collectRequiredImports(modelName, aggregateConfig, outputConfig) {
|
|
424
|
+
const valueObjects = new Set();
|
|
425
|
+
const entities = new Set();
|
|
426
|
+
// Check aggregate fields for value object types
|
|
427
|
+
if (outputConfig !== 'void' && outputConfig.from) {
|
|
428
|
+
const aggregateFields = Object.entries(aggregateConfig.fields);
|
|
429
|
+
let fieldsToCheck = aggregateFields;
|
|
430
|
+
// Apply pick filter if specified
|
|
431
|
+
if (outputConfig.pick && outputConfig.pick.length > 0) {
|
|
432
|
+
fieldsToCheck = fieldsToCheck.filter(([fieldName]) => outputConfig.pick.includes(fieldName));
|
|
433
|
+
}
|
|
434
|
+
// Check each field for value object types
|
|
435
|
+
fieldsToCheck.forEach(([, fieldConfig]) => {
|
|
436
|
+
if (this.isValueObjectType(fieldConfig.type)) {
|
|
437
|
+
valueObjects.add((0, typeUtils_1.capitalize)(fieldConfig.type));
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
// Check include for child entities
|
|
442
|
+
if (outputConfig !== 'void' && outputConfig.include) {
|
|
443
|
+
Object.entries(outputConfig.include).forEach(([, includeConfig]) => {
|
|
444
|
+
if (includeConfig.from && includeConfig.from !== modelName) {
|
|
445
|
+
entities.add(includeConfig.from);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return { valueObjects, entities };
|
|
450
|
+
}
|
|
451
|
+
generateFromConfig(config) {
|
|
452
|
+
const result = {};
|
|
453
|
+
// Collect all aggregates
|
|
454
|
+
if (config.domain.aggregates) {
|
|
455
|
+
Object.entries(config.domain.aggregates).forEach(([name, aggConfig]) => {
|
|
456
|
+
this.availableAggregates.set(name, aggConfig);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
// Collect all value objects
|
|
460
|
+
if (config.domain.valueObjects) {
|
|
461
|
+
Object.entries(config.domain.valueObjects).forEach(([name, voConfig]) => {
|
|
462
|
+
this.availableValueObjects.set(name, voConfig);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
|
|
466
|
+
// Generate DTOs for each use case
|
|
467
|
+
Object.entries(config.useCases).forEach(([modelName, useCases]) => {
|
|
468
|
+
const aggregateConfig = this.availableAggregates.get(modelName);
|
|
469
|
+
if (!aggregateConfig) {
|
|
470
|
+
console.warn(`Warning: No aggregate found for model ${modelName}`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const childInfo = childEntityMap.get(modelName);
|
|
474
|
+
Object.entries(useCases).forEach(([actionName, useCaseConfig]) => {
|
|
475
|
+
// Generate Input DTO
|
|
476
|
+
const inputDto = this.generateInputDto(modelName, actionName, useCaseConfig.input, aggregateConfig, childInfo);
|
|
477
|
+
// Generate Output DTO
|
|
478
|
+
const outputDto = this.generateOutputDto(modelName, actionName, useCaseConfig.output || 'void', aggregateConfig, this.availableAggregates);
|
|
479
|
+
// Collect required imports
|
|
480
|
+
const imports = [];
|
|
481
|
+
const needsModelImport = useCaseConfig.output !== 'void';
|
|
482
|
+
if (needsModelImport) {
|
|
483
|
+
imports.push(`import { ${modelName} } from '../../domain/entities/${modelName}';`);
|
|
484
|
+
}
|
|
485
|
+
// Collect value objects and entities needed
|
|
486
|
+
const { valueObjects, entities } = this.collectRequiredImports(modelName, aggregateConfig, useCaseConfig.output || 'void');
|
|
487
|
+
// Add value object imports
|
|
488
|
+
valueObjects.forEach(voName => {
|
|
489
|
+
imports.push(`import { ${voName} } from '../../domain/valueObjects/${voName}';`);
|
|
490
|
+
});
|
|
491
|
+
// Add entity imports for includes
|
|
492
|
+
entities.forEach(entityName => {
|
|
493
|
+
imports.push(`import { ${entityName} } from '../../domain/entities/${entityName}';`);
|
|
494
|
+
});
|
|
495
|
+
const importStatement = imports.length > 0 ? imports.join('\n') + '\n\n' : '';
|
|
496
|
+
const dtoFileName = `${modelName}${(0, typeUtils_1.capitalize)(actionName)}`;
|
|
497
|
+
result[dtoFileName] = `${importStatement}${inputDto}\n\n${outputDto}`;
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
generateFromYamlFile(yamlFilePath) {
|
|
503
|
+
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
504
|
+
const config = (0, yaml_1.parse)(yamlContent);
|
|
505
|
+
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
506
|
+
throw new Error('Configuration does not match new module format. Expected useCases structure.');
|
|
507
|
+
}
|
|
508
|
+
return this.generateFromConfig(config);
|
|
509
|
+
}
|
|
510
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
511
|
+
const dtosByName = this.generateFromYamlFile(yamlFilePath);
|
|
512
|
+
const dtoDir = path.join(moduleDir, 'application', 'dto');
|
|
513
|
+
fs.mkdirSync(dtoDir, { recursive: true });
|
|
514
|
+
for (const [name, code] of Object.entries(dtosByName)) {
|
|
515
|
+
const filePath = path.join(dtoDir, `${name}.ts`);
|
|
516
|
+
// eslint-disable-next-line no-await-in-loop
|
|
517
|
+
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) });
|
|
518
|
+
}
|
|
519
|
+
// eslint-disable-next-line no-console
|
|
520
|
+
console.log('\n' + colors_1.colors.green('DTO files generated successfully!') + '\n');
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
exports.DtoGenerator = DtoGenerator;
|
|
@@ -1,57 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
name: string;
|
|
3
|
-
type: string;
|
|
4
|
-
required?: boolean;
|
|
5
|
-
unique?: boolean;
|
|
6
|
-
auto?: boolean;
|
|
7
|
-
displayFields?: string[];
|
|
8
|
-
}
|
|
9
|
-
interface ActionConfig {
|
|
10
|
-
handlers: string[];
|
|
11
|
-
}
|
|
12
|
-
interface PermissionConfig {
|
|
13
|
-
role: string;
|
|
14
|
-
actions: string[];
|
|
15
|
-
}
|
|
16
|
-
interface ModelConfig {
|
|
17
|
-
name: string;
|
|
18
|
-
fields: FieldConfig[];
|
|
19
|
-
}
|
|
20
|
-
type ModuleConfig = {
|
|
21
|
-
models?: ModelConfig[];
|
|
22
|
-
actions?: Record<string, ActionConfig>;
|
|
23
|
-
permissions?: PermissionConfig[];
|
|
24
|
-
};
|
|
1
|
+
import { ModuleConfig } from '../types/configTypes';
|
|
25
2
|
export declare class ServiceGenerator {
|
|
26
|
-
private
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
private
|
|
30
|
-
private
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
private
|
|
39
|
-
private
|
|
40
|
-
private
|
|
41
|
-
private
|
|
42
|
-
private
|
|
43
|
-
private
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
generateServiceForModel(model: ModelConfig, moduleName: string, moduleConfig: ModuleConfig, hasGlobalPermissions: boolean): string;
|
|
47
|
-
generateService(moduleName: string, moduleConfig: ModuleConfig, hasGlobalPermissions: boolean): string;
|
|
48
|
-
private generateForeignStoreImports;
|
|
49
|
-
private generateForeignStoreConstructorParams;
|
|
50
|
-
private generateCustomImports;
|
|
3
|
+
private availableAggregates;
|
|
4
|
+
private mapType;
|
|
5
|
+
private getDefaultHandlerReturnType;
|
|
6
|
+
private buildHandlerContextMap;
|
|
7
|
+
private deriveInputType;
|
|
8
|
+
private deriveCustomHandlerTypes;
|
|
9
|
+
private getInputDtoFields;
|
|
10
|
+
private computeDtoFieldsForHandler;
|
|
11
|
+
private generateListHandler;
|
|
12
|
+
private generateGetHandler;
|
|
13
|
+
private generateCreateHandler;
|
|
14
|
+
private generateUpdateHandler;
|
|
15
|
+
private generateDeleteHandler;
|
|
16
|
+
private generateDefaultHandlerMethod;
|
|
17
|
+
private generateCustomHandlerMethod;
|
|
18
|
+
private collectHandlers;
|
|
19
|
+
private generateListByParentMethod;
|
|
20
|
+
private generateGetResourceOwnerMethod;
|
|
21
|
+
private generateService;
|
|
22
|
+
generateFromConfig(config: ModuleConfig): Record<string, string>;
|
|
51
23
|
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
52
|
-
generateAndSaveFiles(yamlFilePath
|
|
24
|
+
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
53
25
|
force?: boolean;
|
|
54
26
|
skipOnConflict?: boolean;
|
|
55
27
|
}): Promise<void>;
|
|
56
28
|
}
|
|
57
|
-
export {};
|