@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.
- package/dist/codedna/generators/base.d.ts +41 -0
- package/dist/codedna/generators/base.js +102 -0
- package/dist/codedna/generators/express-api.d.ts +12 -0
- package/dist/codedna/generators/express-api.js +61 -0
- package/dist/codedna/index.d.ts +4 -0
- package/dist/codedna/index.js +7 -0
- package/dist/codedna/parser.d.ts +80 -0
- package/dist/codedna/parser.js +176 -0
- package/dist/codedna/registry.d.ts +60 -0
- package/dist/codedna/registry.js +214 -0
- package/dist/codedna/template-engine.d.ts +17 -0
- package/dist/codedna/template-engine.js +149 -0
- package/dist/codedna/types.d.ts +64 -0
- package/dist/codedna/types.js +4 -0
- package/dist/handlers/codedna-handlers.d.ts +122 -0
- package/dist/handlers/codedna-handlers.js +167 -0
- package/dist/handlers/tool-handlers.js +593 -14
- package/dist/helpers/api-client.d.ts +37 -0
- package/dist/helpers/api-client.js +63 -0
- package/dist/helpers/library-detection.d.ts +26 -0
- package/dist/helpers/library-detection.js +145 -0
- package/dist/helpers/tasks-retry.d.ts +49 -0
- package/dist/helpers/tasks-retry.js +168 -0
- package/dist/helpers/tasks.d.ts +24 -1
- package/dist/helpers/tasks.js +146 -50
- package/dist/helpers/workers.d.ts +25 -0
- package/dist/helpers/workers.js +80 -0
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +16 -5
- package/dist/tools.js +314 -0
- package/package.json +3 -1
|
@@ -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,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
|
+
}
|