@currentjs/gen 0.5.5 ā 0.5.6
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 +4 -0
- package/README.md +2 -0
- package/dist/commands/generateAll.js +7 -6
- package/dist/commands/migrateCommit.js +6 -3
- package/dist/generators/controllerGenerator.d.ts +5 -4
- package/dist/generators/controllerGenerator.js +13 -7
- package/dist/generators/domainLayerGenerator.d.ts +5 -4
- package/dist/generators/domainLayerGenerator.js +11 -8
- package/dist/generators/dtoGenerator.d.ts +5 -4
- package/dist/generators/dtoGenerator.js +29 -16
- package/dist/generators/serviceGenerator.d.ts +5 -4
- package/dist/generators/serviceGenerator.js +23 -14
- package/dist/generators/storeGenerator.d.ts +11 -4
- package/dist/generators/storeGenerator.js +147 -24
- package/dist/generators/templates/data/appYamlTemplate +1 -1
- package/dist/generators/templates/storeTemplates.d.ts +1 -1
- package/dist/generators/templates/storeTemplates.js +20 -19
- package/dist/generators/useCaseGenerator.d.ts +5 -4
- package/dist/generators/useCaseGenerator.js +10 -7
- package/dist/types/configTypes.d.ts +3 -0
- package/dist/types/configTypes.js +15 -0
- package/dist/utils/commandUtils.d.ts +1 -1
- package/dist/utils/commandUtils.js +5 -3
- package/dist/utils/migrationUtils.d.ts +8 -6
- package/dist/utils/migrationUtils.js +37 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -492,6 +492,8 @@ Collects all aggregate definitions from module YAMLs, compares them against the
|
|
|
492
492
|
|
|
493
493
|
The migration file contains `CREATE TABLE`, `ALTER TABLE ADD/MODIFY/DROP COLUMN`, and `DROP TABLE` statements as needed. Foreign keys, indexes, and standard timestamp columns (`created_at`, `updated_at`, `deleted_at`) are handled automatically.
|
|
494
494
|
|
|
495
|
+
The SQL types of primary keys and foreign keys are determined by the `config.identifiers` setting in `app.yaml`.
|
|
496
|
+
|
|
495
497
|
After generating the file, the schema state is updated so the next `migrate commit` only produces a diff of subsequent changes.
|
|
496
498
|
|
|
497
499
|
### `migrate push` *(not yet implemented)*
|
|
@@ -81,18 +81,19 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
81
81
|
const moduleDir = path.dirname(moduleYamlPath);
|
|
82
82
|
// eslint-disable-next-line no-console
|
|
83
83
|
console.log(colors_1.colors.blue(`\nGenerating module: ${path.basename(moduleDir)}`));
|
|
84
|
+
const identifiers = entry.identifiers;
|
|
84
85
|
// eslint-disable-next-line no-await-in-loop
|
|
85
|
-
await domainGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
86
|
+
await domainGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts, identifiers);
|
|
86
87
|
// eslint-disable-next-line no-await-in-loop
|
|
87
|
-
await dtoGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
88
|
+
await dtoGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts, identifiers);
|
|
88
89
|
// eslint-disable-next-line no-await-in-loop
|
|
89
|
-
await useCaseGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
90
|
+
await useCaseGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts, identifiers);
|
|
90
91
|
// eslint-disable-next-line no-await-in-loop
|
|
91
|
-
await serviceGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
92
|
+
await serviceGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts, identifiers);
|
|
92
93
|
// eslint-disable-next-line no-await-in-loop
|
|
93
|
-
await storeGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
94
|
+
await storeGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts, identifiers);
|
|
94
95
|
// eslint-disable-next-line no-await-in-loop
|
|
95
|
-
await controllerGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
96
|
+
await controllerGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts, identifiers);
|
|
96
97
|
// eslint-disable-next-line no-await-in-loop
|
|
97
98
|
await templateGen.generateAndSaveFiles(moduleYamlPath, moduleDir, { force: opts === null || opts === void 0 ? void 0 : opts.force, skipOnConflict: opts === null || opts === void 0 ? void 0 : opts.skip, onlyIfMissing: !(opts === null || opts === void 0 ? void 0 : opts.withTemplates) });
|
|
98
99
|
// Find srcDir by probing upward for app.ts
|
|
@@ -43,12 +43,15 @@ const commandUtils_1 = require("../utils/commandUtils");
|
|
|
43
43
|
const configTypes_1 = require("../types/configTypes");
|
|
44
44
|
const migrationUtils_1 = require("../utils/migrationUtils");
|
|
45
45
|
function collectSchemaFromModules(appYamlPath) {
|
|
46
|
+
var _a, _b;
|
|
46
47
|
const appConfig = (0, commandUtils_1.loadAppConfig)(appYamlPath);
|
|
47
48
|
const moduleEntries = (0, commandUtils_1.getModuleEntries)(appConfig);
|
|
48
49
|
const projectRoot = path.dirname(appYamlPath);
|
|
49
50
|
const allAggregates = {};
|
|
50
51
|
const allValueObjects = new Set();
|
|
51
52
|
const sources = [];
|
|
53
|
+
const rawIdentifiers = (_b = (_a = appConfig.config) === null || _a === void 0 ? void 0 : _a.identifiers) !== null && _b !== void 0 ? _b : 'numeric';
|
|
54
|
+
const identifiers = (0, configTypes_1.normalizeIdentifierType)(rawIdentifiers);
|
|
52
55
|
for (const entry of moduleEntries) {
|
|
53
56
|
const moduleYamlPath = path.isAbsolute(entry.path)
|
|
54
57
|
? entry.path
|
|
@@ -79,7 +82,7 @@ function collectSchemaFromModules(appYamlPath) {
|
|
|
79
82
|
// eslint-disable-next-line no-console
|
|
80
83
|
console.log(colors_1.colors.gray(` Sources: ${sources.join(', ')}`));
|
|
81
84
|
}
|
|
82
|
-
return { aggregates: allAggregates, valueObjects: allValueObjects };
|
|
85
|
+
return { aggregates: allAggregates, valueObjects: allValueObjects, identifiers };
|
|
83
86
|
}
|
|
84
87
|
function handleMigrateCommit(yamlPath) {
|
|
85
88
|
try {
|
|
@@ -100,7 +103,7 @@ function handleMigrateCommit(yamlPath) {
|
|
|
100
103
|
}
|
|
101
104
|
// eslint-disable-next-line no-console
|
|
102
105
|
console.log(colors_1.colors.cyan('\nš Collecting aggregates from all modules...'));
|
|
103
|
-
const { aggregates: currentAggregates, valueObjects: currentValueObjects } = collectSchemaFromModules(resolvedYamlPath);
|
|
106
|
+
const { aggregates: currentAggregates, valueObjects: currentValueObjects, identifiers } = collectSchemaFromModules(resolvedYamlPath);
|
|
104
107
|
if (Object.keys(currentAggregates).length === 0) {
|
|
105
108
|
// eslint-disable-next-line no-console
|
|
106
109
|
console.log(colors_1.colors.yellow('ā ļø No aggregates found in module configuration.'));
|
|
@@ -119,7 +122,7 @@ function handleMigrateCommit(yamlPath) {
|
|
|
119
122
|
}
|
|
120
123
|
// eslint-disable-next-line no-console
|
|
121
124
|
console.log(colors_1.colors.cyan('\nš Comparing schemas...'));
|
|
122
|
-
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates, currentValueObjects);
|
|
125
|
+
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates, currentValueObjects, identifiers);
|
|
123
126
|
if (sqlStatements.length === 0 || sqlStatements.every(s => s.trim() === '' || s.startsWith('--'))) {
|
|
124
127
|
// eslint-disable-next-line no-console
|
|
125
128
|
console.log(colors_1.colors.yellow('ā ļø No changes detected. Schema is up to date.'));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ModuleConfig } from '../types/configTypes';
|
|
1
|
+
import { ModuleConfig, IdentifierType } from '../types/configTypes';
|
|
2
2
|
export declare class ControllerGenerator {
|
|
3
|
+
private identifiers;
|
|
3
4
|
private getHttpDecorator;
|
|
4
5
|
private parseUseCase;
|
|
5
6
|
/**
|
|
@@ -52,10 +53,10 @@ export declare class ControllerGenerator {
|
|
|
52
53
|
private resolveLayout;
|
|
53
54
|
private generateApiController;
|
|
54
55
|
private generateWebController;
|
|
55
|
-
generateFromConfig(config: ModuleConfig): Record<string, string>;
|
|
56
|
-
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
56
|
+
generateFromConfig(config: ModuleConfig, identifiers?: IdentifierType): Record<string, string>;
|
|
57
|
+
generateFromYamlFile(yamlFilePath: string, identifiers?: IdentifierType): Record<string, string>;
|
|
57
58
|
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
58
59
|
force?: boolean;
|
|
59
60
|
skipOnConflict?: boolean;
|
|
60
|
-
}): Promise<string[]>;
|
|
61
|
+
}, identifiers?: IdentifierType): Promise<string[]>;
|
|
61
62
|
}
|
|
@@ -44,6 +44,9 @@ const childEntityUtils_1 = require("../utils/childEntityUtils");
|
|
|
44
44
|
const typeUtils_1 = require("../utils/typeUtils");
|
|
45
45
|
const constants_1 = require("../utils/constants");
|
|
46
46
|
class ControllerGenerator {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.identifiers = 'numeric';
|
|
49
|
+
}
|
|
47
50
|
getHttpDecorator(method) {
|
|
48
51
|
switch (method.toUpperCase()) {
|
|
49
52
|
case 'GET': return 'Get';
|
|
@@ -278,7 +281,8 @@ class ControllerGenerator {
|
|
|
278
281
|
parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ${childInfo.parentIdField}: context.request.parameters.${childInfo.parentIdField} });`;
|
|
279
282
|
}
|
|
280
283
|
else {
|
|
281
|
-
|
|
284
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
285
|
+
parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ownerId: context.request.user?.id as ${idTs} });`;
|
|
282
286
|
}
|
|
283
287
|
}
|
|
284
288
|
else if (action === 'update') {
|
|
@@ -428,7 +432,8 @@ class ControllerGenerator {
|
|
|
428
432
|
parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ${childInfo.parentIdField}: context.request.parameters.${childInfo.parentIdField} });`;
|
|
429
433
|
}
|
|
430
434
|
else {
|
|
431
|
-
|
|
435
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
436
|
+
parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ownerId: context.request.user?.id as ${idTs} });`;
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
else {
|
|
@@ -633,8 +638,9 @@ export class ${controllerName} {
|
|
|
633
638
|
${methods.join('\n\n')}
|
|
634
639
|
}`;
|
|
635
640
|
}
|
|
636
|
-
generateFromConfig(config) {
|
|
641
|
+
generateFromConfig(config, identifiers = 'numeric') {
|
|
637
642
|
const result = {};
|
|
643
|
+
this.identifiers = identifiers;
|
|
638
644
|
const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
|
|
639
645
|
// Generate API controllers
|
|
640
646
|
if (config.api) {
|
|
@@ -655,16 +661,16 @@ ${methods.join('\n\n')}
|
|
|
655
661
|
}
|
|
656
662
|
return result;
|
|
657
663
|
}
|
|
658
|
-
generateFromYamlFile(yamlFilePath) {
|
|
664
|
+
generateFromYamlFile(yamlFilePath, identifiers = 'numeric') {
|
|
659
665
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
660
666
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
661
667
|
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
662
668
|
throw new Error('Configuration does not match new module format. Expected domain/useCases/api/web structure.');
|
|
663
669
|
}
|
|
664
|
-
return this.generateFromConfig(config);
|
|
670
|
+
return this.generateFromConfig(config, identifiers);
|
|
665
671
|
}
|
|
666
|
-
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
667
|
-
const controllersByName = this.generateFromYamlFile(yamlFilePath);
|
|
672
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts, identifiers = 'numeric') {
|
|
673
|
+
const controllersByName = this.generateFromYamlFile(yamlFilePath, identifiers);
|
|
668
674
|
const controllersDir = path.join(moduleDir, 'infrastructure', 'controllers');
|
|
669
675
|
fs.mkdirSync(controllersDir, { recursive: true });
|
|
670
676
|
const generatedPaths = [];
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { ModuleConfig } from '../types/configTypes';
|
|
1
|
+
import { ModuleConfig, IdentifierType } from '../types/configTypes';
|
|
2
2
|
export declare class DomainLayerGenerator {
|
|
3
3
|
private availableAggregates;
|
|
4
4
|
private availableValueObjects;
|
|
5
|
+
private identifiers;
|
|
5
6
|
private mapType;
|
|
6
7
|
private getDefaultValue;
|
|
7
8
|
private generateValueObject;
|
|
8
9
|
private generateAggregate;
|
|
9
|
-
generateFromConfig(config: ModuleConfig): Record<string, {
|
|
10
|
+
generateFromConfig(config: ModuleConfig, identifiers?: IdentifierType): Record<string, {
|
|
10
11
|
code: string;
|
|
11
12
|
type: 'entity' | 'valueObject';
|
|
12
13
|
}>;
|
|
13
|
-
generateFromYamlFile(yamlFilePath: string): Record<string, {
|
|
14
|
+
generateFromYamlFile(yamlFilePath: string, identifiers?: IdentifierType): Record<string, {
|
|
14
15
|
code: string;
|
|
15
16
|
type: 'entity' | 'valueObject';
|
|
16
17
|
}>;
|
|
17
18
|
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
18
19
|
force?: boolean;
|
|
19
20
|
skipOnConflict?: boolean;
|
|
20
|
-
}): Promise<void>;
|
|
21
|
+
}, identifiers?: IdentifierType): Promise<void>;
|
|
21
22
|
}
|
|
@@ -46,6 +46,7 @@ class DomainLayerGenerator {
|
|
|
46
46
|
constructor() {
|
|
47
47
|
this.availableAggregates = new Set();
|
|
48
48
|
this.availableValueObjects = new Set();
|
|
49
|
+
this.identifiers = 'numeric';
|
|
49
50
|
}
|
|
50
51
|
mapType(yamlType) {
|
|
51
52
|
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates, this.availableValueObjects);
|
|
@@ -154,12 +155,13 @@ class DomainLayerGenerator {
|
|
|
154
155
|
.join('\n');
|
|
155
156
|
const imports = [entityImports, valueObjectImports, aggregateRefImports].filter(Boolean).join('\n');
|
|
156
157
|
// Generate constructor parameters: id, then ownerId (root) or parentId field (child)
|
|
157
|
-
const
|
|
158
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
159
|
+
const constructorParams = [`public id: ${idTs}`];
|
|
158
160
|
if (childInfo) {
|
|
159
|
-
constructorParams.push(`public ${childInfo.parentIdField}:
|
|
161
|
+
constructorParams.push(`public ${childInfo.parentIdField}: ${idTs}`);
|
|
160
162
|
}
|
|
161
163
|
else {
|
|
162
|
-
constructorParams.push(
|
|
164
|
+
constructorParams.push(`public ownerId: ${idTs}`);
|
|
163
165
|
}
|
|
164
166
|
// Sort fields: required first, then optional
|
|
165
167
|
// Fields are required by default unless required: false
|
|
@@ -256,8 +258,9 @@ class DomainLayerGenerator {
|
|
|
256
258
|
${setterMethods}
|
|
257
259
|
}`;
|
|
258
260
|
}
|
|
259
|
-
generateFromConfig(config) {
|
|
261
|
+
generateFromConfig(config, identifiers = 'numeric') {
|
|
260
262
|
const result = {};
|
|
263
|
+
this.identifiers = identifiers;
|
|
261
264
|
// First pass: collect all aggregate and value object names
|
|
262
265
|
if (config.domain.aggregates) {
|
|
263
266
|
Object.keys(config.domain.aggregates).forEach(name => {
|
|
@@ -291,16 +294,16 @@ ${setterMethods}
|
|
|
291
294
|
}
|
|
292
295
|
return result;
|
|
293
296
|
}
|
|
294
|
-
generateFromYamlFile(yamlFilePath) {
|
|
297
|
+
generateFromYamlFile(yamlFilePath, identifiers = 'numeric') {
|
|
295
298
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
296
299
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
297
300
|
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
298
301
|
throw new Error('Configuration does not match new module format. Expected domain.aggregates structure.');
|
|
299
302
|
}
|
|
300
|
-
return this.generateFromConfig(config);
|
|
303
|
+
return this.generateFromConfig(config, identifiers);
|
|
301
304
|
}
|
|
302
|
-
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
303
|
-
const codeByEntity = this.generateFromYamlFile(yamlFilePath);
|
|
305
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts, identifiers = 'numeric') {
|
|
306
|
+
const codeByEntity = this.generateFromYamlFile(yamlFilePath, identifiers);
|
|
304
307
|
const entitiesDir = path.join(moduleDir, 'domain', 'entities');
|
|
305
308
|
const valueObjectsDir = path.join(moduleDir, 'domain', 'valueObjects');
|
|
306
309
|
fs.mkdirSync(entitiesDir, { recursive: true });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ModuleConfig } from '../types/configTypes';
|
|
1
|
+
import { ModuleConfig, IdentifierType } from '../types/configTypes';
|
|
2
2
|
export declare class DtoGenerator {
|
|
3
3
|
private availableAggregates;
|
|
4
4
|
private availableValueObjects;
|
|
5
|
+
private identifiers;
|
|
5
6
|
private mapType;
|
|
6
7
|
private isValueObjectType;
|
|
7
8
|
private getValidationCode;
|
|
@@ -12,10 +13,10 @@ export declare class DtoGenerator {
|
|
|
12
13
|
* Collect types that need to be imported for a use case DTO.
|
|
13
14
|
*/
|
|
14
15
|
private collectRequiredImports;
|
|
15
|
-
generateFromConfig(config: ModuleConfig): Record<string, string>;
|
|
16
|
-
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
16
|
+
generateFromConfig(config: ModuleConfig, identifiers?: IdentifierType): Record<string, string>;
|
|
17
|
+
generateFromYamlFile(yamlFilePath: string, identifiers?: IdentifierType): Record<string, string>;
|
|
17
18
|
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
18
19
|
force?: boolean;
|
|
19
20
|
skipOnConflict?: boolean;
|
|
20
|
-
}): Promise<void>;
|
|
21
|
+
}, identifiers?: IdentifierType): Promise<void>;
|
|
21
22
|
}
|
|
@@ -46,6 +46,7 @@ class DtoGenerator {
|
|
|
46
46
|
constructor() {
|
|
47
47
|
this.availableAggregates = new Map();
|
|
48
48
|
this.availableValueObjects = new Map();
|
|
49
|
+
this.identifiers = 'numeric';
|
|
49
50
|
}
|
|
50
51
|
mapType(yamlType) {
|
|
51
52
|
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates, this.availableValueObjects);
|
|
@@ -116,13 +117,17 @@ class DtoGenerator {
|
|
|
116
117
|
// Handle identifier (for get, update, delete)
|
|
117
118
|
if (inputConfig.identifier) {
|
|
118
119
|
const fieldName = inputConfig.identifier;
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
121
|
+
const idTransform = this.identifiers === 'numeric'
|
|
122
|
+
? `typeof b.${fieldName} === 'string' ? parseInt(b.${fieldName}, 10) : b.${fieldName} as number`
|
|
123
|
+
: `b.${fieldName} as string`;
|
|
124
|
+
fieldDeclarations.push(` readonly ${fieldName}: ${idTs};`);
|
|
125
|
+
constructorParams.push(`${fieldName}: ${idTs}`);
|
|
121
126
|
constructorAssignments.push(` this.${fieldName} = ${fieldName};`);
|
|
122
127
|
validationChecks.push(` if (b.${fieldName} === undefined || b.${fieldName} === null) {
|
|
123
128
|
throw new Error('${fieldName} is required');
|
|
124
129
|
}`);
|
|
125
|
-
fieldTransforms.push(` ${fieldName}:
|
|
130
|
+
fieldTransforms.push(` ${fieldName}: ${idTransform}`);
|
|
126
131
|
}
|
|
127
132
|
// Handle pagination
|
|
128
133
|
if (inputConfig.pagination) {
|
|
@@ -158,21 +163,26 @@ class DtoGenerator {
|
|
|
158
163
|
const isCreateAction = !inputConfig.identifier && !inputConfig.partial;
|
|
159
164
|
if (isCreateAction) {
|
|
160
165
|
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
167
|
+
const ownerTransform = this.identifiers === 'numeric'
|
|
168
|
+
? `typeof b.${ownerOrParentField} === 'string' ? parseInt(b.${ownerOrParentField}, 10) : b.${ownerOrParentField} as number`
|
|
169
|
+
: `b.${ownerOrParentField} as string`;
|
|
170
|
+
fieldDeclarations.push(` readonly ${ownerOrParentField}: ${idTs};`);
|
|
171
|
+
constructorParams.push(`${ownerOrParentField}: ${idTs}`);
|
|
163
172
|
constructorAssignments.push(` this.${ownerOrParentField} = ${ownerOrParentField};`);
|
|
164
173
|
validationChecks.push(` if (b.${ownerOrParentField} === undefined || b.${ownerOrParentField} === null) {
|
|
165
174
|
throw new Error('${ownerOrParentField} is required');
|
|
166
175
|
}`);
|
|
167
|
-
fieldTransforms.push(` ${ownerOrParentField}:
|
|
176
|
+
fieldTransforms.push(` ${ownerOrParentField}: ${ownerTransform}`);
|
|
168
177
|
}
|
|
169
178
|
// Add fields
|
|
179
|
+
const aggIdTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
170
180
|
fieldsToInclude.forEach(([fieldName, fieldConfig]) => {
|
|
171
181
|
if (fieldName === 'id' || fieldConfig.auto)
|
|
172
182
|
return;
|
|
173
183
|
const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
|
|
174
|
-
const tsType = isAggRef ?
|
|
175
|
-
const effectiveFieldType = isAggRef ? 'number' : fieldConfig.type;
|
|
184
|
+
const tsType = isAggRef ? aggIdTs : this.mapType(fieldConfig.type);
|
|
185
|
+
const effectiveFieldType = isAggRef ? (this.identifiers === 'numeric' ? 'number' : 'string') : fieldConfig.type;
|
|
176
186
|
// Aggregate references are always optional in DTOs; other fields default to required
|
|
177
187
|
const isRequired = !isAggRef && !inputConfig.partial && fieldConfig.required !== false;
|
|
178
188
|
const optional = isRequired ? '' : '?';
|
|
@@ -252,8 +262,9 @@ ${transformsStr}
|
|
|
252
262
|
const fromMappings = [];
|
|
253
263
|
// Always include id for entity outputs
|
|
254
264
|
if (outputConfig.from) {
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
266
|
+
fieldDeclarations.push(` readonly id: ${idTs};`);
|
|
267
|
+
constructorParams.push(`id: ${idTs}`);
|
|
257
268
|
fromMappings.push(` id: entity.id`);
|
|
258
269
|
}
|
|
259
270
|
// Handle fields from aggregate (pick)
|
|
@@ -265,11 +276,12 @@ ${transformsStr}
|
|
|
265
276
|
fieldsToInclude = fieldsToInclude.filter(([fieldName]) => outputConfig.pick.includes(fieldName));
|
|
266
277
|
}
|
|
267
278
|
// Add fields
|
|
279
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
268
280
|
fieldsToInclude.forEach(([fieldName, fieldConfig]) => {
|
|
269
281
|
if (fieldName === 'id')
|
|
270
282
|
return;
|
|
271
283
|
const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
|
|
272
|
-
const tsType = isAggRef ?
|
|
284
|
+
const tsType = isAggRef ? idTs : this.mapType(fieldConfig.type);
|
|
273
285
|
const isOptional = fieldConfig.required === false || isAggRef;
|
|
274
286
|
const optional = isOptional ? '?' : '';
|
|
275
287
|
fieldDeclarations.push(` readonly ${fieldName}${optional}: ${tsType};`);
|
|
@@ -447,8 +459,9 @@ ${mappingsStr}
|
|
|
447
459
|
}
|
|
448
460
|
return { valueObjects, entities };
|
|
449
461
|
}
|
|
450
|
-
generateFromConfig(config) {
|
|
462
|
+
generateFromConfig(config, identifiers = 'numeric') {
|
|
451
463
|
const result = {};
|
|
464
|
+
this.identifiers = identifiers;
|
|
452
465
|
// Collect all aggregates
|
|
453
466
|
if (config.domain.aggregates) {
|
|
454
467
|
Object.entries(config.domain.aggregates).forEach(([name, aggConfig]) => {
|
|
@@ -498,16 +511,16 @@ ${mappingsStr}
|
|
|
498
511
|
});
|
|
499
512
|
return result;
|
|
500
513
|
}
|
|
501
|
-
generateFromYamlFile(yamlFilePath) {
|
|
514
|
+
generateFromYamlFile(yamlFilePath, identifiers = 'numeric') {
|
|
502
515
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
503
516
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
504
517
|
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
505
518
|
throw new Error('Configuration does not match new module format. Expected useCases structure.');
|
|
506
519
|
}
|
|
507
|
-
return this.generateFromConfig(config);
|
|
520
|
+
return this.generateFromConfig(config, identifiers);
|
|
508
521
|
}
|
|
509
|
-
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
510
|
-
const dtosByName = this.generateFromYamlFile(yamlFilePath);
|
|
522
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts, identifiers = 'numeric') {
|
|
523
|
+
const dtosByName = this.generateFromYamlFile(yamlFilePath, identifiers);
|
|
511
524
|
const dtoDir = path.join(moduleDir, 'application', 'dto');
|
|
512
525
|
fs.mkdirSync(dtoDir, { recursive: true });
|
|
513
526
|
for (const [name, code] of Object.entries(dtosByName)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ModuleConfig } from '../types/configTypes';
|
|
1
|
+
import { ModuleConfig, IdentifierType } from '../types/configTypes';
|
|
2
2
|
export declare class ServiceGenerator {
|
|
3
3
|
private availableAggregates;
|
|
4
|
+
private identifiers;
|
|
4
5
|
private mapType;
|
|
5
6
|
private getDefaultHandlerReturnType;
|
|
6
7
|
private buildHandlerContextMap;
|
|
@@ -19,10 +20,10 @@ export declare class ServiceGenerator {
|
|
|
19
20
|
private generateListByParentMethod;
|
|
20
21
|
private generateGetResourceOwnerMethod;
|
|
21
22
|
private generateService;
|
|
22
|
-
generateFromConfig(config: ModuleConfig): Record<string, string>;
|
|
23
|
-
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
23
|
+
generateFromConfig(config: ModuleConfig, identifiers?: IdentifierType): Record<string, string>;
|
|
24
|
+
generateFromYamlFile(yamlFilePath: string, identifiers?: IdentifierType): Record<string, string>;
|
|
24
25
|
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
25
26
|
force?: boolean;
|
|
26
27
|
skipOnConflict?: boolean;
|
|
27
|
-
}): Promise<void>;
|
|
28
|
+
}, identifiers?: IdentifierType): Promise<void>;
|
|
28
29
|
}
|
|
@@ -45,6 +45,7 @@ const typeUtils_1 = require("../utils/typeUtils");
|
|
|
45
45
|
class ServiceGenerator {
|
|
46
46
|
constructor() {
|
|
47
47
|
this.availableAggregates = new Map();
|
|
48
|
+
this.identifiers = 'numeric';
|
|
48
49
|
}
|
|
49
50
|
mapType(yamlType) {
|
|
50
51
|
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates);
|
|
@@ -177,8 +178,9 @@ class ServiceGenerator {
|
|
|
177
178
|
}
|
|
178
179
|
generateListHandler(modelName, storeName, hasPagination) {
|
|
179
180
|
const returnType = `{ items: ${modelName}[]; total: number; page: number; limit: number }`;
|
|
181
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
180
182
|
if (hasPagination) {
|
|
181
|
-
return ` async list(page: number = 1, limit: number = 20, ownerId?:
|
|
183
|
+
return ` async list(page: number = 1, limit: number = 20, ownerId?: ${idTs}): Promise<${returnType}> {
|
|
182
184
|
const [items, total] = await Promise.all([
|
|
183
185
|
this.${storeName}.getPaginated(page, limit, ownerId),
|
|
184
186
|
this.${storeName}.count(ownerId)
|
|
@@ -186,13 +188,14 @@ class ServiceGenerator {
|
|
|
186
188
|
return { items, total, page, limit };
|
|
187
189
|
}`;
|
|
188
190
|
}
|
|
189
|
-
return ` async list(ownerId?:
|
|
191
|
+
return ` async list(ownerId?: ${idTs}): Promise<${returnType}> {
|
|
190
192
|
const items = await this.${storeName}.getAll(ownerId);
|
|
191
193
|
return { items, total: items.length, page: 1, limit: items.length };
|
|
192
194
|
}`;
|
|
193
195
|
}
|
|
194
196
|
generateGetHandler(modelName, storeName, entityLower) {
|
|
195
|
-
|
|
197
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
198
|
+
return ` async get(id: ${idTs}): Promise<${modelName}> {
|
|
196
199
|
const ${entityLower} = await this.${storeName}.getById(id);
|
|
197
200
|
if (!${entityLower}) {
|
|
198
201
|
throw new Error('${modelName} not found');
|
|
@@ -227,12 +230,14 @@ class ServiceGenerator {
|
|
|
227
230
|
return `input.${fieldName}`;
|
|
228
231
|
}).join(', ');
|
|
229
232
|
const constructorArgs = `input.${firstArgField}, ${fieldArgs}`;
|
|
233
|
+
const idPlaceholder = this.identifiers === 'numeric' ? '0' : "''";
|
|
230
234
|
return ` async create(input: ${inputType}): Promise<${modelName}> {
|
|
231
|
-
const ${entityLower} = new ${modelName}(
|
|
235
|
+
const ${entityLower} = new ${modelName}(${idPlaceholder}, ${constructorArgs});
|
|
232
236
|
return await this.${storeName}.insert(${entityLower});
|
|
233
237
|
}`;
|
|
234
238
|
}
|
|
235
|
-
generateUpdateHandler(modelName, storeName, aggregateConfig, inputType, dtoFields) {
|
|
239
|
+
generateUpdateHandler(modelName, storeName, aggregateConfig, inputType, dtoFields, _identifiers) {
|
|
240
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
236
241
|
const setterCalls = Object.entries(aggregateConfig.fields)
|
|
237
242
|
.filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id' && dtoFields.has(fieldName))
|
|
238
243
|
.map(([fieldName, fieldConfig]) => {
|
|
@@ -253,7 +258,7 @@ class ServiceGenerator {
|
|
|
253
258
|
}`;
|
|
254
259
|
})
|
|
255
260
|
.join('\n');
|
|
256
|
-
return ` async update(id:
|
|
261
|
+
return ` async update(id: ${idTs}, input: ${inputType}): Promise<${modelName}> {
|
|
257
262
|
const existing${modelName} = await this.${storeName}.getById(id);
|
|
258
263
|
if (!existing${modelName}) {
|
|
259
264
|
throw new Error('${modelName} not found');
|
|
@@ -265,7 +270,8 @@ ${setterCalls}
|
|
|
265
270
|
}`;
|
|
266
271
|
}
|
|
267
272
|
generateDeleteHandler(modelName, storeName) {
|
|
268
|
-
|
|
273
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
274
|
+
return ` async delete(id: ${idTs}): Promise<{ success: boolean; message: string }> {
|
|
269
275
|
const success = await this.${storeName}.softDelete(id);
|
|
270
276
|
if (!success) {
|
|
271
277
|
throw new Error('${modelName} not found or could not be deleted');
|
|
@@ -316,19 +322,21 @@ ${setterCalls}
|
|
|
316
322
|
if (!childInfo)
|
|
317
323
|
return '';
|
|
318
324
|
const storeVar = `${modelName.toLowerCase()}Store`;
|
|
325
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
319
326
|
return `
|
|
320
|
-
async listByParent(parentId:
|
|
327
|
+
async listByParent(parentId: ${idTs}): Promise<${modelName}[]> {
|
|
321
328
|
return await this.${storeVar}.getByParentId(parentId);
|
|
322
329
|
}`;
|
|
323
330
|
}
|
|
324
331
|
generateGetResourceOwnerMethod(modelName) {
|
|
325
332
|
const storeVar = `${modelName.toLowerCase()}Store`;
|
|
333
|
+
const idTs = (0, configTypes_1.idTsType)(this.identifiers);
|
|
326
334
|
return `
|
|
327
335
|
/**
|
|
328
336
|
* Get the owner ID of a resource by its ID.
|
|
329
337
|
* Used for pre-mutation authorization checks.
|
|
330
338
|
*/
|
|
331
|
-
async getResourceOwner(id:
|
|
339
|
+
async getResourceOwner(id: ${idTs}): Promise<${idTs} | null> {
|
|
332
340
|
return await this.${storeVar}.getResourceOwner(id);
|
|
333
341
|
}`;
|
|
334
342
|
}
|
|
@@ -411,8 +419,9 @@ export class ${serviceName} {
|
|
|
411
419
|
${methods.join('\n\n')}
|
|
412
420
|
}`;
|
|
413
421
|
}
|
|
414
|
-
generateFromConfig(config) {
|
|
422
|
+
generateFromConfig(config, identifiers = 'numeric') {
|
|
415
423
|
const result = {};
|
|
424
|
+
this.identifiers = identifiers;
|
|
416
425
|
// Collect all aggregates
|
|
417
426
|
if (config.domain.aggregates) {
|
|
418
427
|
Object.entries(config.domain.aggregates).forEach(([name, aggConfig]) => {
|
|
@@ -432,16 +441,16 @@ ${methods.join('\n\n')}
|
|
|
432
441
|
});
|
|
433
442
|
return result;
|
|
434
443
|
}
|
|
435
|
-
generateFromYamlFile(yamlFilePath) {
|
|
444
|
+
generateFromYamlFile(yamlFilePath, identifiers = 'numeric') {
|
|
436
445
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
437
446
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
438
447
|
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
439
448
|
throw new Error('Configuration does not match new module format. Expected useCases structure.');
|
|
440
449
|
}
|
|
441
|
-
return this.generateFromConfig(config);
|
|
450
|
+
return this.generateFromConfig(config, identifiers);
|
|
442
451
|
}
|
|
443
|
-
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
444
|
-
const servicesByModel = this.generateFromYamlFile(yamlFilePath);
|
|
452
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts, identifiers = 'numeric') {
|
|
453
|
+
const servicesByModel = this.generateFromYamlFile(yamlFilePath, identifiers);
|
|
445
454
|
const servicesDir = path.join(moduleDir, 'application', 'services');
|
|
446
455
|
fs.mkdirSync(servicesDir, { recursive: true });
|
|
447
456
|
for (const [modelName, code] of Object.entries(servicesByModel)) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ModuleConfig } from '../types/configTypes';
|
|
1
|
+
import { ModuleConfig, IdentifierType } from '../types/configTypes';
|
|
2
2
|
export declare class StoreGenerator {
|
|
3
3
|
private availableValueObjects;
|
|
4
4
|
private availableAggregates;
|
|
5
|
+
private identifiers;
|
|
5
6
|
private isAggregateField;
|
|
6
7
|
private isValueObjectType;
|
|
7
8
|
private isArrayVoType;
|
|
@@ -51,11 +52,17 @@ export declare class StoreGenerator {
|
|
|
51
52
|
private generateListMethods;
|
|
52
53
|
private generateGetByParentIdMethod;
|
|
53
54
|
private generateGetResourceOwnerMethod;
|
|
55
|
+
private generateIdHelpers;
|
|
56
|
+
private generateInsertIdVariables;
|
|
57
|
+
private generateWhereIdExpr;
|
|
58
|
+
private generateIdParamExpr;
|
|
59
|
+
private generateRowIdExpr;
|
|
60
|
+
private generateCryptoImport;
|
|
54
61
|
private generateStore;
|
|
55
|
-
generateFromConfig(config: ModuleConfig): Record<string, string>;
|
|
56
|
-
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
62
|
+
generateFromConfig(config: ModuleConfig, identifiers?: IdentifierType): Record<string, string>;
|
|
63
|
+
generateFromYamlFile(yamlFilePath: string, identifiers?: IdentifierType): Record<string, string>;
|
|
57
64
|
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
58
65
|
force?: boolean;
|
|
59
66
|
skipOnConflict?: boolean;
|
|
60
|
-
}): Promise<void>;
|
|
67
|
+
}, identifiers?: IdentifierType): Promise<void>;
|
|
61
68
|
}
|