@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,442 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kappa v2.5 Mongoose Schema Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generates Mongoose schema code from Kappa entity blocks.
|
|
6
|
+
// Supports MongoDB with TypeScript type definitions.
|
|
7
|
+
//
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Type Mappings
|
|
10
|
+
// =============================================================================
|
|
11
|
+
const PRIMITIVE_TYPE_MAP = {
|
|
12
|
+
string: 'String',
|
|
13
|
+
int: 'Number',
|
|
14
|
+
float: 'Number',
|
|
15
|
+
bool: 'Boolean',
|
|
16
|
+
email: 'String',
|
|
17
|
+
url: 'String',
|
|
18
|
+
uuid: 'String',
|
|
19
|
+
phone: 'String',
|
|
20
|
+
slug: 'String',
|
|
21
|
+
markdown: 'String',
|
|
22
|
+
json: 'Schema.Types.Mixed',
|
|
23
|
+
timestamp: 'Date',
|
|
24
|
+
date: 'Date',
|
|
25
|
+
time: 'String',
|
|
26
|
+
duration: 'Number',
|
|
27
|
+
};
|
|
28
|
+
const TYPESCRIPT_TYPE_MAP = {
|
|
29
|
+
string: 'string',
|
|
30
|
+
int: 'number',
|
|
31
|
+
float: 'number',
|
|
32
|
+
bool: 'boolean',
|
|
33
|
+
email: 'string',
|
|
34
|
+
url: 'string',
|
|
35
|
+
uuid: 'string',
|
|
36
|
+
phone: 'string',
|
|
37
|
+
slug: 'string',
|
|
38
|
+
markdown: 'string',
|
|
39
|
+
json: 'any',
|
|
40
|
+
timestamp: 'Date',
|
|
41
|
+
date: 'Date',
|
|
42
|
+
time: 'string',
|
|
43
|
+
duration: 'number',
|
|
44
|
+
};
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Generator Class
|
|
47
|
+
// =============================================================================
|
|
48
|
+
export class KappaMongooseGenerator {
|
|
49
|
+
typescript;
|
|
50
|
+
provenance;
|
|
51
|
+
separateTypes;
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
this.typescript = options.typescript ?? true;
|
|
54
|
+
this.provenance = options.provenance ?? true;
|
|
55
|
+
this.separateTypes = options.separateTypes ?? true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generate Mongoose schema from entity blocks
|
|
59
|
+
*/
|
|
60
|
+
generate(entities) {
|
|
61
|
+
const schema = this.generateSchemaFile(entities);
|
|
62
|
+
const result = { schema };
|
|
63
|
+
if (this.typescript && this.separateTypes) {
|
|
64
|
+
result.types = this.generateTypesFile(entities);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
// ===========================================================================
|
|
69
|
+
// Schema File Generation
|
|
70
|
+
// ===========================================================================
|
|
71
|
+
generateSchemaFile(entities) {
|
|
72
|
+
const lines = [];
|
|
73
|
+
// Header comment
|
|
74
|
+
if (this.provenance) {
|
|
75
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
76
|
+
lines.push('// Mongoose Schema for MongoDB');
|
|
77
|
+
lines.push(`// Generated at: ${new Date().toISOString()}`);
|
|
78
|
+
lines.push('');
|
|
79
|
+
}
|
|
80
|
+
// Imports
|
|
81
|
+
lines.push(this.generateSchemaImports(entities));
|
|
82
|
+
lines.push('');
|
|
83
|
+
// Generate each schema
|
|
84
|
+
for (const entity of entities) {
|
|
85
|
+
lines.push(this.generateSchema(entity, entities));
|
|
86
|
+
lines.push('');
|
|
87
|
+
}
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
}
|
|
90
|
+
generateSchemaImports(entities) {
|
|
91
|
+
const imports = ['Schema', 'model', 'Document'];
|
|
92
|
+
// Check if we need Types for Mixed
|
|
93
|
+
for (const entity of entities) {
|
|
94
|
+
for (const field of entity.fields) {
|
|
95
|
+
if (this.fieldUsesSchemaTypes(field.type)) {
|
|
96
|
+
imports.push('Types');
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const mongooseImport = `import { ${imports.join(', ')} } from 'mongoose';`;
|
|
102
|
+
if (this.typescript && this.separateTypes) {
|
|
103
|
+
// Import types from separate file
|
|
104
|
+
const typeImports = entities
|
|
105
|
+
.map(e => `${e.name}Document`)
|
|
106
|
+
.join(', ');
|
|
107
|
+
return `${mongooseImport}\nimport type { ${typeImports} } from './types';`;
|
|
108
|
+
}
|
|
109
|
+
return mongooseImport;
|
|
110
|
+
}
|
|
111
|
+
fieldUsesSchemaTypes(type) {
|
|
112
|
+
if (type.kind === 'primitive' && type.type === 'json') {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (type.kind === 'reference') {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (type.kind === 'array' && type.itemType.kind === 'reference') {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
generateSchema(entity, allEntities) {
|
|
124
|
+
const schemaName = `${entity.name}Schema`;
|
|
125
|
+
const modelName = entity.name;
|
|
126
|
+
const lines = [];
|
|
127
|
+
// Interface definition (if TypeScript and not separate)
|
|
128
|
+
if (this.typescript && !this.separateTypes) {
|
|
129
|
+
lines.push(this.generateInterface(entity, allEntities));
|
|
130
|
+
lines.push('');
|
|
131
|
+
}
|
|
132
|
+
// Schema definition
|
|
133
|
+
lines.push(`const ${schemaName} = new Schema<${modelName}Document>({`);
|
|
134
|
+
// Generate fields
|
|
135
|
+
for (const field of entity.fields) {
|
|
136
|
+
lines.push(` ${this.generateField(field, entity)},`);
|
|
137
|
+
}
|
|
138
|
+
// Generate foreign key fields for belongs_to relationships
|
|
139
|
+
for (const rel of entity.relationships) {
|
|
140
|
+
if (rel.type === 'belongs_to') {
|
|
141
|
+
lines.push(` ${this.generateRelationshipField(rel)},`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
lines.push('}, {');
|
|
145
|
+
lines.push(' timestamps: true,');
|
|
146
|
+
lines.push(' collection: \'' + this.toSnakeCase(entity.name) + '\',');
|
|
147
|
+
lines.push('});');
|
|
148
|
+
// Generate indexes
|
|
149
|
+
if (entity.indexes.length > 0) {
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push(this.generateIndexes(entity, schemaName));
|
|
152
|
+
}
|
|
153
|
+
// Generate virtual relationships
|
|
154
|
+
const virtuals = this.generateVirtualRelationships(entity, allEntities, schemaName);
|
|
155
|
+
if (virtuals) {
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push(virtuals);
|
|
158
|
+
}
|
|
159
|
+
// Export model
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push(`export const ${modelName} = model<${modelName}Document>('${modelName}', ${schemaName});`);
|
|
162
|
+
return lines.join('\n');
|
|
163
|
+
}
|
|
164
|
+
generateField(field, entity) {
|
|
165
|
+
const { name, type, modifiers } = field;
|
|
166
|
+
const fieldDef = [];
|
|
167
|
+
// Type
|
|
168
|
+
const mongooseType = this.getMongooseType(type);
|
|
169
|
+
fieldDef.push(`type: ${mongooseType}`);
|
|
170
|
+
// Handle modifiers
|
|
171
|
+
if (modifiers.includes('primary')) {
|
|
172
|
+
// MongoDB uses _id automatically, but if explicitly marked primary
|
|
173
|
+
if (name !== 'id') {
|
|
174
|
+
fieldDef.push('unique: true');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (modifiers.includes('unique')) {
|
|
178
|
+
fieldDef.push('unique: true');
|
|
179
|
+
}
|
|
180
|
+
if (!modifiers.includes('optional') && !modifiers.includes('primary')) {
|
|
181
|
+
fieldDef.push('required: true');
|
|
182
|
+
}
|
|
183
|
+
if (modifiers.includes('immutable')) {
|
|
184
|
+
fieldDef.push('immutable: true');
|
|
185
|
+
}
|
|
186
|
+
// Auto-increment or default values
|
|
187
|
+
if (modifiers.includes('auto')) {
|
|
188
|
+
if (type.kind === 'primitive') {
|
|
189
|
+
if (type.type === 'uuid') {
|
|
190
|
+
fieldDef.push('default: () => new Types.ObjectId().toString()');
|
|
191
|
+
}
|
|
192
|
+
else if (type.type === 'timestamp' || type.type === 'date') {
|
|
193
|
+
fieldDef.push('default: Date.now');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Update trigger (for updated_at fields)
|
|
198
|
+
if (field.updateTrigger) {
|
|
199
|
+
// Mongoose handles this via timestamps option
|
|
200
|
+
// But we can add a pre-save hook comment
|
|
201
|
+
}
|
|
202
|
+
// Default values
|
|
203
|
+
if (field.defaultValue !== undefined) {
|
|
204
|
+
if (type.kind === 'enum') {
|
|
205
|
+
fieldDef.push(`default: '${field.defaultValue}'`);
|
|
206
|
+
}
|
|
207
|
+
else if (type.kind === 'primitive') {
|
|
208
|
+
if (type.type === 'bool') {
|
|
209
|
+
fieldDef.push(`default: ${field.defaultValue}`);
|
|
210
|
+
}
|
|
211
|
+
else if (type.type === 'string' || type.type === 'email' || type.type === 'url' ||
|
|
212
|
+
type.type === 'phone' || type.type === 'slug' || type.type === 'markdown') {
|
|
213
|
+
fieldDef.push(`default: '${field.defaultValue}'`);
|
|
214
|
+
}
|
|
215
|
+
else if (type.type === 'int' || type.type === 'float' || type.type === 'duration') {
|
|
216
|
+
fieldDef.push(`default: ${field.defaultValue}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Validators
|
|
221
|
+
const validators = this.generateValidators(field);
|
|
222
|
+
if (validators.length > 0) {
|
|
223
|
+
fieldDef.push(...validators);
|
|
224
|
+
}
|
|
225
|
+
// Enum values
|
|
226
|
+
if (type.kind === 'enum') {
|
|
227
|
+
fieldDef.push(`enum: [${type.values.map(v => `'${v}'`).join(', ')}]`);
|
|
228
|
+
}
|
|
229
|
+
// Handle range constraints
|
|
230
|
+
if (type.kind === 'primitive' && type.range) {
|
|
231
|
+
if (type.type === 'string' && type.range.max) {
|
|
232
|
+
fieldDef.push(`maxlength: ${type.range.max}`);
|
|
233
|
+
}
|
|
234
|
+
if (type.type === 'string' && type.range.min) {
|
|
235
|
+
fieldDef.push(`minlength: ${type.range.min}`);
|
|
236
|
+
}
|
|
237
|
+
if ((type.type === 'int' || type.type === 'float') && type.range.min !== undefined) {
|
|
238
|
+
fieldDef.push(`min: ${type.range.min}`);
|
|
239
|
+
}
|
|
240
|
+
if ((type.type === 'int' || type.type === 'float') && type.range.max !== undefined) {
|
|
241
|
+
fieldDef.push(`max: ${type.range.max}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Skip _id field as Mongoose handles it
|
|
245
|
+
if (name === 'id' && modifiers.includes('primary') && modifiers.includes('auto')) {
|
|
246
|
+
return '// _id is handled automatically by MongoDB';
|
|
247
|
+
}
|
|
248
|
+
return `${name}: { ${fieldDef.join(', ')} }`;
|
|
249
|
+
}
|
|
250
|
+
generateRelationshipField(rel) {
|
|
251
|
+
const fieldName = `${this.toCamelCase(rel.entity)}Id`;
|
|
252
|
+
const refModel = rel.entity;
|
|
253
|
+
return `${fieldName}: { type: Schema.Types.ObjectId, ref: '${refModel}' }`;
|
|
254
|
+
}
|
|
255
|
+
generateValidators(field) {
|
|
256
|
+
const validators = [];
|
|
257
|
+
if (field.type.kind === 'primitive') {
|
|
258
|
+
switch (field.type.type) {
|
|
259
|
+
case 'email':
|
|
260
|
+
validators.push('validate: { validator: (v: string) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(v), message: \'Invalid email format\' }');
|
|
261
|
+
break;
|
|
262
|
+
case 'url':
|
|
263
|
+
validators.push('validate: { validator: (v: string) => /^https?:\\/\\/.+/.test(v), message: \'Invalid URL format\' }');
|
|
264
|
+
break;
|
|
265
|
+
case 'phone':
|
|
266
|
+
validators.push('validate: { validator: (v: string) => /^\\+?[1-9]\\d{1,14}$/.test(v), message: \'Invalid phone format\' }');
|
|
267
|
+
break;
|
|
268
|
+
case 'uuid':
|
|
269
|
+
validators.push('validate: { validator: (v: string) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v), message: \'Invalid UUID format\' }');
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return validators;
|
|
274
|
+
}
|
|
275
|
+
getMongooseType(type) {
|
|
276
|
+
switch (type.kind) {
|
|
277
|
+
case 'primitive':
|
|
278
|
+
return PRIMITIVE_TYPE_MAP[type.type];
|
|
279
|
+
case 'enum':
|
|
280
|
+
return 'String';
|
|
281
|
+
case 'reference':
|
|
282
|
+
return 'Schema.Types.ObjectId';
|
|
283
|
+
case 'array':
|
|
284
|
+
if (type.itemType.kind === 'reference') {
|
|
285
|
+
return `[{ type: Schema.Types.ObjectId, ref: '${type.itemType.entity}' }]`;
|
|
286
|
+
}
|
|
287
|
+
else if (type.itemType.kind === 'primitive') {
|
|
288
|
+
return `[${PRIMITIVE_TYPE_MAP[type.itemType.type]}]`;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
return '[Schema.Types.Mixed]';
|
|
292
|
+
}
|
|
293
|
+
default:
|
|
294
|
+
return 'Schema.Types.Mixed';
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
generateIndexes(entity, schemaName) {
|
|
298
|
+
const lines = [];
|
|
299
|
+
for (const index of entity.indexes) {
|
|
300
|
+
const fields = index.fields.map(f => `${f}: 1`).join(', ');
|
|
301
|
+
const unique = index.unique ? ', { unique: true }' : '';
|
|
302
|
+
lines.push(`${schemaName}.index({ ${fields} }${unique});`);
|
|
303
|
+
}
|
|
304
|
+
return lines.join('\n');
|
|
305
|
+
}
|
|
306
|
+
generateVirtualRelationships(entity, allEntities, schemaName) {
|
|
307
|
+
const virtuals = [];
|
|
308
|
+
for (const rel of entity.relationships) {
|
|
309
|
+
const relName = rel.as || this.toCamelCase(rel.entity);
|
|
310
|
+
const refModel = rel.entity;
|
|
311
|
+
const localField = rel.type === 'belongs_to' ? `${this.toCamelCase(rel.entity)}Id` : '_id';
|
|
312
|
+
const foreignField = rel.type === 'belongs_to' ? '_id' : `${this.toCamelCase(entity.name)}Id`;
|
|
313
|
+
switch (rel.type) {
|
|
314
|
+
case 'has_many':
|
|
315
|
+
virtuals.push(`${schemaName}.virtual('${relName}', {`);
|
|
316
|
+
virtuals.push(` ref: '${refModel}',`);
|
|
317
|
+
virtuals.push(` localField: '${localField}',`);
|
|
318
|
+
virtuals.push(` foreignField: '${foreignField}',`);
|
|
319
|
+
if (rel.through) {
|
|
320
|
+
virtuals.push(` // Many-to-many through ${rel.through}`);
|
|
321
|
+
}
|
|
322
|
+
virtuals.push('});');
|
|
323
|
+
break;
|
|
324
|
+
case 'has_one':
|
|
325
|
+
virtuals.push(`${schemaName}.virtual('${relName}', {`);
|
|
326
|
+
virtuals.push(` ref: '${refModel}',`);
|
|
327
|
+
virtuals.push(` localField: '${localField}',`);
|
|
328
|
+
virtuals.push(` foreignField: '${foreignField}',`);
|
|
329
|
+
virtuals.push(' justOne: true,');
|
|
330
|
+
virtuals.push('});');
|
|
331
|
+
break;
|
|
332
|
+
// belongs_to is handled via direct reference field
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (virtuals.length === 0)
|
|
336
|
+
return null;
|
|
337
|
+
return virtuals.join('\n');
|
|
338
|
+
}
|
|
339
|
+
// ===========================================================================
|
|
340
|
+
// TypeScript Interface Generation
|
|
341
|
+
// ===========================================================================
|
|
342
|
+
generateInterface(entity, allEntities) {
|
|
343
|
+
const lines = [];
|
|
344
|
+
lines.push(`export interface ${entity.name}Document extends Document {`);
|
|
345
|
+
// Generate field types
|
|
346
|
+
for (const field of entity.fields) {
|
|
347
|
+
if (field.name === 'id' && field.modifiers.includes('primary')) {
|
|
348
|
+
// Skip - Document already has _id
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const tsType = this.getTypeScriptType(field.type);
|
|
352
|
+
const optional = field.modifiers.includes('optional') ? '?' : '';
|
|
353
|
+
lines.push(` ${field.name}${optional}: ${tsType};`);
|
|
354
|
+
}
|
|
355
|
+
// Generate relationship types
|
|
356
|
+
for (const rel of entity.relationships) {
|
|
357
|
+
const relName = rel.as || this.toCamelCase(rel.entity);
|
|
358
|
+
const refType = rel.entity;
|
|
359
|
+
switch (rel.type) {
|
|
360
|
+
case 'belongs_to':
|
|
361
|
+
const fieldName = `${this.toCamelCase(rel.entity)}Id`;
|
|
362
|
+
lines.push(` ${fieldName}?: Types.ObjectId;`);
|
|
363
|
+
lines.push(` ${relName}?: ${refType}Document;`);
|
|
364
|
+
break;
|
|
365
|
+
case 'has_many':
|
|
366
|
+
lines.push(` ${relName}?: ${refType}Document[];`);
|
|
367
|
+
break;
|
|
368
|
+
case 'has_one':
|
|
369
|
+
lines.push(` ${relName}?: ${refType}Document;`);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Mongoose timestamps
|
|
374
|
+
lines.push(' createdAt: Date;');
|
|
375
|
+
lines.push(' updatedAt: Date;');
|
|
376
|
+
lines.push('}');
|
|
377
|
+
return lines.join('\n');
|
|
378
|
+
}
|
|
379
|
+
getTypeScriptType(type) {
|
|
380
|
+
switch (type.kind) {
|
|
381
|
+
case 'primitive':
|
|
382
|
+
return TYPESCRIPT_TYPE_MAP[type.type];
|
|
383
|
+
case 'enum':
|
|
384
|
+
return type.values.map(v => `'${v}'`).join(' | ');
|
|
385
|
+
case 'reference':
|
|
386
|
+
return `Types.ObjectId | ${type.entity}Document`;
|
|
387
|
+
case 'array':
|
|
388
|
+
if (type.itemType.kind === 'reference') {
|
|
389
|
+
return `(Types.ObjectId | ${type.itemType.entity}Document)[]`;
|
|
390
|
+
}
|
|
391
|
+
else if (type.itemType.kind === 'primitive') {
|
|
392
|
+
return `${TYPESCRIPT_TYPE_MAP[type.itemType.type]}[]`;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
return 'any[]';
|
|
396
|
+
}
|
|
397
|
+
default:
|
|
398
|
+
return 'any';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// ===========================================================================
|
|
402
|
+
// Types File Generation
|
|
403
|
+
// ===========================================================================
|
|
404
|
+
generateTypesFile(entities) {
|
|
405
|
+
const lines = [];
|
|
406
|
+
if (this.provenance) {
|
|
407
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA');
|
|
408
|
+
lines.push('// TypeScript types for Mongoose models');
|
|
409
|
+
lines.push('');
|
|
410
|
+
}
|
|
411
|
+
lines.push(`import type { Document, Types } from 'mongoose';`);
|
|
412
|
+
lines.push('');
|
|
413
|
+
// Generate interfaces for each entity
|
|
414
|
+
for (const entity of entities) {
|
|
415
|
+
lines.push(this.generateInterface(entity, entities));
|
|
416
|
+
lines.push('');
|
|
417
|
+
}
|
|
418
|
+
return lines.join('\n');
|
|
419
|
+
}
|
|
420
|
+
// ===========================================================================
|
|
421
|
+
// Utilities
|
|
422
|
+
// ===========================================================================
|
|
423
|
+
toSnakeCase(str) {
|
|
424
|
+
return str
|
|
425
|
+
.replace(/([A-Z])/g, '_$1')
|
|
426
|
+
.toLowerCase()
|
|
427
|
+
.replace(/^_/, '');
|
|
428
|
+
}
|
|
429
|
+
toCamelCase(str) {
|
|
430
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// =============================================================================
|
|
434
|
+
// Convenience Function
|
|
435
|
+
// =============================================================================
|
|
436
|
+
/**
|
|
437
|
+
* Generate Mongoose schema from Kappa entities
|
|
438
|
+
*/
|
|
439
|
+
export function generateMongooseSchema(entities, options = {}) {
|
|
440
|
+
const generator = new KappaMongooseGenerator(options);
|
|
441
|
+
return generator.generate(entities);
|
|
442
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CstParser, IToken, CstNode } from 'chevrotain';
|
|
2
|
-
import { KappaSpec, ProjectBlock, AuthBlock, HashConfig, EntityBlock, EntityField, FieldType, FieldModifier, EntityRelationship, Capability, LifecycleHook, APIBlock, APIOperation, APIReturnType, CRUDShorthand, CRUDAction, EffectType, APIParameter, JourneyBlock, JourneyStep, PageBlock, ComponentBlock, FormBlock, FormField, DesignBlock } from './kappa-ast.js';
|
|
2
|
+
import { KappaSpec, ProjectBlock, AuthBlock, HashConfig, EntityBlock, EntityField, FieldType, FieldModifier, EntityRelationship, Capability, LifecycleHook, APIBlock, APIOperation, APIReturnType, CRUDShorthand, CRUDAction, EffectType, APIParameter, JourneyBlock, JourneyStep, PageBlock, ComponentBlock, FormBlock, FormField, DesignBlock, TestBlock, TestSuite, TestCase, TestAssertion, TestMock } from './kappa-ast.js';
|
|
3
3
|
declare class KappaParser extends CstParser {
|
|
4
4
|
constructor();
|
|
5
5
|
kappaSpec: import("chevrotain").ParserMethod<[], CstNode>;
|
|
@@ -73,6 +73,18 @@ declare class KappaParser extends CstParser {
|
|
|
73
73
|
typographyDefinition: import("chevrotain").ParserMethod<[], CstNode>;
|
|
74
74
|
spacingDefinition: import("chevrotain").ParserMethod<[], CstNode>;
|
|
75
75
|
animationDefinition: import("chevrotain").ParserMethod<[], CstNode>;
|
|
76
|
+
testBlock: import("chevrotain").ParserMethod<[], CstNode>;
|
|
77
|
+
testProperty: import("chevrotain").ParserMethod<[], CstNode>;
|
|
78
|
+
coverageProperty: import("chevrotain").ParserMethod<[], CstNode>;
|
|
79
|
+
testSuite: import("chevrotain").ParserMethod<[], CstNode>;
|
|
80
|
+
testLifecycle: import("chevrotain").ParserMethod<[], CstNode>;
|
|
81
|
+
testMock: import("chevrotain").ParserMethod<[], CstNode>;
|
|
82
|
+
mockProperty: import("chevrotain").ParserMethod<[], CstNode>;
|
|
83
|
+
testCase: import("chevrotain").ParserMethod<[], CstNode>;
|
|
84
|
+
testCaseSection: import("chevrotain").ParserMethod<[], CstNode>;
|
|
85
|
+
testAssertion: import("chevrotain").ParserMethod<[], CstNode>;
|
|
86
|
+
assertionProperty: import("chevrotain").ParserMethod<[], CstNode>;
|
|
87
|
+
assertionType: import("chevrotain").ParserMethod<[], CstNode>;
|
|
76
88
|
}
|
|
77
89
|
declare const parser: KappaParser;
|
|
78
90
|
declare const BaseCstVisitor: new (...args: any[]) => import("chevrotain").ICstVisitor<any, any>;
|
|
@@ -242,6 +254,36 @@ declare class KappaAstBuilder extends BaseCstVisitor {
|
|
|
242
254
|
name: string;
|
|
243
255
|
value: string;
|
|
244
256
|
};
|
|
257
|
+
testBlock(ctx: Record<string, unknown>): TestBlock;
|
|
258
|
+
testProperty(ctx: Record<string, unknown>): {
|
|
259
|
+
type: string;
|
|
260
|
+
value: unknown;
|
|
261
|
+
};
|
|
262
|
+
coverageProperty(ctx: Record<string, IToken[]>): {
|
|
263
|
+
name: string;
|
|
264
|
+
value: number;
|
|
265
|
+
};
|
|
266
|
+
testSuite(ctx: Record<string, unknown>): TestSuite;
|
|
267
|
+
testLifecycle(ctx: Record<string, unknown>): {
|
|
268
|
+
type: string;
|
|
269
|
+
statements: string[];
|
|
270
|
+
};
|
|
271
|
+
testMock(ctx: Record<string, unknown>): TestMock;
|
|
272
|
+
mockProperty(ctx: Record<string, unknown>): {
|
|
273
|
+
type: string;
|
|
274
|
+
value: string;
|
|
275
|
+
};
|
|
276
|
+
testCase(ctx: Record<string, unknown>): TestCase;
|
|
277
|
+
testCaseSection(ctx: Record<string, unknown>): {
|
|
278
|
+
type: string;
|
|
279
|
+
value: unknown;
|
|
280
|
+
};
|
|
281
|
+
testAssertion(ctx: Record<string, unknown>): TestAssertion;
|
|
282
|
+
assertionProperty(ctx: Record<string, unknown>): {
|
|
283
|
+
name: string;
|
|
284
|
+
value: string;
|
|
285
|
+
};
|
|
286
|
+
assertionType(ctx: Record<string, IToken[]>): string;
|
|
245
287
|
}
|
|
246
288
|
export interface ParseResult {
|
|
247
289
|
success: boolean;
|