@claudetools/tools 0.9.0 → 0.9.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.
- package/dist/cli.js +9 -1
- package/dist/codedna/__tests__/examples/mongoose-example.d.ts +6 -0
- package/dist/codedna/__tests__/examples/mongoose-example.js +163 -0
- package/dist/codedna/__tests__/fixtures/typeorm-production-test.d.ts +1 -0
- package/dist/codedna/__tests__/fixtures/typeorm-production-test.js +231 -0
- package/dist/codedna/__tests__/fixtures/typeorm-test.d.ts +1 -0
- package/dist/codedna/__tests__/fixtures/typeorm-test.js +124 -0
- package/dist/codedna/__tests__/laravel-output-review.d.ts +1 -0
- package/dist/codedna/__tests__/laravel-output-review.js +249 -0
- package/dist/codedna/__tests__/mongoose-output-test.d.ts +1 -0
- package/dist/codedna/__tests__/mongoose-output-test.js +178 -0
- package/dist/codedna/examples/radix-example.d.ts +2 -0
- package/dist/codedna/examples/radix-example.js +259 -0
- package/dist/codedna/index.d.ts +5 -3
- package/dist/codedna/index.js +6 -3
- package/dist/codedna/kappa-ast.d.ts +143 -5
- package/dist/codedna/kappa-drizzle-generator.js +8 -5
- package/dist/codedna/kappa-gofiber-generator.d.ts +65 -0
- package/dist/codedna/kappa-gofiber-generator.js +587 -0
- package/dist/codedna/kappa-laravel-generator.d.ts +68 -0
- package/dist/codedna/kappa-laravel-generator.js +741 -0
- package/dist/codedna/kappa-lexer.d.ts +44 -0
- package/dist/codedna/kappa-lexer.js +124 -0
- package/dist/codedna/kappa-mantine-generator.d.ts +65 -0
- package/dist/codedna/kappa-mantine-generator.js +518 -0
- package/dist/codedna/kappa-mongoose-generator.d.ts +44 -0
- package/dist/codedna/kappa-mongoose-generator.js +442 -0
- package/dist/codedna/kappa-parser.d.ts +43 -1
- package/dist/codedna/kappa-parser.js +601 -0
- package/dist/codedna/kappa-radix-generator.d.ts +61 -0
- package/dist/codedna/kappa-radix-generator.js +566 -0
- package/dist/codedna/kappa-typeorm-generator.d.ts +59 -0
- package/dist/codedna/kappa-typeorm-generator.js +723 -0
- package/dist/codedna/kappa-vitest-generator.d.ts +85 -0
- package/dist/codedna/kappa-vitest-generator.js +739 -0
- package/dist/codedna/parser.js +26 -1
- package/dist/codegen/cloud-client.d.ts +160 -0
- package/dist/codegen/cloud-client.js +195 -0
- package/dist/codegen/codegen-tool.d.ts +35 -0
- package/dist/codegen/codegen-tool.js +312 -0
- package/dist/codegen/field-inference.d.ts +24 -0
- package/dist/codegen/field-inference.js +101 -0
- package/dist/codegen/form-parser.d.ts +13 -0
- package/dist/codegen/form-parser.js +186 -0
- package/dist/codegen/index.d.ts +2 -0
- package/dist/codegen/index.js +4 -0
- package/dist/codegen/natural-parser.d.ts +50 -0
- package/dist/codegen/natural-parser.js +769 -0
- package/dist/handlers/codedna-handlers.d.ts +1 -1
- package/dist/handlers/codegen-handlers.d.ts +20 -0
- package/dist/handlers/codegen-handlers.js +60 -0
- package/dist/handlers/kappa-handlers.d.ts +97 -0
- package/dist/handlers/kappa-handlers.js +408 -0
- package/dist/handlers/tool-handlers.js +124 -221
- package/dist/helpers/api-client.js +48 -3
- package/dist/helpers/compact-formatter.d.ts +9 -2
- package/dist/helpers/compact-formatter.js +26 -2
- package/dist/helpers/config.d.ts +7 -2
- package/dist/helpers/config.js +25 -10
- package/dist/helpers/session-validation.d.ts +1 -1
- package/dist/helpers/session-validation.js +2 -4
- package/dist/helpers/tasks.d.ts +21 -0
- package/dist/helpers/tasks.js +52 -0
- package/dist/helpers/workers.d.ts +1 -1
- package/dist/helpers/workers.js +19 -19
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +228 -3
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +37 -152
- package/dist/templates/orchestrator-prompt.d.ts +2 -2
- package/dist/templates/orchestrator-prompt.js +31 -38
- package/dist/templates/self-critique.d.ts +50 -0
- package/dist/templates/self-critique.js +209 -0
- package/dist/templates/worker-prompt.d.ts +3 -3
- package/dist/templates/worker-prompt.js +18 -18
- package/dist/tools.js +77 -413
- package/docs/codedna/generator-testing-summary.md +205 -0
- package/docs/codedna/radix-ui-generator.md +478 -0
- package/docs/kappa-gofiber-generator.md +274 -0
- package/docs/kappa-laravel-fixes.md +172 -0
- package/docs/kappa-mongoose-generator.md +322 -0
- package/docs/kappa-vitest-generator.md +337 -0
- package/package.json +1 -1
- package/dist/context/deduplication.test.d.ts +0 -6
- package/dist/context/deduplication.test.js +0 -84
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kappa v2.5 TypeORM Entity Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generates TypeORM entity classes from Kappa entity blocks.
|
|
6
|
+
// Supports PostgreSQL, MySQL, and SQLite dialects.
|
|
7
|
+
//
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Type Mappings
|
|
10
|
+
// =============================================================================
|
|
11
|
+
const COLUMN_TYPE_MAP = {
|
|
12
|
+
postgres: {
|
|
13
|
+
string: 'varchar',
|
|
14
|
+
int: 'integer',
|
|
15
|
+
float: 'double precision',
|
|
16
|
+
bool: 'boolean',
|
|
17
|
+
email: 'varchar',
|
|
18
|
+
url: 'text',
|
|
19
|
+
uuid: 'uuid',
|
|
20
|
+
phone: 'varchar',
|
|
21
|
+
slug: 'varchar',
|
|
22
|
+
markdown: 'text',
|
|
23
|
+
json: 'jsonb',
|
|
24
|
+
timestamp: 'timestamp with time zone',
|
|
25
|
+
date: 'date',
|
|
26
|
+
time: 'time',
|
|
27
|
+
duration: 'interval',
|
|
28
|
+
},
|
|
29
|
+
mysql: {
|
|
30
|
+
string: 'varchar',
|
|
31
|
+
int: 'int',
|
|
32
|
+
float: 'double',
|
|
33
|
+
bool: 'tinyint',
|
|
34
|
+
email: 'varchar',
|
|
35
|
+
url: 'text',
|
|
36
|
+
uuid: 'varchar',
|
|
37
|
+
phone: 'varchar',
|
|
38
|
+
slug: 'varchar',
|
|
39
|
+
markdown: 'text',
|
|
40
|
+
json: 'json',
|
|
41
|
+
timestamp: 'timestamp',
|
|
42
|
+
date: 'date',
|
|
43
|
+
time: 'time',
|
|
44
|
+
duration: 'varchar',
|
|
45
|
+
},
|
|
46
|
+
sqlite: {
|
|
47
|
+
string: 'varchar',
|
|
48
|
+
int: 'integer',
|
|
49
|
+
float: 'real',
|
|
50
|
+
bool: 'integer',
|
|
51
|
+
email: 'varchar',
|
|
52
|
+
url: 'text',
|
|
53
|
+
uuid: 'varchar',
|
|
54
|
+
phone: 'varchar',
|
|
55
|
+
slug: 'varchar',
|
|
56
|
+
markdown: 'text',
|
|
57
|
+
json: 'text',
|
|
58
|
+
timestamp: 'datetime',
|
|
59
|
+
date: 'date',
|
|
60
|
+
time: 'time',
|
|
61
|
+
duration: 'varchar',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const TS_TYPE_MAP = {
|
|
65
|
+
string: 'string',
|
|
66
|
+
int: 'number',
|
|
67
|
+
float: 'number',
|
|
68
|
+
bool: 'boolean',
|
|
69
|
+
email: 'string',
|
|
70
|
+
url: 'string',
|
|
71
|
+
uuid: 'string',
|
|
72
|
+
phone: 'string',
|
|
73
|
+
slug: 'string',
|
|
74
|
+
markdown: 'string',
|
|
75
|
+
json: 'any',
|
|
76
|
+
timestamp: 'Date',
|
|
77
|
+
date: 'Date',
|
|
78
|
+
time: 'string',
|
|
79
|
+
duration: 'string',
|
|
80
|
+
};
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Generator Class
|
|
83
|
+
// =============================================================================
|
|
84
|
+
export class KappaTypeORMGenerator {
|
|
85
|
+
dialect;
|
|
86
|
+
provenance;
|
|
87
|
+
generateRepositories;
|
|
88
|
+
generateMigrations;
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
this.dialect = options.dialect ?? 'postgres';
|
|
91
|
+
this.provenance = options.provenance ?? true;
|
|
92
|
+
this.generateRepositories = options.repositories ?? false;
|
|
93
|
+
this.generateMigrations = options.migrations ?? true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Generate TypeORM entities from entity blocks
|
|
97
|
+
*/
|
|
98
|
+
generate(entities) {
|
|
99
|
+
const entityFiles = {};
|
|
100
|
+
for (const entity of entities) {
|
|
101
|
+
entityFiles[entity.name] = this.generateEntity(entity, entities);
|
|
102
|
+
}
|
|
103
|
+
const result = {
|
|
104
|
+
entities: entityFiles,
|
|
105
|
+
dataSource: this.generateDataSource(entities),
|
|
106
|
+
};
|
|
107
|
+
if (this.generateRepositories) {
|
|
108
|
+
result.repositories = this.generateRepositories ? this.generateRepositoryFiles(entities) : undefined;
|
|
109
|
+
}
|
|
110
|
+
if (this.generateMigrations) {
|
|
111
|
+
result.migration = this.generateMigrationFile(entities);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
// ===========================================================================
|
|
116
|
+
// Entity Generation
|
|
117
|
+
// ===========================================================================
|
|
118
|
+
generateEntity(entity, allEntities) {
|
|
119
|
+
const lines = [];
|
|
120
|
+
// Header comment
|
|
121
|
+
if (this.provenance) {
|
|
122
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
123
|
+
lines.push(`// Dialect: ${this.dialect}`);
|
|
124
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
125
|
+
lines.push('');
|
|
126
|
+
}
|
|
127
|
+
// Imports
|
|
128
|
+
lines.push(this.generateEntityImports(entity, allEntities));
|
|
129
|
+
lines.push('');
|
|
130
|
+
// Entity class
|
|
131
|
+
lines.push(this.generateEntityClass(entity, allEntities));
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
generateEntityImports(entity, allEntities) {
|
|
135
|
+
const imports = new Set([
|
|
136
|
+
'Entity',
|
|
137
|
+
'Column',
|
|
138
|
+
'PrimaryGeneratedColumn',
|
|
139
|
+
'CreateDateColumn',
|
|
140
|
+
'UpdateDateColumn',
|
|
141
|
+
]);
|
|
142
|
+
// Add Index decorator if needed
|
|
143
|
+
if (entity.indexes.length > 0) {
|
|
144
|
+
imports.add('Index');
|
|
145
|
+
}
|
|
146
|
+
// Add relationship decorators
|
|
147
|
+
for (const rel of entity.relationships) {
|
|
148
|
+
switch (rel.type) {
|
|
149
|
+
case 'belongs_to':
|
|
150
|
+
imports.add('ManyToOne');
|
|
151
|
+
imports.add('JoinColumn');
|
|
152
|
+
break;
|
|
153
|
+
case 'has_one':
|
|
154
|
+
imports.add('OneToOne');
|
|
155
|
+
imports.add('JoinColumn');
|
|
156
|
+
break;
|
|
157
|
+
case 'has_many':
|
|
158
|
+
if (rel.through) {
|
|
159
|
+
imports.add('ManyToMany');
|
|
160
|
+
imports.add('JoinTable');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
imports.add('OneToMany');
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const lines = [];
|
|
169
|
+
lines.push(`import { ${Array.from(imports).sort().join(', ')} } from 'typeorm';`);
|
|
170
|
+
// Import related entities
|
|
171
|
+
const relatedEntities = new Set();
|
|
172
|
+
for (const rel of entity.relationships) {
|
|
173
|
+
const relEntity = allEntities.find(e => e.name === rel.entity);
|
|
174
|
+
if (relEntity) {
|
|
175
|
+
relatedEntities.add(rel.entity);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (relatedEntities.size > 0) {
|
|
179
|
+
for (const relEntity of Array.from(relatedEntities).sort()) {
|
|
180
|
+
lines.push(`import { ${relEntity} } from './${relEntity}';`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
generateEntityClass(entity, allEntities) {
|
|
186
|
+
const tableName = this.toSnakeCase(entity.name);
|
|
187
|
+
const lines = [];
|
|
188
|
+
// Entity decorator with indexes
|
|
189
|
+
if (entity.indexes.length > 0) {
|
|
190
|
+
for (const index of entity.indexes) {
|
|
191
|
+
const indexFields = index.fields.map(f => `"${this.toSnakeCase(f)}"`).join(', ');
|
|
192
|
+
lines.push(`@Index([${indexFields}]${index.unique ? ', { unique: true }' : ''})`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
lines.push(`@Entity('${tableName}')`);
|
|
196
|
+
lines.push(`export class ${entity.name} {`);
|
|
197
|
+
// Primary key
|
|
198
|
+
const pkField = entity.fields.find(f => f.modifiers.includes('primary'));
|
|
199
|
+
if (pkField) {
|
|
200
|
+
lines.push(this.generatePrimaryKey(pkField));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Default auto-increment ID
|
|
204
|
+
lines.push(' @PrimaryGeneratedColumn()');
|
|
205
|
+
lines.push(' id: number;');
|
|
206
|
+
lines.push('');
|
|
207
|
+
}
|
|
208
|
+
// Regular fields
|
|
209
|
+
for (const field of entity.fields) {
|
|
210
|
+
if (!field.modifiers.includes('primary')) {
|
|
211
|
+
lines.push(this.generateField(field));
|
|
212
|
+
lines.push('');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Foreign key fields and relationships
|
|
216
|
+
for (const rel of entity.relationships) {
|
|
217
|
+
lines.push(this.generateRelationship(rel, entity, allEntities));
|
|
218
|
+
lines.push('');
|
|
219
|
+
}
|
|
220
|
+
// Timestamps (if not already defined)
|
|
221
|
+
const hasCreatedAt = entity.fields.some(f => f.name.toLowerCase() === 'createdat');
|
|
222
|
+
const hasUpdatedAt = entity.fields.some(f => f.name.toLowerCase() === 'updatedat');
|
|
223
|
+
if (!hasCreatedAt) {
|
|
224
|
+
lines.push(' @CreateDateColumn()');
|
|
225
|
+
lines.push(' createdAt: Date;');
|
|
226
|
+
lines.push('');
|
|
227
|
+
}
|
|
228
|
+
if (!hasUpdatedAt) {
|
|
229
|
+
lines.push(' @UpdateDateColumn()');
|
|
230
|
+
lines.push(' updatedAt: Date;');
|
|
231
|
+
lines.push('');
|
|
232
|
+
}
|
|
233
|
+
lines.push('}');
|
|
234
|
+
return lines.join('\n');
|
|
235
|
+
}
|
|
236
|
+
generatePrimaryKey(field) {
|
|
237
|
+
const lines = [];
|
|
238
|
+
const columnName = this.toSnakeCase(field.name);
|
|
239
|
+
const tsType = this.getTSType(field.type);
|
|
240
|
+
if (field.modifiers.includes('auto')) {
|
|
241
|
+
if (field.type.kind === 'primitive' && field.type.type === 'uuid') {
|
|
242
|
+
lines.push(' @PrimaryGeneratedColumn("uuid")');
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
lines.push(' @PrimaryGeneratedColumn()');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const columnType = this.getColumnType(field.type);
|
|
250
|
+
lines.push(` @Column({ type: '${columnType}', primary: true })`);
|
|
251
|
+
}
|
|
252
|
+
lines.push(` ${field.name}: ${tsType};`);
|
|
253
|
+
lines.push('');
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
generateField(field) {
|
|
257
|
+
const lines = [];
|
|
258
|
+
const columnName = this.toSnakeCase(field.name);
|
|
259
|
+
const columnType = this.getColumnType(field.type);
|
|
260
|
+
const tsType = this.getTSType(field.type);
|
|
261
|
+
const columnOptions = [];
|
|
262
|
+
// Type
|
|
263
|
+
columnOptions.push(`type: '${columnType}'`);
|
|
264
|
+
// Name (if different from property)
|
|
265
|
+
if (columnName !== field.name) {
|
|
266
|
+
columnOptions.push(`name: '${columnName}'`);
|
|
267
|
+
}
|
|
268
|
+
// Nullable
|
|
269
|
+
if (field.modifiers.includes('optional')) {
|
|
270
|
+
columnOptions.push('nullable: true');
|
|
271
|
+
}
|
|
272
|
+
// Unique
|
|
273
|
+
if (field.modifiers.includes('unique')) {
|
|
274
|
+
columnOptions.push('unique: true');
|
|
275
|
+
}
|
|
276
|
+
// Length for strings
|
|
277
|
+
if (field.type.kind === 'primitive' && field.type.range?.max) {
|
|
278
|
+
if (field.type.type === 'string' || field.type.type === 'email' || field.type.type === 'phone') {
|
|
279
|
+
columnOptions.push(`length: ${field.type.range.max}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (field.type.kind === 'primitive') {
|
|
283
|
+
// Default lengths for specific types
|
|
284
|
+
if (field.type.type === 'email') {
|
|
285
|
+
columnOptions.push('length: 255');
|
|
286
|
+
}
|
|
287
|
+
else if (field.type.type === 'phone') {
|
|
288
|
+
columnOptions.push('length: 20');
|
|
289
|
+
}
|
|
290
|
+
else if (field.type.type === 'slug') {
|
|
291
|
+
columnOptions.push('length: 255');
|
|
292
|
+
}
|
|
293
|
+
else if (field.type.type === 'uuid') {
|
|
294
|
+
columnOptions.push('length: 36');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else if (field.type.kind === 'enum') {
|
|
298
|
+
// Default length for enum columns (find longest value + buffer)
|
|
299
|
+
const maxLength = Math.max(...field.type.values.map(v => v.length));
|
|
300
|
+
columnOptions.push(`length: ${Math.max(maxLength, 50)}`);
|
|
301
|
+
}
|
|
302
|
+
// Default value
|
|
303
|
+
if (field.defaultValue) {
|
|
304
|
+
columnOptions.push(`default: ${field.defaultValue}`);
|
|
305
|
+
}
|
|
306
|
+
else if (field.type.kind === 'enum' && field.type.defaultValue) {
|
|
307
|
+
columnOptions.push(`default: '${field.type.defaultValue}'`);
|
|
308
|
+
}
|
|
309
|
+
// Update trigger
|
|
310
|
+
if (field.updateTrigger) {
|
|
311
|
+
columnOptions.push('onUpdate: "CURRENT_TIMESTAMP"');
|
|
312
|
+
}
|
|
313
|
+
lines.push(` @Column({ ${columnOptions.join(', ')} })`);
|
|
314
|
+
const optional = field.modifiers.includes('optional') ? '?' : '';
|
|
315
|
+
lines.push(` ${field.name}${optional}: ${tsType};`);
|
|
316
|
+
return lines.join('\n');
|
|
317
|
+
}
|
|
318
|
+
generateRelationship(rel, entity, allEntities) {
|
|
319
|
+
const lines = [];
|
|
320
|
+
const relatedEntity = rel.entity;
|
|
321
|
+
const propertyName = rel.as || this.toLowerCamelCase(relatedEntity);
|
|
322
|
+
switch (rel.type) {
|
|
323
|
+
case 'belongs_to': {
|
|
324
|
+
const fkColumn = `${this.toSnakeCase(relatedEntity)}_id`;
|
|
325
|
+
lines.push(` @ManyToOne(() => ${relatedEntity}${rel.cascade === 'delete' ? ', { onDelete: "CASCADE" }' : ''})`);
|
|
326
|
+
lines.push(` @JoinColumn({ name: '${fkColumn}' })`);
|
|
327
|
+
lines.push(` ${propertyName}: ${relatedEntity};`);
|
|
328
|
+
lines.push('');
|
|
329
|
+
// Add FK column - determine type based on related entity's PK
|
|
330
|
+
const { fkType, fkTsType } = this.getForeignKeyType(relatedEntity, allEntities);
|
|
331
|
+
const columnOptions = [`type: '${fkType}'`, `name: '${fkColumn}'`];
|
|
332
|
+
// Add length for varchar types
|
|
333
|
+
if (fkType === 'varchar') {
|
|
334
|
+
columnOptions.splice(1, 0, 'length: 36');
|
|
335
|
+
}
|
|
336
|
+
if (rel.cascade === 'nullify') {
|
|
337
|
+
columnOptions.push('nullable: true');
|
|
338
|
+
}
|
|
339
|
+
lines.push(` @Column({ ${columnOptions.join(', ')} })`);
|
|
340
|
+
lines.push(` ${this.toCamelCase(fkColumn)}: ${fkTsType};`);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case 'has_one': {
|
|
344
|
+
const inverseSide = this.toLowerCamelCase(entity.name);
|
|
345
|
+
lines.push(` @OneToOne(() => ${relatedEntity}, (${this.toLowerCamelCase(relatedEntity)}) => ${this.toLowerCamelCase(relatedEntity)}.${inverseSide}${rel.cascade === 'delete' ? ', { cascade: true }' : ''})`);
|
|
346
|
+
lines.push(` @JoinColumn()`);
|
|
347
|
+
lines.push(` ${propertyName}?: ${relatedEntity};`);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'has_many': {
|
|
351
|
+
if (rel.through) {
|
|
352
|
+
// Many-to-many
|
|
353
|
+
const throughTable = this.toSnakeCase(rel.through);
|
|
354
|
+
lines.push(` @ManyToMany(() => ${relatedEntity}${rel.cascade === 'delete' ? ', { cascade: true }' : ''})`);
|
|
355
|
+
lines.push(` @JoinTable({ name: '${throughTable}' })`);
|
|
356
|
+
lines.push(` ${propertyName}: ${relatedEntity}[];`);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// One-to-many
|
|
360
|
+
const inverseSide = this.toLowerCamelCase(entity.name);
|
|
361
|
+
lines.push(` @OneToMany(() => ${relatedEntity}, (${this.toLowerCamelCase(relatedEntity)}) => ${this.toLowerCamelCase(relatedEntity)}.${inverseSide}${rel.cascade === 'delete' ? ', { cascade: true }' : ''})`);
|
|
362
|
+
lines.push(` ${propertyName}: ${relatedEntity}[];`);
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return lines.join('\n');
|
|
368
|
+
}
|
|
369
|
+
getColumnType(type) {
|
|
370
|
+
switch (type.kind) {
|
|
371
|
+
case 'primitive':
|
|
372
|
+
return COLUMN_TYPE_MAP[this.dialect][type.type];
|
|
373
|
+
case 'enum':
|
|
374
|
+
return 'varchar';
|
|
375
|
+
case 'reference':
|
|
376
|
+
return this.dialect === 'postgres' ? 'uuid' : 'varchar';
|
|
377
|
+
case 'array':
|
|
378
|
+
return this.dialect === 'postgres' ? 'jsonb' : 'text';
|
|
379
|
+
default:
|
|
380
|
+
return 'varchar';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
getTSType(type) {
|
|
384
|
+
switch (type.kind) {
|
|
385
|
+
case 'primitive':
|
|
386
|
+
return TS_TYPE_MAP[type.type];
|
|
387
|
+
case 'enum':
|
|
388
|
+
return type.values.map(v => `'${v}'`).join(' | ');
|
|
389
|
+
case 'reference':
|
|
390
|
+
return type.entity;
|
|
391
|
+
case 'array':
|
|
392
|
+
if (type.itemType.kind === 'primitive') {
|
|
393
|
+
return `${TS_TYPE_MAP[type.itemType.type]}[]`;
|
|
394
|
+
}
|
|
395
|
+
return 'any[]';
|
|
396
|
+
default:
|
|
397
|
+
return 'any';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Determine the foreign key column type based on the related entity's primary key
|
|
402
|
+
*/
|
|
403
|
+
getForeignKeyType(relatedEntityName, allEntities) {
|
|
404
|
+
const relatedEntity = allEntities.find(e => e.name === relatedEntityName);
|
|
405
|
+
if (!relatedEntity) {
|
|
406
|
+
// Default to varchar if entity not found
|
|
407
|
+
return { fkType: 'varchar', fkTsType: 'string' };
|
|
408
|
+
}
|
|
409
|
+
// Find the primary key field
|
|
410
|
+
const pkField = relatedEntity.fields.find(f => f.modifiers.includes('primary'));
|
|
411
|
+
if (!pkField) {
|
|
412
|
+
// Default auto-increment ID would be integer
|
|
413
|
+
return { fkType: 'integer', fkTsType: 'number' };
|
|
414
|
+
}
|
|
415
|
+
// Get the column type for the PK field
|
|
416
|
+
const pkType = pkField.type;
|
|
417
|
+
if (pkType.kind === 'primitive') {
|
|
418
|
+
const columnType = COLUMN_TYPE_MAP[this.dialect][pkType.type];
|
|
419
|
+
const tsType = TS_TYPE_MAP[pkType.type];
|
|
420
|
+
return { fkType: columnType, fkTsType: tsType };
|
|
421
|
+
}
|
|
422
|
+
// Fallback
|
|
423
|
+
return { fkType: 'varchar', fkTsType: 'string' };
|
|
424
|
+
}
|
|
425
|
+
// ===========================================================================
|
|
426
|
+
// Data Source Configuration
|
|
427
|
+
// ===========================================================================
|
|
428
|
+
generateDataSource(entities) {
|
|
429
|
+
const lines = [];
|
|
430
|
+
if (this.provenance) {
|
|
431
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
432
|
+
lines.push('// TypeORM Data Source Configuration');
|
|
433
|
+
lines.push('');
|
|
434
|
+
}
|
|
435
|
+
lines.push(`import { DataSource } from 'typeorm';`);
|
|
436
|
+
// Import all entities
|
|
437
|
+
for (const entity of entities) {
|
|
438
|
+
lines.push(`import { ${entity.name} } from './entities/${entity.name}';`);
|
|
439
|
+
}
|
|
440
|
+
lines.push('');
|
|
441
|
+
// Data source configuration
|
|
442
|
+
lines.push('export const AppDataSource = new DataSource({');
|
|
443
|
+
// Dialect-specific configuration
|
|
444
|
+
switch (this.dialect) {
|
|
445
|
+
case 'postgres':
|
|
446
|
+
lines.push(' type: "postgres",');
|
|
447
|
+
lines.push(' host: process.env.DB_HOST || "localhost",');
|
|
448
|
+
lines.push(' port: parseInt(process.env.DB_PORT || "5432"),');
|
|
449
|
+
lines.push(' username: process.env.DB_USER || "postgres",');
|
|
450
|
+
lines.push(' password: process.env.DB_PASSWORD || "",');
|
|
451
|
+
lines.push(' database: process.env.DB_NAME || "database",');
|
|
452
|
+
break;
|
|
453
|
+
case 'mysql':
|
|
454
|
+
lines.push(' type: "mysql",');
|
|
455
|
+
lines.push(' host: process.env.DB_HOST || "localhost",');
|
|
456
|
+
lines.push(' port: parseInt(process.env.DB_PORT || "3306"),');
|
|
457
|
+
lines.push(' username: process.env.DB_USER || "root",');
|
|
458
|
+
lines.push(' password: process.env.DB_PASSWORD || "",');
|
|
459
|
+
lines.push(' database: process.env.DB_NAME || "database",');
|
|
460
|
+
break;
|
|
461
|
+
case 'sqlite':
|
|
462
|
+
lines.push(' type: "sqlite",');
|
|
463
|
+
lines.push(' database: process.env.DB_PATH || "database.sqlite",');
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
lines.push(' synchronize: process.env.NODE_ENV === "development",');
|
|
467
|
+
lines.push(' logging: process.env.NODE_ENV === "development",');
|
|
468
|
+
lines.push(' entities: [');
|
|
469
|
+
for (const entity of entities) {
|
|
470
|
+
lines.push(` ${entity.name},`);
|
|
471
|
+
}
|
|
472
|
+
lines.push(' ],');
|
|
473
|
+
lines.push(' migrations: ["./migrations/*.ts"],');
|
|
474
|
+
lines.push(' subscribers: [],');
|
|
475
|
+
lines.push('});');
|
|
476
|
+
return lines.join('\n');
|
|
477
|
+
}
|
|
478
|
+
// ===========================================================================
|
|
479
|
+
// Repository Generation
|
|
480
|
+
// ===========================================================================
|
|
481
|
+
generateRepositoryFiles(entities) {
|
|
482
|
+
const repositories = {};
|
|
483
|
+
for (const entity of entities) {
|
|
484
|
+
repositories[`${entity.name}Repository`] = this.generateRepository(entity);
|
|
485
|
+
}
|
|
486
|
+
return repositories;
|
|
487
|
+
}
|
|
488
|
+
generateRepository(entity) {
|
|
489
|
+
const lines = [];
|
|
490
|
+
if (this.provenance) {
|
|
491
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
492
|
+
lines.push(`// ${entity.name} Repository`);
|
|
493
|
+
lines.push('');
|
|
494
|
+
}
|
|
495
|
+
lines.push(`import { Repository } from 'typeorm';`);
|
|
496
|
+
lines.push(`import { AppDataSource } from '../data-source';`);
|
|
497
|
+
lines.push(`import { ${entity.name} } from '../entities/${entity.name}';`);
|
|
498
|
+
lines.push('');
|
|
499
|
+
lines.push(`export class ${entity.name}Repository extends Repository<${entity.name}> {`);
|
|
500
|
+
lines.push(' constructor() {');
|
|
501
|
+
lines.push(` super(${entity.name}, AppDataSource.createEntityManager());`);
|
|
502
|
+
lines.push(' }');
|
|
503
|
+
lines.push('');
|
|
504
|
+
lines.push(' // Add custom repository methods here');
|
|
505
|
+
lines.push('}');
|
|
506
|
+
return lines.join('\n');
|
|
507
|
+
}
|
|
508
|
+
// ===========================================================================
|
|
509
|
+
// Migration Generation
|
|
510
|
+
// ===========================================================================
|
|
511
|
+
generateMigrationFile(entities) {
|
|
512
|
+
const lines = [];
|
|
513
|
+
const timestamp = Date.now();
|
|
514
|
+
const className = `CreateInitialSchema${timestamp}`;
|
|
515
|
+
if (this.provenance) {
|
|
516
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
517
|
+
lines.push('// Initial migration');
|
|
518
|
+
lines.push('');
|
|
519
|
+
}
|
|
520
|
+
lines.push(`import { MigrationInterface, QueryRunner } from 'typeorm';`);
|
|
521
|
+
lines.push('');
|
|
522
|
+
lines.push(`export class ${className} implements MigrationInterface {`);
|
|
523
|
+
lines.push(' public async up(queryRunner: QueryRunner): Promise<void> {');
|
|
524
|
+
// Generate CREATE TABLE statements for each entity
|
|
525
|
+
for (const entity of entities) {
|
|
526
|
+
lines.push(` // Create ${entity.name} table`);
|
|
527
|
+
lines.push(` await queryRunner.query(\``);
|
|
528
|
+
lines.push(this.generateCreateTableSQL(entity, entities));
|
|
529
|
+
lines.push(` \`);`);
|
|
530
|
+
lines.push('');
|
|
531
|
+
}
|
|
532
|
+
// Generate indexes
|
|
533
|
+
for (const entity of entities) {
|
|
534
|
+
if (entity.indexes.length > 0) {
|
|
535
|
+
for (const index of entity.indexes) {
|
|
536
|
+
lines.push(this.generateCreateIndexSQL(entity, index));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
lines.push(' }');
|
|
541
|
+
lines.push('');
|
|
542
|
+
lines.push(' public async down(queryRunner: QueryRunner): Promise<void> {');
|
|
543
|
+
// Drop tables in reverse order
|
|
544
|
+
for (let i = entities.length - 1; i >= 0; i--) {
|
|
545
|
+
const tableName = this.toSnakeCase(entities[i].name);
|
|
546
|
+
lines.push(` await queryRunner.query(\`DROP TABLE IF EXISTS "${tableName}"\`);`);
|
|
547
|
+
}
|
|
548
|
+
lines.push(' }');
|
|
549
|
+
lines.push('}');
|
|
550
|
+
return lines.join('\n');
|
|
551
|
+
}
|
|
552
|
+
generateCreateTableSQL(entity, entities) {
|
|
553
|
+
const tableName = this.toSnakeCase(entity.name);
|
|
554
|
+
const lines = [];
|
|
555
|
+
lines.push(` CREATE TABLE "${tableName}" (`);
|
|
556
|
+
const columns = [];
|
|
557
|
+
// Primary key
|
|
558
|
+
const pkField = entity.fields.find(f => f.modifiers.includes('primary'));
|
|
559
|
+
if (pkField) {
|
|
560
|
+
columns.push(this.generateColumnSQL(pkField, true));
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
// Default ID column
|
|
564
|
+
if (this.dialect === 'postgres') {
|
|
565
|
+
columns.push(' "id" SERIAL PRIMARY KEY');
|
|
566
|
+
}
|
|
567
|
+
else if (this.dialect === 'mysql') {
|
|
568
|
+
columns.push(' "id" INT AUTO_INCREMENT PRIMARY KEY');
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
columns.push(' "id" INTEGER PRIMARY KEY AUTOINCREMENT');
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Regular columns
|
|
575
|
+
for (const field of entity.fields) {
|
|
576
|
+
if (!field.modifiers.includes('primary')) {
|
|
577
|
+
columns.push(this.generateColumnSQL(field, false));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// Foreign keys
|
|
581
|
+
for (const rel of entity.relationships) {
|
|
582
|
+
if (rel.type === 'belongs_to') {
|
|
583
|
+
const fkColumn = `${this.toSnakeCase(rel.entity)}_id`;
|
|
584
|
+
const { fkType } = this.getForeignKeyType(rel.entity, entities);
|
|
585
|
+
// Convert to SQL type with proper casing and length
|
|
586
|
+
let sqlType;
|
|
587
|
+
if (fkType === 'uuid' && this.dialect === 'postgres') {
|
|
588
|
+
sqlType = 'UUID';
|
|
589
|
+
}
|
|
590
|
+
else if (fkType === 'uuid' || fkType === 'varchar') {
|
|
591
|
+
sqlType = 'VARCHAR(36)';
|
|
592
|
+
}
|
|
593
|
+
else if (fkType === 'integer') {
|
|
594
|
+
sqlType = this.dialect === 'mysql' ? 'INT' : 'INTEGER';
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
sqlType = fkType.toUpperCase();
|
|
598
|
+
}
|
|
599
|
+
const nullable = rel.cascade === 'nullify' ? '' : ' NOT NULL';
|
|
600
|
+
columns.push(` "${fkColumn}" ${sqlType}${nullable}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Timestamps
|
|
604
|
+
if (!entity.fields.some(f => f.name.toLowerCase() === 'createdat')) {
|
|
605
|
+
columns.push(` "created_at" ${this.dialect === 'postgres' ? 'TIMESTAMP WITH TIME ZONE' : 'TIMESTAMP'} DEFAULT CURRENT_TIMESTAMP`);
|
|
606
|
+
}
|
|
607
|
+
if (!entity.fields.some(f => f.name.toLowerCase() === 'updatedat')) {
|
|
608
|
+
columns.push(` "updated_at" ${this.dialect === 'postgres' ? 'TIMESTAMP WITH TIME ZONE' : 'TIMESTAMP'} DEFAULT CURRENT_TIMESTAMP`);
|
|
609
|
+
}
|
|
610
|
+
lines.push(columns.join(',\n'));
|
|
611
|
+
lines.push(' )');
|
|
612
|
+
return lines.join('\n');
|
|
613
|
+
}
|
|
614
|
+
generateColumnSQL(field, isPrimary) {
|
|
615
|
+
const columnName = this.toSnakeCase(field.name);
|
|
616
|
+
const columnType = this.getColumnType(field.type).toUpperCase();
|
|
617
|
+
const parts = [` "${columnName}"`];
|
|
618
|
+
// Type with length
|
|
619
|
+
if (field.type.kind === 'primitive' && field.type.range?.max) {
|
|
620
|
+
if (field.type.type === 'string' || field.type.type === 'email' || field.type.type === 'phone') {
|
|
621
|
+
parts.push(`VARCHAR(${field.type.range.max})`);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
parts.push(columnType);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else if (field.type.kind === 'primitive') {
|
|
628
|
+
if (field.type.type === 'email') {
|
|
629
|
+
parts.push('VARCHAR(255)');
|
|
630
|
+
}
|
|
631
|
+
else if (field.type.type === 'phone') {
|
|
632
|
+
parts.push('VARCHAR(20)');
|
|
633
|
+
}
|
|
634
|
+
else if (field.type.type === 'slug') {
|
|
635
|
+
parts.push('VARCHAR(255)');
|
|
636
|
+
}
|
|
637
|
+
else if (field.type.type === 'uuid') {
|
|
638
|
+
parts.push(this.dialect === 'postgres' ? 'UUID' : 'VARCHAR(36)');
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
parts.push(columnType);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else if (field.type.kind === 'enum') {
|
|
645
|
+
// Enum columns get varchar with appropriate length
|
|
646
|
+
const maxLength = Math.max(...field.type.values.map(v => v.length));
|
|
647
|
+
parts.push(`VARCHAR(${Math.max(maxLength, 50)})`);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
parts.push(columnType);
|
|
651
|
+
}
|
|
652
|
+
// Primary key
|
|
653
|
+
if (isPrimary) {
|
|
654
|
+
if (field.modifiers.includes('auto')) {
|
|
655
|
+
if (field.type.kind === 'primitive' && field.type.type === 'uuid') {
|
|
656
|
+
parts.push(this.dialect === 'postgres' ? 'DEFAULT gen_random_uuid()' : '');
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
if (this.dialect === 'postgres') {
|
|
660
|
+
parts[parts.length - 1] = 'SERIAL';
|
|
661
|
+
}
|
|
662
|
+
else if (this.dialect === 'mysql') {
|
|
663
|
+
parts.push('PRIMARY KEY');
|
|
664
|
+
parts.push('AUTO_INCREMENT');
|
|
665
|
+
return parts.join(' ');
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
// SQLite: INTEGER PRIMARY KEY AUTOINCREMENT
|
|
669
|
+
parts.push('PRIMARY KEY');
|
|
670
|
+
parts.push('AUTOINCREMENT');
|
|
671
|
+
return parts.join(' ');
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
parts.push('PRIMARY KEY');
|
|
676
|
+
}
|
|
677
|
+
// Nullable
|
|
678
|
+
if (!field.modifiers.includes('optional') && !isPrimary) {
|
|
679
|
+
parts.push('NOT NULL');
|
|
680
|
+
}
|
|
681
|
+
// Unique
|
|
682
|
+
if (field.modifiers.includes('unique')) {
|
|
683
|
+
parts.push('UNIQUE');
|
|
684
|
+
}
|
|
685
|
+
// Default value
|
|
686
|
+
if (field.defaultValue) {
|
|
687
|
+
parts.push(`DEFAULT ${field.defaultValue}`);
|
|
688
|
+
}
|
|
689
|
+
return parts.join(' ');
|
|
690
|
+
}
|
|
691
|
+
generateCreateIndexSQL(entity, index) {
|
|
692
|
+
const tableName = this.toSnakeCase(entity.name);
|
|
693
|
+
const indexName = `idx_${tableName}_${index.fields.map(f => this.toSnakeCase(f)).join('_')}`;
|
|
694
|
+
const fields = index.fields.map(f => `"${this.toSnakeCase(f)}"`).join(', ');
|
|
695
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
696
|
+
return ` await queryRunner.query(\`CREATE ${unique}INDEX "${indexName}" ON "${tableName}" (${fields})\`);`;
|
|
697
|
+
}
|
|
698
|
+
// ===========================================================================
|
|
699
|
+
// Utilities
|
|
700
|
+
// ===========================================================================
|
|
701
|
+
toSnakeCase(str) {
|
|
702
|
+
return str
|
|
703
|
+
.replace(/([A-Z])/g, '_$1')
|
|
704
|
+
.toLowerCase()
|
|
705
|
+
.replace(/^_/, '');
|
|
706
|
+
}
|
|
707
|
+
toCamelCase(str) {
|
|
708
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
709
|
+
}
|
|
710
|
+
toLowerCamelCase(str) {
|
|
711
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// =============================================================================
|
|
715
|
+
// Convenience Function
|
|
716
|
+
// =============================================================================
|
|
717
|
+
/**
|
|
718
|
+
* Generate TypeORM entities from Kappa entity blocks
|
|
719
|
+
*/
|
|
720
|
+
export function generateTypeORMEntities(entities, options = {}) {
|
|
721
|
+
const generator = new KappaTypeORMGenerator(options);
|
|
722
|
+
return generator.generate(entities);
|
|
723
|
+
}
|