@currentjs/gen 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +374 -996
  3. package/dist/cli.js +28 -10
  4. package/dist/commands/createModel.d.ts +1 -0
  5. package/dist/commands/createModel.js +764 -0
  6. package/dist/commands/createModule.js +13 -0
  7. package/dist/commands/generateAll.d.ts +1 -0
  8. package/dist/commands/generateAll.js +1 -1
  9. package/dist/commands/init.d.ts +1 -0
  10. package/dist/commands/{createApp.js → init.js} +2 -2
  11. package/dist/commands/migrateCommit.js +33 -68
  12. package/dist/generators/controllerGenerator.d.ts +7 -0
  13. package/dist/generators/controllerGenerator.js +56 -17
  14. package/dist/generators/domainLayerGenerator.js +51 -8
  15. package/dist/generators/dtoGenerator.js +13 -8
  16. package/dist/generators/serviceGenerator.d.ts +6 -0
  17. package/dist/generators/serviceGenerator.js +219 -23
  18. package/dist/generators/storeGenerator.d.ts +4 -0
  19. package/dist/generators/storeGenerator.js +116 -9
  20. package/dist/generators/templateGenerator.d.ts +1 -0
  21. package/dist/generators/templateGenerator.js +8 -2
  22. package/dist/generators/templates/appTemplates.js +1 -1
  23. package/dist/generators/templates/data/cursorRulesTemplate +11 -755
  24. package/dist/generators/templates/data/frontendScriptTemplate +11 -4
  25. package/dist/generators/templates/data/mainViewTemplate +1 -0
  26. package/dist/generators/templates/storeTemplates.d.ts +1 -1
  27. package/dist/generators/templates/storeTemplates.js +3 -26
  28. package/dist/generators/useCaseGenerator.js +6 -3
  29. package/dist/types/configTypes.d.ts +6 -0
  30. package/dist/utils/migrationUtils.d.ts +9 -19
  31. package/dist/utils/migrationUtils.js +80 -110
  32. package/dist/utils/promptUtils.d.ts +37 -0
  33. package/dist/utils/promptUtils.js +149 -0
  34. package/dist/utils/typeUtils.d.ts +4 -0
  35. package/dist/utils/typeUtils.js +7 -0
  36. package/package.json +1 -1
  37. package/dist/commands/createApp.d.ts +0 -1
  38. package/dist/commands/migratePush.d.ts +0 -1
  39. package/dist/commands/migratePush.js +0 -135
  40. package/dist/commands/migrateUpdate.d.ts +0 -1
  41. package/dist/commands/migrateUpdate.js +0 -147
  42. package/dist/commands/newGenerateAll.d.ts +0 -4
  43. package/dist/commands/newGenerateAll.js +0 -336
  44. package/dist/generators/domainModelGenerator.d.ts +0 -41
  45. package/dist/generators/domainModelGenerator.js +0 -242
  46. package/dist/generators/newControllerGenerator.d.ts +0 -55
  47. package/dist/generators/newControllerGenerator.js +0 -644
  48. package/dist/generators/newServiceGenerator.d.ts +0 -19
  49. package/dist/generators/newServiceGenerator.js +0 -266
  50. package/dist/generators/newStoreGenerator.d.ts +0 -39
  51. package/dist/generators/newStoreGenerator.js +0 -408
  52. package/dist/generators/newTemplateGenerator.d.ts +0 -29
  53. package/dist/generators/newTemplateGenerator.js +0 -510
  54. package/dist/generators/storeGeneratorV2.d.ts +0 -31
  55. package/dist/generators/storeGeneratorV2.js +0 -190
  56. package/dist/generators/templates/controllerTemplates.d.ts +0 -43
  57. package/dist/generators/templates/controllerTemplates.js +0 -82
  58. package/dist/generators/templates/newStoreTemplates.d.ts +0 -5
  59. package/dist/generators/templates/newStoreTemplates.js +0 -141
  60. package/dist/generators/templates/serviceTemplates.d.ts +0 -16
  61. package/dist/generators/templates/serviceTemplates.js +0 -59
  62. package/dist/generators/templates/validationTemplates.d.ts +0 -25
  63. package/dist/generators/templates/validationTemplates.js +0 -66
  64. package/dist/generators/templates/viewTemplates.d.ts +0 -25
  65. package/dist/generators/templates/viewTemplates.js +0 -491
  66. package/dist/generators/validationGenerator.d.ts +0 -29
  67. package/dist/generators/validationGenerator.js +0 -250
  68. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  69. package/dist/utils/new_parts_of_migrationUtils.js +0 -164
  70. package/howto.md +0 -667
