@currentjs/gen 0.5.4 → 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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.6] - 2026-04-08
4
+
5
+ - database identifier types (numeric, uuid, nanoid)
6
+
7
+ ## [0.5.5] - 2026-04-07
8
+
9
+ - data access layer generation fixes and improvements
10
+
3
11
  ## [0.5.4] - 2026-04-07
4
12
 
5
13
  - Array and union value objects
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
@@ -42,12 +42,16 @@ const cliUtils_1 = require("../utils/cliUtils");
42
42
  const commandUtils_1 = require("../utils/commandUtils");
43
43
  const configTypes_1 = require("../types/configTypes");
44
44
  const migrationUtils_1 = require("../utils/migrationUtils");
45
- function collectAggregatesFromModules(appYamlPath) {
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 = {};
51
+ const allValueObjects = new Set();
50
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);
51
55
  for (const entry of moduleEntries) {
52
56
  const moduleYamlPath = path.isAbsolute(entry.path)
53
57
  ? entry.path
@@ -67,13 +71,18 @@ function collectAggregatesFromModules(appYamlPath) {
67
71
  const aggregates = moduleConfig.domain.aggregates;
68
72
  const count = Object.keys(aggregates).length;
69
73
  Object.assign(allAggregates, aggregates);
74
+ if (moduleConfig.domain.valueObjects) {
75
+ for (const voName of Object.keys(moduleConfig.domain.valueObjects)) {
76
+ allValueObjects.add(voName);
77
+ }
78
+ }
70
79
  sources.push(`${entry.name} (${count} aggregate(s))`);
71
80
  }
72
81
  if (sources.length > 0) {
73
82
  // eslint-disable-next-line no-console
74
83
  console.log(colors_1.colors.gray(` Sources: ${sources.join(', ')}`));
75
84
  }
76
- return allAggregates;
85
+ return { aggregates: allAggregates, valueObjects: allValueObjects, identifiers };
77
86
  }
78
87
  function handleMigrateCommit(yamlPath) {
79
88
  try {
@@ -94,7 +103,7 @@ function handleMigrateCommit(yamlPath) {
94
103
  }
95
104
  // eslint-disable-next-line no-console
96
105
  console.log(colors_1.colors.cyan('\n📋 Collecting aggregates from all modules...'));
97
- const currentAggregates = collectAggregatesFromModules(resolvedYamlPath);
106
+ const { aggregates: currentAggregates, valueObjects: currentValueObjects, identifiers } = collectSchemaFromModules(resolvedYamlPath);
98
107
  if (Object.keys(currentAggregates).length === 0) {
99
108
  // eslint-disable-next-line no-console
100
109
  console.log(colors_1.colors.yellow('⚠️ No aggregates found in module configuration.'));
@@ -113,7 +122,7 @@ function handleMigrateCommit(yamlPath) {
113
122
  }
114
123
  // eslint-disable-next-line no-console
115
124
  console.log(colors_1.colors.cyan('\n🔍 Comparing schemas...'));
116
- const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates);
125
+ const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates, currentValueObjects, identifiers);
117
126
  if (sqlStatements.length === 0 || sqlStatements.every(s => s.trim() === '' || s.startsWith('--'))) {
118
127
  // eslint-disable-next-line no-console
119
128
  console.log(colors_1.colors.yellow('⚠️ No changes detected. Schema is up to date.'));
@@ -132,7 +141,7 @@ function handleMigrateCommit(yamlPath) {
132
141
  const newState = {
133
142
  aggregates: currentAggregates,
134
143
  version: timestamp,
135
- timestamp: new Date().toISOString()
144
+ timestamp: new Date().toISOString(),
136
145
  };
137
146
  (0, migrationUtils_1.saveSchemaState)(stateFilePath, newState);
138
147
  // eslint-disable-next-line no-console
@@ -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
- parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ownerId: context.request.user?.id });`;
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
- parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ownerId: context.request.user?.id });`;
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 constructorParams = ['public id: number'];
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}: number`);
161
+ constructorParams.push(`public ${childInfo.parentIdField}: ${idTs}`);
160
162
  }
161
163
  else {
162
- constructorParams.push('public ownerId: number');
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
- fieldDeclarations.push(` readonly ${fieldName}: number;`);
120
- constructorParams.push(`${fieldName}: number`);
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}: typeof b.${fieldName} === 'string' ? parseInt(b.${fieldName}, 10) : b.${fieldName} as number`);
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
- fieldDeclarations.push(` readonly ${ownerOrParentField}: number;`);
162
- constructorParams.push(`${ownerOrParentField}: number`);
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}: typeof b.${ownerOrParentField} === 'string' ? parseInt(b.${ownerOrParentField}, 10) : b.${ownerOrParentField} as number`);
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 ? 'number' : this.mapType(fieldConfig.type);
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
- fieldDeclarations.push(` readonly id: number;`);
256
- constructorParams.push(`id: number`);
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 ? 'number' : this.mapType(fieldConfig.type);
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?: number): Promise<${returnType}> {
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?: number): Promise<${returnType}> {
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
- return ` async get(id: number): Promise<${modelName}> {
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}(0, ${constructorArgs});
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: number, input: ${inputType}): Promise<${modelName}> {
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
- return ` async delete(id: number): Promise<{ success: boolean; message: string }> {
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: number): Promise<${modelName}[]> {
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: number): Promise<number | null> {
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
  }