@claudetools/tools 0.3.9 → 0.4.0

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.
@@ -0,0 +1,41 @@
1
+ import { EntitySpec } from '../parser.js';
2
+ import { TemplateEngine } from '../template-engine.js';
3
+ import { TemplateRegistry } from '../registry.js';
4
+ import { GenerationResult, GenerationMetadata } from '../types.js';
5
+ export declare abstract class BaseGenerator {
6
+ protected engine: TemplateEngine;
7
+ protected registry: TemplateRegistry;
8
+ constructor(registry: TemplateRegistry);
9
+ /**
10
+ * Generate code from entity specification
11
+ */
12
+ generate(entity: EntitySpec, options?: any): Promise<GenerationResult>;
13
+ /**
14
+ * Get the generator ID (e.g., "express-api")
15
+ */
16
+ protected abstract getGeneratorId(): string;
17
+ /**
18
+ * Get list of required template files based on options
19
+ */
20
+ protected abstract getRequiredTemplates(options: any): string[];
21
+ /**
22
+ * Get mapping of output file paths to template files
23
+ */
24
+ protected abstract getFileMapping(entity: EntitySpec, options: any): Record<string, string>;
25
+ /**
26
+ * Calculate generation metadata
27
+ */
28
+ protected calculateMetadata(generatorId: string, framework: string, entity: EntitySpec, files: Record<string, string>): GenerationMetadata;
29
+ /**
30
+ * Validate options against generator capabilities
31
+ */
32
+ protected validateOptions(options: any): Promise<void>;
33
+ /**
34
+ * Helper to get output path for entity file
35
+ */
36
+ protected getEntityPath(entity: EntitySpec, directory: string, extension: string): string;
37
+ /**
38
+ * Helper to check if option is enabled
39
+ */
40
+ protected isEnabled(options: any, key: string, defaultValue?: boolean): boolean;
41
+ }
@@ -0,0 +1,102 @@
1
+ // =============================================================================
2
+ // Base Generator Class
3
+ // =============================================================================
4
+ //
5
+ // Abstract base class for all code generators with common file generation
6
+ // pipeline, metadata tracking, and utilities.
7
+ //
8
+ import { TemplateEngine, buildContext } from '../template-engine.js';
9
+ export class BaseGenerator {
10
+ engine;
11
+ registry;
12
+ constructor(registry) {
13
+ this.engine = new TemplateEngine();
14
+ this.registry = registry;
15
+ }
16
+ /**
17
+ * Generate code from entity specification
18
+ */
19
+ async generate(entity, options = {}) {
20
+ // Get generator metadata
21
+ const generatorId = this.getGeneratorId();
22
+ const metadata = await this.registry.getGeneratorMetadata(generatorId);
23
+ // Get required templates
24
+ const templateFiles = this.getRequiredTemplates(options);
25
+ const templates = await this.registry.getTemplates(generatorId, templateFiles);
26
+ // Build template context
27
+ const context = buildContext(entity, options);
28
+ // Generate files
29
+ const files = {};
30
+ const fileMapping = this.getFileMapping(entity, options);
31
+ for (const [outputPath, templateFile] of Object.entries(fileMapping)) {
32
+ const template = templates[templateFile];
33
+ if (!template) {
34
+ throw new Error(`Template not found: ${templateFile}`);
35
+ }
36
+ const content = this.engine.render(template, context);
37
+ files[outputPath] = content;
38
+ }
39
+ // Calculate metadata
40
+ const generationMetadata = this.calculateMetadata(generatorId, metadata.framework, entity, files);
41
+ return {
42
+ files,
43
+ metadata: generationMetadata,
44
+ };
45
+ }
46
+ /**
47
+ * Calculate generation metadata
48
+ */
49
+ calculateMetadata(generatorId, framework, entity, files) {
50
+ const filesGenerated = Object.keys(files).length;
51
+ const linesOfCode = Object.values(files).reduce((sum, content) => sum + content.split('\n').length, 0);
52
+ // Estimate token savings
53
+ // Average: 1 line of code ≈ 25 tokens
54
+ // AI would generate all lines from scratch
55
+ // With CodeDNA: ~150 tokens for the API call
56
+ const estimatedTokensSaved = Math.max(0, linesOfCode * 25 - 150);
57
+ return {
58
+ generator: generatorId,
59
+ framework,
60
+ entities: [entity.name],
61
+ filesGenerated,
62
+ linesOfCode,
63
+ estimatedTokensSaved,
64
+ };
65
+ }
66
+ /**
67
+ * Validate options against generator capabilities
68
+ */
69
+ async validateOptions(options) {
70
+ const generatorId = this.getGeneratorId();
71
+ const metadata = await this.registry.getGeneratorMetadata(generatorId);
72
+ // Validate database option
73
+ if (options.database && metadata.databases) {
74
+ if (!metadata.databases.includes(options.database)) {
75
+ throw new Error(`Unsupported database: ${options.database}. Supported: ${metadata.databases.join(', ')}`);
76
+ }
77
+ }
78
+ // Validate features
79
+ if (options.auth && !metadata.features.includes('auth')) {
80
+ throw new Error('This generator does not support authentication');
81
+ }
82
+ if (options.validation && !metadata.features.includes('validation')) {
83
+ throw new Error('This generator does not support validation');
84
+ }
85
+ if (options.tests && !metadata.features.includes('tests')) {
86
+ throw new Error('This generator does not support test generation');
87
+ }
88
+ }
89
+ /**
90
+ * Helper to get output path for entity file
91
+ */
92
+ getEntityPath(entity, directory, extension) {
93
+ const filename = entity.name.toLowerCase();
94
+ return `${directory}/${filename}.${extension}`;
95
+ }
96
+ /**
97
+ * Helper to check if option is enabled
98
+ */
99
+ isEnabled(options, key, defaultValue = false) {
100
+ return options[key] ?? defaultValue;
101
+ }
102
+ }
@@ -0,0 +1,12 @@
1
+ import { BaseGenerator } from './base.js';
2
+ import { EntitySpec } from '../parser.js';
3
+ import { GenerateApiOptions } from '../types.js';
4
+ export declare class ExpressApiGenerator extends BaseGenerator {
5
+ protected getGeneratorId(): string;
6
+ protected getRequiredTemplates(options: GenerateApiOptions): string[];
7
+ protected getFileMapping(entity: EntitySpec, options: GenerateApiOptions): Record<string, string>;
8
+ /**
9
+ * Generate Express API from entity specification
10
+ */
11
+ generate(entity: EntitySpec, options?: GenerateApiOptions): Promise<import('../types.js').GenerationResult>;
12
+ }
@@ -0,0 +1,61 @@
1
+ // =============================================================================
2
+ // Express API Generator
3
+ // =============================================================================
4
+ //
5
+ // Generate TypeScript Express REST API with CRUD operations, models,
6
+ // controllers, routes, middleware, and validators.
7
+ //
8
+ import { BaseGenerator } from './base.js';
9
+ export class ExpressApiGenerator extends BaseGenerator {
10
+ getGeneratorId() {
11
+ return 'express-api';
12
+ }
13
+ getRequiredTemplates(options) {
14
+ const templates = [
15
+ 'model.ts.j2',
16
+ 'controller.ts.j2',
17
+ 'route.ts.j2',
18
+ 'validator.ts.j2',
19
+ ];
20
+ if (this.isEnabled(options, 'auth')) {
21
+ templates.push('middleware.ts.j2');
22
+ }
23
+ if (this.isEnabled(options, 'tests')) {
24
+ templates.push('test.ts.j2');
25
+ }
26
+ return templates;
27
+ }
28
+ getFileMapping(entity, options) {
29
+ const entityLower = entity.name.toLowerCase();
30
+ const mapping = {
31
+ [`src/models/${entityLower}.model.ts`]: 'model.ts.j2',
32
+ [`src/controllers/${entityLower}.controller.ts`]: 'controller.ts.j2',
33
+ [`src/routes/${entityLower}.routes.ts`]: 'route.ts.j2',
34
+ [`src/validators/${entityLower}.validator.ts`]: 'validator.ts.j2',
35
+ };
36
+ if (this.isEnabled(options, 'auth')) {
37
+ mapping['src/middleware/auth.middleware.ts'] = 'middleware.ts.j2';
38
+ }
39
+ if (this.isEnabled(options, 'tests')) {
40
+ mapping[`src/__tests__/${entityLower}.test.ts`] = 'test.ts.j2';
41
+ }
42
+ return mapping;
43
+ }
44
+ /**
45
+ * Generate Express API from entity specification
46
+ */
47
+ async generate(entity, options = {}) {
48
+ // Validate options
49
+ await this.validateOptions(options);
50
+ // Set defaults
51
+ const opts = {
52
+ auth: false,
53
+ validation: true,
54
+ tests: false,
55
+ database: 'postgresql',
56
+ ...options,
57
+ };
58
+ // Generate files
59
+ return super.generate(entity, opts);
60
+ }
61
+ }
@@ -0,0 +1,4 @@
1
+ export * from './parser.js';
2
+ export * from './types.js';
3
+ export * from './template-engine.js';
4
+ export * from './registry.js';
@@ -0,0 +1,7 @@
1
+ // =============================================================================
2
+ // CodeDNA Module Exports
3
+ // =============================================================================
4
+ export * from './parser.js';
5
+ export * from './types.js';
6
+ export * from './template-engine.js';
7
+ export * from './registry.js';
@@ -0,0 +1,80 @@
1
+ export interface EntitySpec {
2
+ name: string;
3
+ fields: Field[];
4
+ }
5
+ export interface Field {
6
+ name: string;
7
+ type: FieldType;
8
+ constraints: Constraint[];
9
+ }
10
+ export type FieldType = {
11
+ kind: 'primitive';
12
+ value: 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime';
13
+ } | {
14
+ kind: 'reference';
15
+ entity: string;
16
+ } | {
17
+ kind: 'enum';
18
+ values: string[];
19
+ };
20
+ export type Constraint = {
21
+ kind: 'unique';
22
+ } | {
23
+ kind: 'required';
24
+ } | {
25
+ kind: 'hashed';
26
+ } | {
27
+ kind: 'index';
28
+ } | {
29
+ kind: 'min';
30
+ value: number;
31
+ } | {
32
+ kind: 'max';
33
+ value: number;
34
+ } | {
35
+ kind: 'default';
36
+ value: string;
37
+ };
38
+ export declare class EntityParser {
39
+ /**
40
+ * Parse Entity DSL specification into structured EntitySpec
41
+ */
42
+ parse(spec: string): EntitySpec;
43
+ /**
44
+ * Parse comma-separated field definitions
45
+ */
46
+ private parseFields;
47
+ /**
48
+ * Split fields by commas, respecting parentheses nesting
49
+ */
50
+ private splitFields;
51
+ /**
52
+ * Parse single field definition
53
+ * Format: name:type:constraint1:constraint2
54
+ */
55
+ private parseField;
56
+ /**
57
+ * Parse field type (primitive, reference, enum)
58
+ */
59
+ private parseType;
60
+ /**
61
+ * Parse field constraint (unique, required, hashed, min, max, default)
62
+ */
63
+ private parseConstraint;
64
+ }
65
+ /**
66
+ * Validate Entity DSL specification
67
+ */
68
+ export declare function validateSpec(spec: string): {
69
+ valid: boolean;
70
+ errors?: string[];
71
+ parsed?: EntitySpec;
72
+ };
73
+ /**
74
+ * Helper to check if field has specific constraint
75
+ */
76
+ export declare function hasConstraint(field: Field, kind: Constraint['kind']): boolean;
77
+ /**
78
+ * Helper to get constraint value
79
+ */
80
+ export declare function getConstraintValue<T extends Constraint>(field: Field, kind: T['kind']): T | undefined;
@@ -0,0 +1,176 @@
1
+ // =============================================================================
2
+ // Entity DSL Parser
3
+ // =============================================================================
4
+ //
5
+ // Parses compact Entity DSL specifications into structured data for code generation.
6
+ //
7
+ // Syntax: EntityName(field:type:constraint, field:type:constraint)
8
+ //
9
+ // Example: User(email:string:unique:required, password:string:hashed, age:integer:min(18))
10
+ //
11
+ export class EntityParser {
12
+ /**
13
+ * Parse Entity DSL specification into structured EntitySpec
14
+ */
15
+ parse(spec) {
16
+ // Remove whitespace
17
+ spec = spec.trim();
18
+ // Extract entity name
19
+ const nameMatch = spec.match(/^([A-Z][a-zA-Z0-9]*)\(/);
20
+ if (!nameMatch) {
21
+ throw new Error('Invalid entity spec: must start with EntityName(');
22
+ }
23
+ const name = nameMatch[1];
24
+ // Extract fields section
25
+ const fieldsMatch = spec.match(/\((.+)\)$/);
26
+ if (!fieldsMatch) {
27
+ throw new Error('Invalid entity spec: missing closing parenthesis');
28
+ }
29
+ const fieldsStr = fieldsMatch[1];
30
+ const fields = this.parseFields(fieldsStr);
31
+ return { name, fields };
32
+ }
33
+ /**
34
+ * Parse comma-separated field definitions
35
+ */
36
+ parseFields(fieldsStr) {
37
+ // Split by commas (but not inside parentheses)
38
+ const fieldStrs = this.splitFields(fieldsStr);
39
+ return fieldStrs.map(fieldStr => this.parseField(fieldStr.trim()));
40
+ }
41
+ /**
42
+ * Split fields by commas, respecting parentheses nesting
43
+ */
44
+ splitFields(str) {
45
+ const fields = [];
46
+ let current = '';
47
+ let depth = 0;
48
+ for (let i = 0; i < str.length; i++) {
49
+ const char = str[i];
50
+ if (char === '(')
51
+ depth++;
52
+ if (char === ')')
53
+ depth--;
54
+ if (char === ',' && depth === 0) {
55
+ fields.push(current);
56
+ current = '';
57
+ }
58
+ else {
59
+ current += char;
60
+ }
61
+ }
62
+ if (current)
63
+ fields.push(current);
64
+ return fields;
65
+ }
66
+ /**
67
+ * Parse single field definition
68
+ * Format: name:type:constraint1:constraint2
69
+ */
70
+ parseField(fieldStr) {
71
+ const parts = fieldStr.split(':');
72
+ if (parts.length < 2) {
73
+ throw new Error(`Invalid field spec: ${fieldStr}`);
74
+ }
75
+ const name = parts[0].trim();
76
+ const typeStr = parts[1].trim();
77
+ const constraintStrs = parts.slice(2).map(s => s.trim());
78
+ // Validate field name
79
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
80
+ throw new Error(`Invalid field name: ${name}`);
81
+ }
82
+ const type = this.parseType(typeStr);
83
+ const constraints = constraintStrs.map(c => this.parseConstraint(c));
84
+ return { name, type, constraints };
85
+ }
86
+ /**
87
+ * Parse field type (primitive, reference, enum)
88
+ */
89
+ parseType(typeStr) {
90
+ // Check for reference: ref(EntityName)
91
+ const refMatch = typeStr.match(/^ref\(([A-Z][a-zA-Z0-9]*)\)$/);
92
+ if (refMatch) {
93
+ return { kind: 'reference', entity: refMatch[1] };
94
+ }
95
+ // Check for enum: enum(val1,val2,val3)
96
+ const enumMatch = typeStr.match(/^enum\((.+)\)$/);
97
+ if (enumMatch) {
98
+ const values = enumMatch[1].split(',').map(v => v.trim());
99
+ if (values.length === 0) {
100
+ throw new Error('Enum must have at least one value');
101
+ }
102
+ return { kind: 'enum', values };
103
+ }
104
+ // Primitive types
105
+ const primitives = ['string', 'integer', 'decimal', 'boolean', 'datetime'];
106
+ if (primitives.includes(typeStr)) {
107
+ return { kind: 'primitive', value: typeStr };
108
+ }
109
+ throw new Error(`Unknown field type: ${typeStr}`);
110
+ }
111
+ /**
112
+ * Parse field constraint (unique, required, hashed, min, max, default)
113
+ */
114
+ parseConstraint(constraintStr) {
115
+ // Simple constraints
116
+ if (constraintStr === 'unique')
117
+ return { kind: 'unique' };
118
+ if (constraintStr === 'required')
119
+ return { kind: 'required' };
120
+ if (constraintStr === 'hashed')
121
+ return { kind: 'hashed' };
122
+ if (constraintStr === 'index')
123
+ return { kind: 'index' };
124
+ // Parameterized constraints: min(18), max(100), default(true)
125
+ const paramMatch = constraintStr.match(/^([a-z]+)\((.+)\)$/);
126
+ if (paramMatch) {
127
+ const [, kind, value] = paramMatch;
128
+ if (kind === 'min') {
129
+ const numValue = Number(value);
130
+ if (isNaN(numValue)) {
131
+ throw new Error(`Invalid min constraint value: ${value}`);
132
+ }
133
+ return { kind: 'min', value: numValue };
134
+ }
135
+ if (kind === 'max') {
136
+ const numValue = Number(value);
137
+ if (isNaN(numValue)) {
138
+ throw new Error(`Invalid max constraint value: ${value}`);
139
+ }
140
+ return { kind: 'max', value: numValue };
141
+ }
142
+ if (kind === 'default') {
143
+ return { kind: 'default', value };
144
+ }
145
+ }
146
+ throw new Error(`Unknown constraint: ${constraintStr}`);
147
+ }
148
+ }
149
+ /**
150
+ * Validate Entity DSL specification
151
+ */
152
+ export function validateSpec(spec) {
153
+ try {
154
+ const parser = new EntityParser();
155
+ const parsed = parser.parse(spec);
156
+ return { valid: true, parsed };
157
+ }
158
+ catch (error) {
159
+ return {
160
+ valid: false,
161
+ errors: [error instanceof Error ? error.message : String(error)],
162
+ };
163
+ }
164
+ }
165
+ /**
166
+ * Helper to check if field has specific constraint
167
+ */
168
+ export function hasConstraint(field, kind) {
169
+ return field.constraints.some(c => c.kind === kind);
170
+ }
171
+ /**
172
+ * Helper to get constraint value
173
+ */
174
+ export function getConstraintValue(field, kind) {
175
+ return field.constraints.find(c => c.kind === kind);
176
+ }
@@ -0,0 +1,60 @@
1
+ import { GeneratorMetadata } from './types.js';
2
+ export interface RegistryResponse {
3
+ generators: GeneratorMetadata[];
4
+ version: string;
5
+ updated: string;
6
+ }
7
+ export declare class TemplateRegistry {
8
+ private baseUrl;
9
+ private cacheDir;
10
+ private useCache;
11
+ constructor(baseUrl?: string, useCache?: boolean);
12
+ /**
13
+ * List all available generators
14
+ */
15
+ listGenerators(): Promise<GeneratorMetadata[]>;
16
+ /**
17
+ * Get metadata for specific generator
18
+ */
19
+ getGeneratorMetadata(generatorId: string): Promise<GeneratorMetadata>;
20
+ /**
21
+ * Get template file content
22
+ */
23
+ getTemplate(generatorId: string, templateFile: string): Promise<string>;
24
+ /**
25
+ * Get multiple templates at once
26
+ */
27
+ getTemplates(generatorId: string, templateFiles: string[]): Promise<Record<string, string>>;
28
+ /**
29
+ * Check if template is cached locally
30
+ */
31
+ private getCachedTemplate;
32
+ /**
33
+ * Cache template locally
34
+ */
35
+ private cacheTemplate;
36
+ /**
37
+ * Get local generators (fallback)
38
+ */
39
+ private getLocalGenerators;
40
+ /**
41
+ * Get local metadata (fallback)
42
+ */
43
+ private getLocalMetadata;
44
+ /**
45
+ * Get local template (fallback)
46
+ */
47
+ private getLocalTemplate;
48
+ /**
49
+ * Clear local cache
50
+ */
51
+ clearCache(): Promise<void>;
52
+ /**
53
+ * Get cache statistics
54
+ */
55
+ getCacheStats(): Promise<{
56
+ cachedGenerators: string[];
57
+ totalFiles: number;
58
+ totalSize: number;
59
+ }>;
60
+ }