@@ -374,7 +374,10 @@ window.AppConfig = {
374
374
  */
375
375
  function navigateToPage(url, targetElement = null) {
376
376
  const target = targetElement || document.querySelector('#main');
377
- if (!target) return;
377
+ if (!target) {
378
+ window.location.href = url;
379
+ return;
380
+ }
378
381
 
379
382
  showLoading('#main');
380
383
 
@@ -388,18 +391,22 @@ window.AppConfig = {
388
391
  if (!response.ok) {
389
392
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
390
393
  }
394
+ const targetLayout = response.headers.get('X-Layout') || '';
395
+ const currentLayout = (document.querySelector('meta[name="app-layout"]') || {}).content || '';
396
+ if (targetLayout !== currentLayout) {
397
+ window.location.href = url;
398
+ return null;
399
+ }
391
400
  return response.text();
392
401
  })
393
402
  .then(html => {
403
+ if (html === null) return;
394
404
  target.innerHTML = html;
395
- // Update browser history
396
405
  window.history.pushState({}, '', url);
397
- // No need to re-initialize - delegation handles new content automatically
398
406
  })
399
407
  .catch(error => {
400
408
  console.error('Navigation failed:', error);
401
409
  showToast('Failed to load page', 'error');
402
- // Fallback to normal navigation
403
410
  window.location.href = url;
404
411
  })
405
412
  .finally(() => {
@@ -5,6 +5,7 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Your App</title>
8
+ <meta name="app-layout" content="main_view">
8
9
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
9
10
  <script src="/app.js"></script>
10
11
  </head>
@@ -2,4 +2,4 @@ export declare const storeTemplates: {
2
2
  rowInterface: string;
3
3
  storeClass: string;
4
4
  };
5
- export declare const storeFileTemplate = "import { Injectable } from '../../../../system';\nimport { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';\nimport type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}\n\n{{ROW_INTERFACE}}\n\n{{STORE_CLASS}}\n";
5
+ export declare const storeFileTemplate = "import { Injectable } from '../../../../system';\nimport { {{ENTITY_IMPORT_ITEMS}} } from '../../domain/entities/{{ENTITY_NAME}}';\nimport type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}{{AGGREGATE_REF_IMPORTS}}\n\n{{ROW_INTERFACE}}\n\n{{STORE_CLASS}}\n";
@@ -29,30 +29,7 @@ export class {{ENTITY_NAME}}Store {
29
29
  );
30
30
  }
31
31
 
32
- async getAll(page: number = 1, limit: number = 20): Promise<{{ENTITY_NAME}}[]> {
33
- const offset = (page - 1) * limit;
34
- const result = await this.db.query(
35
- \`SELECT {{FIELD_NAMES}} FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL LIMIT :limit OFFSET :offset\`,
36
- { limit: String(limit), offset: String(offset) }
37
- );
38
-
39
- if (result.success && result.data) {
40
- return result.data.map((row: {{ENTITY_NAME}}Row) => this.rowToModel(row));
41
- }
42
- return [];
43
- }
44
-
45
- async count(): Promise<number> {
46
- const result = await this.db.query(
47
- \`SELECT COUNT(*) as count FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL\`,
48
- {}
49
- );
50
-
51
- if (result.success && result.data && result.data.length > 0) {
52
- return parseInt(result.data[0].count, 10);
53
- }
54
- return 0;
55
- }
32
+ {{LIST_METHODS}}
56
33
 
57
34
  async getById(id: number): Promise<{{ENTITY_NAME}} | null> {
58
35
  const result = await this.db.query(
@@ -134,8 +111,8 @@ export class {{ENTITY_NAME}}Store {
134
111
  {{GET_RESOURCE_OWNER_METHOD}}}`
135
112
  };
136
113
  exports.storeFileTemplate = `import { Injectable } from '../../../../system';
137
- import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
138
- import type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}
114
+ import { {{ENTITY_IMPORT_ITEMS}} } from '../../domain/entities/{{ENTITY_NAME}}';
115
+ import type { ISqlProvider } from '@currentjs/provider-mysql';{{VALUE_OBJECT_IMPORTS}}{{AGGREGATE_REF_IMPORTS}}
139
116
 
140
117
  {{ROW_INTERFACE}}
141
118
 
@@ -71,8 +71,8 @@ class UseCaseGenerator {
71
71
  let params = '';
72
72
  if (defaultAction === 'list') {
73
73
  params = ((_a = useCaseConfig.input) === null || _a === void 0 ? void 0 : _a.pagination)
74
- ? 'input.page || 1, input.limit || 20'
75
- : '';
74
+ ? 'input.page || 1, input.limit || 20, ownerId'
75
+ : 'ownerId';
76
76
  }
77
77
  else if (defaultAction === 'get') {
78
78
  params = 'input.id';
@@ -99,7 +99,10 @@ class UseCaseGenerator {
99
99
  }
100
100
  }).join('\n');
101
101
  const returnStatement = '\n return result;';
102
- return ` async ${methodName}(input: ${inputType}): Promise<${returnType}> {
102
+ const methodParams = actionName === 'list'
103
+ ? `input: ${inputType}, ownerId?: number`
104
+ : `input: ${inputType}`;
105
+ return ` async ${methodName}(${methodParams}): Promise<${returnType}> {
103
106
  ${handlerCalls}${returnStatement}
104
107
  }`;
105
108
  }
@@ -22,6 +22,11 @@ export interface AggregateFieldConfig {
22
22
  unique?: boolean;
23
23
  auto?: boolean;
24
24
  values?: string[];
25
+ constraints?: {
26
+ min?: number;
27
+ max?: number;
28
+ pattern?: string;
29
+ };
25
30
  }
26
31
  export interface AggregateConfig {
27
32
  root?: boolean;
@@ -121,6 +126,7 @@ export interface WebPageConfig {
121
126
  method?: 'GET' | 'POST';
122
127
  useCase?: string;
123
128
  view?: string;
129
+ layout?: string;
124
130
  auth?: AuthConfig;
125
131
  onSuccess?: {
126
132
  redirect?: string;
@@ -1,16 +1,6 @@
1
- export interface FieldConfig {
2
- name: string;
3
- type: string;
4
- required?: boolean;
5
- unique?: boolean;
6
- auto?: boolean;
7
- }
8
- export interface ModelConfig {
9
- name: string;
10
- fields: FieldConfig[];
11
- }
1
+ import { AggregateConfig, AggregateFieldConfig } from '../types/configTypes';
12
2
  export interface SchemaState {
13
- models: ModelConfig[];
3
+ aggregates: Record<string, AggregateConfig>;
14
4
  version: string;
15
5
  timestamp: string;
16
6
  }
@@ -31,19 +21,19 @@ export interface ForeignKeyInfo {
31
21
  REFERENCED_TABLE_NAME: string;
32
22
  REFERENCED_COLUMN_NAME: string;
33
23
  }
34
- export declare function mapYamlTypeToSql(yamlType: string, availableModels: Set<string>): string;
35
- export declare function getTableName(modelName: string): string;
24
+ export declare function mapYamlTypeToSql(yamlType: string, availableAggregates: Set<string>): string;
25
+ export declare function getTableName(aggregateName: string): string;
36
26
  export declare function getForeignKeyFieldName(fieldName: string): string;
37
- export declare function isRelationshipField(fieldType: string, availableModels: Set<string>): boolean;
38
- export declare function generateCreateTableSQL(model: ModelConfig, availableModels: Set<string>): string;
27
+ export declare function isRelationshipField(fieldType: string, availableAggregates: Set<string>): boolean;
28
+ export declare function generateCreateTableSQL(name: string, aggregate: AggregateConfig, availableAggregates: Set<string>): string;
39
29
  export declare function generateDropTableSQL(tableName: string): string;
40
- export declare function generateAddColumnSQL(tableName: string, field: FieldConfig, availableModels: Set<string>): string;
30
+ export declare function generateAddColumnSQL(tableName: string, fieldName: string, field: AggregateFieldConfig, availableAggregates: Set<string>): string;
41
31
  export declare function generateDropColumnSQL(tableName: string, columnName: string): string;
42
- export declare function generateModifyColumnSQL(tableName: string, field: FieldConfig, availableModels: Set<string>): string;
32
+ export declare function generateModifyColumnSQL(tableName: string, fieldName: string, field: AggregateFieldConfig, availableAggregates: Set<string>): string;
43
33
  export declare function loadSchemaState(stateFilePath: string): SchemaState | null;
44
34
  export declare function saveSchemaState(stateFilePath: string, state: SchemaState): void;
45
35
  export declare function loadMigrationLog(logFilePath: string): MigrationLog;
46
36
  export declare function saveMigrationLog(logFilePath: string, log: MigrationLog): void;
47
- export declare function compareSchemas(oldState: SchemaState | null, newModels: ModelConfig[]): string[];
37
+ export declare function compareSchemas(oldState: SchemaState | null, newAggregates: Record<string, AggregateConfig>): string[];
48
38
  export declare function generateTimestamp(): string;
49
39
  export declare function getMigrationFileName(timestamp: string): string;
@@ -61,39 +61,33 @@ const TYPE_MAPPING = {
61
61
  array: 'JSON',
62
62
  object: 'JSON'
63
63
  };
64
- function mapYamlTypeToSql(yamlType, availableModels) {
65
- // Check if this is a relationship (foreign key)
66
- if (availableModels.has(yamlType)) {
67
- return 'INT'; // Foreign keys are INT
64
+ function mapYamlTypeToSql(yamlType, availableAggregates) {
65
+ if (availableAggregates.has(yamlType)) {
66
+ return 'INT';
68
67
  }
69
68
  return TYPE_MAPPING[yamlType] || 'VARCHAR(255)';
70
69
  }
71
- function getTableName(modelName) {
72
- return modelName.toLowerCase() + 's';
70
+ function getTableName(aggregateName) {
71
+ return aggregateName.toLowerCase() + 's';
73
72
  }
74
73
  function getForeignKeyFieldName(fieldName) {
75
74
  return fieldName + 'Id';
76
75
  }
77
- function isRelationshipField(fieldType, availableModels) {
78
- return availableModels.has(fieldType);
76
+ function isRelationshipField(fieldType, availableAggregates) {
77
+ return availableAggregates.has(fieldType);
79
78
  }
80
- function generateCreateTableSQL(model, availableModels) {
81
- const tableName = getTableName(model.name);
79
+ function generateCreateTableSQL(name, aggregate, availableAggregates) {
80
+ const tableName = getTableName(name);
82
81
  const columns = [];
83
82
  const indexes = [];
84
83
  const foreignKeys = [];
85
- // Add id column
86
84
  columns.push(' id INT AUTO_INCREMENT PRIMARY KEY');
87
- // Add model fields
88
- model.fields.forEach(field => {
89
- if (isRelationshipField(field.type, availableModels)) {
90
- // Foreign key field
91
- const foreignKeyName = getForeignKeyFieldName(field.name);
85
+ for (const [fieldName, field] of Object.entries(aggregate.fields)) {
86
+ if (isRelationshipField(field.type, availableAggregates)) {
87
+ const foreignKeyName = getForeignKeyFieldName(fieldName);
92
88
  const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
93
89
  columns.push(` ${foreignKeyName} INT ${nullable}`);
94
- // Add index for foreign key
95
90
  indexes.push(` INDEX idx_${tableName}_${foreignKeyName} (${foreignKeyName})`);
96
- // Add foreign key constraint
97
91
  const refTableName = getTableName(field.type);
98
92
  foreignKeys.push(` CONSTRAINT fk_${tableName}_${foreignKeyName} \n` +
99
93
  ` FOREIGN KEY (${foreignKeyName}) \n` +
@@ -102,58 +96,50 @@ function generateCreateTableSQL(model, availableModels) {
102
96
  ` ON UPDATE CASCADE`);
103
97
  }
104
98
  else {
105
- // Regular field
106
- const sqlType = mapYamlTypeToSql(field.type, availableModels);
99
+ const sqlType = mapYamlTypeToSql(field.type, availableAggregates);
107
100
  const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
108
- columns.push(` ${field.name} ${sqlType} ${nullable}`);
109
- // Add index for filterable fields
101
+ columns.push(` ${fieldName} ${sqlType} ${nullable}`);
110
102
  if (['string', 'number'].includes(field.type)) {
111
- indexes.push(` INDEX idx_${tableName}_${field.name} (${field.name})`);
103
+ indexes.push(` INDEX idx_${tableName}_${fieldName} (${fieldName})`);
112
104
  }
113
105
  }
114
- });
115
- // Add standard timestamp columns
106
+ }
116
107
  columns.push(' created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
117
108
  columns.push(' updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
118
109
  columns.push(' deleted_at DATETIME NULL DEFAULT NULL');
119
- // Add standard indexes
120
110
  indexes.push(` INDEX idx_${tableName}_deleted_at (deleted_at)`);
121
111
  indexes.push(` INDEX idx_${tableName}_created_at (created_at)`);
122
- // Combine all parts
123
112
  const allParts = [...columns, ...indexes, ...foreignKeys];
124
- const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (\n${allParts.join(',\n')}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`;
125
- return sql;
113
+ return `CREATE TABLE IF NOT EXISTS ${tableName} (\n${allParts.join(',\n')}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`;
126
114
  }
127
115
  function generateDropTableSQL(tableName) {
128
116
  return `DROP TABLE IF EXISTS ${tableName};`;
129
117
  }
130
- function generateAddColumnSQL(tableName, field, availableModels) {
131
- if (isRelationshipField(field.type, availableModels)) {
132
- const foreignKeyName = getForeignKeyFieldName(field.name);
118
+ function generateAddColumnSQL(tableName, fieldName, field, availableAggregates) {
119
+ if (isRelationshipField(field.type, availableAggregates)) {
120
+ const foreignKeyName = getForeignKeyFieldName(fieldName);
133
121
  const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
134
- const sqlType = 'INT';
135
- return `ALTER TABLE ${tableName} ADD COLUMN ${foreignKeyName} ${sqlType} ${nullable};`;
122
+ return `ALTER TABLE ${tableName} ADD COLUMN ${foreignKeyName} INT ${nullable};`;
136
123
  }
137
124
  else {
138
- const sqlType = mapYamlTypeToSql(field.type, availableModels);
125
+ const sqlType = mapYamlTypeToSql(field.type, availableAggregates);
139
126
  const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
140
- return `ALTER TABLE ${tableName} ADD COLUMN ${field.name} ${sqlType} ${nullable};`;
127
+ return `ALTER TABLE ${tableName} ADD COLUMN ${fieldName} ${sqlType} ${nullable};`;
141
128
  }
142
129
  }
143
130
  function generateDropColumnSQL(tableName, columnName) {
144
131
  return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
145
132
  }
146
- function generateModifyColumnSQL(tableName, field, availableModels) {
147
- if (isRelationshipField(field.type, availableModels)) {
148
- const foreignKeyName = getForeignKeyFieldName(field.name);
133
+ function generateModifyColumnSQL(tableName, fieldName, field, availableAggregates) {
134
+ if (isRelationshipField(field.type, availableAggregates)) {
135
+ const foreignKeyName = getForeignKeyFieldName(fieldName);
149
136
  const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
150
- const sqlType = 'INT';
151
- return `ALTER TABLE ${tableName} MODIFY COLUMN ${foreignKeyName} ${sqlType} ${nullable};`;
137
+ return `ALTER TABLE ${tableName} MODIFY COLUMN ${foreignKeyName} INT ${nullable};`;
152
138
  }
153
139
  else {
154
- const sqlType = mapYamlTypeToSql(field.type, availableModels);
140
+ const sqlType = mapYamlTypeToSql(field.type, availableAggregates);
155
141
  const nullable = field.required === false ? 'NULL DEFAULT NULL' : 'NOT NULL';
156
- return `ALTER TABLE ${tableName} MODIFY COLUMN ${field.name} ${sqlType} ${nullable};`;
142
+ return `ALTER TABLE ${tableName} MODIFY COLUMN ${fieldName} ${sqlType} ${nullable};`;
157
143
  }
158
144
  }
159
145
  function loadSchemaState(stateFilePath) {
@@ -178,109 +164,93 @@ function saveMigrationLog(logFilePath, log) {
178
164
  fs.mkdirSync(path.dirname(logFilePath), { recursive: true });
179
165
  fs.writeFileSync(logFilePath, JSON.stringify(log, null, 2));
180
166
  }
181
- /**
182
- * Sort models by dependencies so tables are created in the right order
183
- * (tables with no foreign keys first, then tables that depend on them)
184
- */
185
- function sortModelsByDependencies(models, availableModels) {
167
+ function sortAggregatesByDependencies(aggregates, availableAggregates) {
186
168
  const sorted = [];
187
169
  const processed = new Set();
188
- const addModel = (model) => {
189
- if (processed.has(model.name))
170
+ const addAggregate = (name, aggregate) => {
171
+ if (processed.has(name))
190
172
  return;
191
- // Find foreign key dependencies
192
- const dependencies = [];
193
- model.fields.forEach(field => {
194
- if (isRelationshipField(field.type, availableModels)) {
195
- dependencies.push(field.type);
196
- }
197
- });
198
- // Add dependencies first
199
- dependencies.forEach(depName => {
200
- const depModel = models.find(m => m.name === depName);
201
- if (depModel && !processed.has(depName)) {
202
- addModel(depModel);
173
+ for (const [, field] of Object.entries(aggregate.fields)) {
174
+ if (isRelationshipField(field.type, availableAggregates)) {
175
+ const dep = aggregates[field.type];
176
+ if (dep && !processed.has(field.type)) {
177
+ addAggregate(field.type, dep);
178
+ }
203
179
  }
204
- });
205
- // Then add this model
206
- sorted.push(model);
207
- processed.add(model.name);
180
+ }
181
+ sorted.push([name, aggregate]);
182
+ processed.add(name);
208
183
  };
209
- models.forEach(model => addModel(model));
184
+ for (const [name, aggregate] of Object.entries(aggregates)) {
185
+ addAggregate(name, aggregate);
186
+ }
210
187
  return sorted;
211
188
  }
212
- function compareSchemas(oldState, newModels) {
189
+ function compareSchemas(oldState, newAggregates) {
213
190
  const sqlStatements = [];
214
- const availableModels = new Set(newModels.map(m => m.name));
215
- if (!oldState || oldState.models.length === 0) {
216
- // No previous state - generate all CREATE TABLE statements in dependency order
217
- const sortedModels = sortModelsByDependencies(newModels, availableModels);
218
- sortedModels.forEach(model => {
219
- sqlStatements.push(`-- Create ${model.name.toLowerCase()}s table`);
220
- sqlStatements.push(generateCreateTableSQL(model, availableModels));
191
+ const availableAggregates = new Set(Object.keys(newAggregates));
192
+ if (!oldState || !oldState.aggregates || Object.keys(oldState.aggregates).length === 0) {
193
+ const sorted = sortAggregatesByDependencies(newAggregates, availableAggregates);
194
+ for (const [name, aggregate] of sorted) {
195
+ sqlStatements.push(`-- Create ${name.toLowerCase()}s table`);
196
+ sqlStatements.push(generateCreateTableSQL(name, aggregate, availableAggregates));
221
197
  sqlStatements.push('');
222
- });
198
+ }
223
199
  return sqlStatements;
224
200
  }
225
- // Create maps for easy lookup
226
- const oldModelsMap = new Map(oldState.models.map(m => [m.name, m]));
227
- const newModelsMap = new Map(newModels.map(m => [m.name, m]));
201
+ const oldAggregates = oldState.aggregates;
228
202
  // Find dropped tables
229
- oldState.models.forEach(oldModel => {
230
- if (!newModelsMap.has(oldModel.name)) {
231
- const tableName = getTableName(oldModel.name);
203
+ for (const oldName of Object.keys(oldAggregates)) {
204
+ if (!newAggregates[oldName]) {
205
+ const tableName = getTableName(oldName);
232
206
  sqlStatements.push(`-- Drop ${tableName} table`);
233
207
  sqlStatements.push(generateDropTableSQL(tableName));
234
208
  sqlStatements.push('');
235
209
  }
236
- });
237
- // Find new tables and modified tables
238
- newModels.forEach(newModel => {
239
- const oldModel = oldModelsMap.get(newModel.name);
240
- const tableName = getTableName(newModel.name);
241
- if (!oldModel) {
242
- // New table
210
+ }
211
+ // Find new and modified tables
212
+ for (const [name, newAggregate] of Object.entries(newAggregates)) {
213
+ const oldAggregate = oldAggregates[name];
214
+ const tableName = getTableName(name);
215
+ if (!oldAggregate) {
243
216
  sqlStatements.push(`-- Create ${tableName} table`);
244
- sqlStatements.push(generateCreateTableSQL(newModel, availableModels));
217
+ sqlStatements.push(generateCreateTableSQL(name, newAggregate, availableAggregates));
245
218
  sqlStatements.push('');
246
219
  }
247
220
  else {
248
- // Table exists - check for column changes
249
- const oldFieldsMap = new Map(oldModel.fields.map(f => [f.name, f]));
250
- const newFieldsMap = new Map(newModel.fields.map(f => [f.name, f]));
221
+ const oldFields = oldAggregate.fields;
222
+ const newFields = newAggregate.fields;
251
223
  // Find dropped columns
252
- oldModel.fields.forEach(oldField => {
253
- if (!newFieldsMap.has(oldField.name)) {
254
- const columnName = isRelationshipField(oldField.type, availableModels)
255
- ? getForeignKeyFieldName(oldField.name)
256
- : oldField.name;
224
+ for (const oldFieldName of Object.keys(oldFields)) {
225
+ if (!newFields[oldFieldName]) {
226
+ const columnName = isRelationshipField(oldFields[oldFieldName].type, availableAggregates)
227
+ ? getForeignKeyFieldName(oldFieldName)
228
+ : oldFieldName;
257
229
  sqlStatements.push(`-- Drop column ${columnName} from ${tableName}`);
258
230
  sqlStatements.push(generateDropColumnSQL(tableName, columnName));
259
231
  sqlStatements.push('');
260
232
  }
261
- });
262
- // Find new columns and modified columns
263
- newModel.fields.forEach(newField => {
264
- const oldField = oldFieldsMap.get(newField.name);
233
+ }
234
+ // Find new and modified columns
235
+ for (const [fieldName, newField] of Object.entries(newFields)) {
236
+ const oldField = oldFields[fieldName];
265
237
  if (!oldField) {
266
- // New column
267
- sqlStatements.push(`-- Add column ${newField.name} to ${tableName}`);
268
- sqlStatements.push(generateAddColumnSQL(tableName, newField, availableModels));
238
+ sqlStatements.push(`-- Add column ${fieldName} to ${tableName}`);
239
+ sqlStatements.push(generateAddColumnSQL(tableName, fieldName, newField, availableAggregates));
269
240
  sqlStatements.push('');
270
241
  }
271
242
  else {
272
- // Check if column definition changed
273
243
  const typeChanged = oldField.type !== newField.type;
274
244
  const requiredChanged = oldField.required !== newField.required;
275
245
  if (typeChanged || requiredChanged) {
276
- sqlStatements.push(`-- Modify column ${newField.name} in ${tableName}`);
277
- sqlStatements.push(generateModifyColumnSQL(tableName, newField, availableModels));
246
+ sqlStatements.push(`-- Modify column ${fieldName} in ${tableName}`);
247
+ sqlStatements.push(generateModifyColumnSQL(tableName, fieldName, newField, availableAggregates));
278
248
  sqlStatements.push('');
279
249
  }
280
250
  }
281
- });
251
+ }
282
252
  }
283
- });
253
+ }
284
254
  return sqlStatements;
285
255
  }
286
256
  function generateTimestamp() {
@@ -0,0 +1,37 @@
1
+ import * as readline from 'readline';
2
+ export declare function createRl(): readline.Interface;
3
+ /**
4
+ * Ask for free-form text.
5
+ * If allowEmpty is false (default), re-prompts until non-blank is entered.
6
+ */
7
+ export declare function promptText(rl: readline.Interface, question: string, opts?: {
8
+ allowEmpty?: boolean;
9
+ defaultValue?: string;
10
+ }): Promise<string>;
11
+ /**
12
+ * Ask for a number. Re-prompts on invalid input.
13
+ */
14
+ export declare function promptNumber(rl: readline.Interface, question: string, defaultValue?: number): Promise<number | undefined>;
15
+ /**
16
+ * Yes/No prompt. Returns true for yes.
17
+ */
18
+ export declare function promptYesNo(rl: readline.Interface, question: string, defaultYes?: boolean): Promise<boolean>;
19
+ /**
20
+ * Single-select from a list of choices. Returns the chosen item.
21
+ */
22
+ export declare function promptSelect<T extends {
23
+ label: string;
24
+ value: string;
25
+ }>(rl: readline.Interface, question: string, choices: T[]): Promise<T>;
26
+ /**
27
+ * Multi-select from a list of choices.
28
+ * User types comma-separated numbers, "all", or "none".
29
+ * Returns array of selected items (may be empty).
30
+ */
31
+ export declare function promptMultiSelect<T extends {
32
+ label: string;
33
+ value: string;
34
+ }>(rl: readline.Interface, question: string, choices: T[], opts?: {
35
+ allowNone?: boolean;
36
+ defaultAll?: boolean;
37
+ }): Promise<T[]>;