@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.
Files changed (85) hide show
  1. package/dist/cli.js +9 -1
  2. package/dist/codedna/__tests__/examples/mongoose-example.d.ts +6 -0
  3. package/dist/codedna/__tests__/examples/mongoose-example.js +163 -0
  4. package/dist/codedna/__tests__/fixtures/typeorm-production-test.d.ts +1 -0
  5. package/dist/codedna/__tests__/fixtures/typeorm-production-test.js +231 -0
  6. package/dist/codedna/__tests__/fixtures/typeorm-test.d.ts +1 -0
  7. package/dist/codedna/__tests__/fixtures/typeorm-test.js +124 -0
  8. package/dist/codedna/__tests__/laravel-output-review.d.ts +1 -0
  9. package/dist/codedna/__tests__/laravel-output-review.js +249 -0
  10. package/dist/codedna/__tests__/mongoose-output-test.d.ts +1 -0
  11. package/dist/codedna/__tests__/mongoose-output-test.js +178 -0
  12. package/dist/codedna/examples/radix-example.d.ts +2 -0
  13. package/dist/codedna/examples/radix-example.js +259 -0
  14. package/dist/codedna/index.d.ts +5 -3
  15. package/dist/codedna/index.js +6 -3
  16. package/dist/codedna/kappa-ast.d.ts +143 -5
  17. package/dist/codedna/kappa-drizzle-generator.js +8 -5
  18. package/dist/codedna/kappa-gofiber-generator.d.ts +65 -0
  19. package/dist/codedna/kappa-gofiber-generator.js +587 -0
  20. package/dist/codedna/kappa-laravel-generator.d.ts +68 -0
  21. package/dist/codedna/kappa-laravel-generator.js +741 -0
  22. package/dist/codedna/kappa-lexer.d.ts +44 -0
  23. package/dist/codedna/kappa-lexer.js +124 -0
  24. package/dist/codedna/kappa-mantine-generator.d.ts +65 -0
  25. package/dist/codedna/kappa-mantine-generator.js +518 -0
  26. package/dist/codedna/kappa-mongoose-generator.d.ts +44 -0
  27. package/dist/codedna/kappa-mongoose-generator.js +442 -0
  28. package/dist/codedna/kappa-parser.d.ts +43 -1
  29. package/dist/codedna/kappa-parser.js +601 -0
  30. package/dist/codedna/kappa-radix-generator.d.ts +61 -0
  31. package/dist/codedna/kappa-radix-generator.js +566 -0
  32. package/dist/codedna/kappa-typeorm-generator.d.ts +59 -0
  33. package/dist/codedna/kappa-typeorm-generator.js +723 -0
  34. package/dist/codedna/kappa-vitest-generator.d.ts +85 -0
  35. package/dist/codedna/kappa-vitest-generator.js +739 -0
  36. package/dist/codedna/parser.js +26 -1
  37. package/dist/codegen/cloud-client.d.ts +160 -0
  38. package/dist/codegen/cloud-client.js +195 -0
  39. package/dist/codegen/codegen-tool.d.ts +35 -0
  40. package/dist/codegen/codegen-tool.js +312 -0
  41. package/dist/codegen/field-inference.d.ts +24 -0
  42. package/dist/codegen/field-inference.js +101 -0
  43. package/dist/codegen/form-parser.d.ts +13 -0
  44. package/dist/codegen/form-parser.js +186 -0
  45. package/dist/codegen/index.d.ts +2 -0
  46. package/dist/codegen/index.js +4 -0
  47. package/dist/codegen/natural-parser.d.ts +50 -0
  48. package/dist/codegen/natural-parser.js +769 -0
  49. package/dist/handlers/codedna-handlers.d.ts +1 -1
  50. package/dist/handlers/codegen-handlers.d.ts +20 -0
  51. package/dist/handlers/codegen-handlers.js +60 -0
  52. package/dist/handlers/kappa-handlers.d.ts +97 -0
  53. package/dist/handlers/kappa-handlers.js +408 -0
  54. package/dist/handlers/tool-handlers.js +124 -221
  55. package/dist/helpers/api-client.js +48 -3
  56. package/dist/helpers/compact-formatter.d.ts +9 -2
  57. package/dist/helpers/compact-formatter.js +26 -2
  58. package/dist/helpers/config.d.ts +7 -2
  59. package/dist/helpers/config.js +25 -10
  60. package/dist/helpers/session-validation.d.ts +1 -1
  61. package/dist/helpers/session-validation.js +2 -4
  62. package/dist/helpers/tasks.d.ts +21 -0
  63. package/dist/helpers/tasks.js +52 -0
  64. package/dist/helpers/workers.d.ts +1 -1
  65. package/dist/helpers/workers.js +19 -19
  66. package/dist/setup.d.ts +1 -0
  67. package/dist/setup.js +228 -3
  68. package/dist/templates/claude-md.d.ts +1 -1
  69. package/dist/templates/claude-md.js +37 -152
  70. package/dist/templates/orchestrator-prompt.d.ts +2 -2
  71. package/dist/templates/orchestrator-prompt.js +31 -38
  72. package/dist/templates/self-critique.d.ts +50 -0
  73. package/dist/templates/self-critique.js +209 -0
  74. package/dist/templates/worker-prompt.d.ts +3 -3
  75. package/dist/templates/worker-prompt.js +18 -18
  76. package/dist/tools.js +77 -413
  77. package/docs/codedna/generator-testing-summary.md +205 -0
  78. package/docs/codedna/radix-ui-generator.md +478 -0
  79. package/docs/kappa-gofiber-generator.md +274 -0
  80. package/docs/kappa-laravel-fixes.md +172 -0
  81. package/docs/kappa-mongoose-generator.md +322 -0
  82. package/docs/kappa-vitest-generator.md +337 -0
  83. package/package.json +1 -1
  84. package/dist/context/deduplication.test.d.ts +0 -6
  85. 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
+ }