@claudetools/tools 0.3.9 → 0.5.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.
Files changed (47) hide show
  1. package/README.md +60 -4
  2. package/dist/cli.js +0 -0
  3. package/dist/codedna/generators/base.d.ts +41 -0
  4. package/dist/codedna/generators/base.js +102 -0
  5. package/dist/codedna/generators/express-api.d.ts +12 -0
  6. package/dist/codedna/generators/express-api.js +61 -0
  7. package/dist/codedna/index.d.ts +4 -0
  8. package/dist/codedna/index.js +7 -0
  9. package/dist/codedna/parser.d.ts +117 -0
  10. package/dist/codedna/parser.js +233 -0
  11. package/dist/codedna/registry.d.ts +60 -0
  12. package/dist/codedna/registry.js +217 -0
  13. package/dist/codedna/template-engine.d.ts +17 -0
  14. package/dist/codedna/template-engine.js +183 -0
  15. package/dist/codedna/types.d.ts +64 -0
  16. package/dist/codedna/types.js +4 -0
  17. package/dist/handlers/codedna-handlers.d.ts +122 -0
  18. package/dist/handlers/codedna-handlers.js +194 -0
  19. package/dist/handlers/tool-handlers.js +593 -14
  20. package/dist/helpers/api-client.d.ts +37 -0
  21. package/dist/helpers/api-client.js +70 -0
  22. package/dist/helpers/codedna-monitoring.d.ts +34 -0
  23. package/dist/helpers/codedna-monitoring.js +159 -0
  24. package/dist/helpers/error-tracking.d.ts +73 -0
  25. package/dist/helpers/error-tracking.js +164 -0
  26. package/dist/helpers/library-detection.d.ts +26 -0
  27. package/dist/helpers/library-detection.js +145 -0
  28. package/dist/helpers/tasks-retry.d.ts +49 -0
  29. package/dist/helpers/tasks-retry.js +168 -0
  30. package/dist/helpers/tasks.d.ts +24 -1
  31. package/dist/helpers/tasks.js +146 -50
  32. package/dist/helpers/usage-analytics.d.ts +91 -0
  33. package/dist/helpers/usage-analytics.js +256 -0
  34. package/dist/helpers/workers.d.ts +25 -0
  35. package/dist/helpers/workers.js +80 -0
  36. package/dist/templates/claude-md.d.ts +1 -1
  37. package/dist/templates/claude-md.js +16 -5
  38. package/dist/tools.js +314 -0
  39. package/docs/AUTO-REGISTRATION.md +353 -0
  40. package/docs/CLAUDE4_PROMPT_ANALYSIS.md +589 -0
  41. package/docs/ENTITY_DSL_REFERENCE.md +685 -0
  42. package/docs/MODERN_STACK_COMPLETE_GUIDE.md +706 -0
  43. package/docs/PROMPT_STANDARDIZATION_RESULTS.md +324 -0
  44. package/docs/PROMPT_TIER_TEMPLATES.md +787 -0
  45. package/docs/RESEARCH_METHODOLOGY_EXTRACTION.md +336 -0
  46. package/package.json +14 -3
  47. package/scripts/verify-prompt-compliance.sh +197 -0
package/README.md CHANGED
@@ -67,19 +67,75 @@ CLAUDETOOLS_API_URL=https://api.claudetools.dev
67
67
  - **Impact analysis** for changes
68
68
  - **Pattern detection** (security, performance)
69
69
 
70
+ ### CodeDNA (AI Code Generation)
71
+ - **95-99% token savings** by generating production code from compact Entity DSL specs
72
+ - **Production-ready APIs** with models, controllers, routes, validation, tests
73
+ - **Express.js generator** available (FastAPI, NestJS, React coming soon)
74
+ - **Template registry** hosted on ClaudeTools API with global CDN
75
+
76
+ ### 10/10 Prompt Engineering Framework
77
+ - **Production-proven architecture** from Claude 4 analysis (~60K char prompt)
78
+ - **4 complexity tiers** (Minimal/Standard/Professional/Enterprise) with progressive disclosure
79
+ - **7 semantic layers** with XML boundaries for machine-parseability
80
+ - **Token-optimized templates** (500t → 10000t based on complexity)
81
+ - **Compliance verification** with automated validation script
82
+
83
+ #### Quick Example
84
+
85
+ ```typescript
86
+ // Instead of AI writing 1000 lines of code (30,000 tokens)...
87
+ // Use a compact Entity DSL spec (150 tokens):
88
+
89
+ codedna_generate_api({
90
+ spec: "User(email:string:unique, password:string:hashed, age:integer:min(18))",
91
+ framework: "express",
92
+ options: {
93
+ auth: true,
94
+ validation: true,
95
+ tests: true,
96
+ database: "postgresql"
97
+ }
98
+ })
99
+
100
+ // Returns 6 complete files:
101
+ // - src/models/user.model.ts
102
+ // - src/controllers/user.controller.ts
103
+ // - src/routes/user.routes.ts
104
+ // - src/validators/user.validator.ts
105
+ // - src/middleware/auth.middleware.ts
106
+ // - tests/user.test.ts
107
+
108
+ // Token savings: 11,100 tokens (98.7%)
109
+ // Cost savings: $0.11 per generation
110
+ // Time savings: 2-3 minutes → 10 seconds
111
+ ```
112
+
113
+ #### Available Tools
114
+
115
+ - `codedna_generate_api(spec, framework, options)` - Generate complete REST API
116
+ - `codedna_validate_spec(spec)` - Validate Entity DSL syntax
117
+ - `codedna_list_generators()` - List available code generators
118
+
119
+ See [CODEDNA_README.md](./CODEDNA_README.md) for full documentation.
120
+
70
121
  ## CLI
71
122
 
72
123
  ```bash
73
- claudetools --setup # Interactive configuration
74
- claudetools --version # Show version
75
- claudetools --help # Show help
76
- claudetools # Start MCP server
124
+ claudetools --setup # Interactive configuration
125
+ claudetools --version # Show version
126
+ claudetools --help # Show help
127
+ claudetools # Start MCP server
128
+ npm run prompt:verify # Verify prompt compliance with 10/10 framework
77
129
  ```
78
130
 
79
131
  ## Documentation
80
132
 
81
133
  - [GitHub](https://github.com/claudetools/memory)
82
134
  - [Configuration Guide](./CONFIG.md)
135
+ - [CodeDNA Guide](./CODEDNA_README.md) - AI code generation with 95-99% token savings
136
+ - [10/10 Prompt Framework](./docs/PROMPT_TIER_TEMPLATES.md) - Production-grade prompt engineering
137
+ - [Claude 4 Analysis](./docs/CLAUDE4_PROMPT_ANALYSIS.md) - Insights from ~60K char production prompt
138
+ - [Research Methodology](./docs/RESEARCH_METHODOLOGY_EXTRACTION.md) - Claude.ai Desktop research patterns
83
139
 
84
140
  ## License
85
141
 
package/dist/cli.js CHANGED
File without changes
@@ -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,117 @@
1
+ export interface EntitySpec {
2
+ name: string;
3
+ fields: Field[];
4
+ hooks?: LifecycleHooks;
5
+ permissions?: Permission[];
6
+ }
7
+ export interface LifecycleHooks {
8
+ beforeCreate?: string[];
9
+ afterCreate?: string[];
10
+ beforeUpdate?: string[];
11
+ afterUpdate?: string[];
12
+ beforeDelete?: string[];
13
+ afterDelete?: string[];
14
+ }
15
+ export interface Permission {
16
+ action: 'create' | 'read' | 'update' | 'delete';
17
+ roles?: string[];
18
+ condition?: string;
19
+ }
20
+ export interface Field {
21
+ name: string;
22
+ type: FieldType;
23
+ constraints: Constraint[];
24
+ }
25
+ export type FieldType = {
26
+ kind: 'primitive';
27
+ value: 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime' | 'email' | 'url' | 'json';
28
+ } | {
29
+ kind: 'array';
30
+ itemType: 'string' | 'integer' | 'decimal' | 'boolean';
31
+ } | {
32
+ kind: 'reference';
33
+ entity: string;
34
+ relation?: 'oneToMany' | 'manyToMany';
35
+ } | {
36
+ kind: 'enum';
37
+ values: string[];
38
+ } | {
39
+ kind: 'computed';
40
+ expression: string;
41
+ };
42
+ export type Constraint = {
43
+ kind: 'unique';
44
+ } | {
45
+ kind: 'required';
46
+ } | {
47
+ kind: 'hashed';
48
+ } | {
49
+ kind: 'index';
50
+ } | {
51
+ kind: 'min';
52
+ value: number;
53
+ } | {
54
+ kind: 'max';
55
+ value: number;
56
+ } | {
57
+ kind: 'default';
58
+ value: string;
59
+ } | {
60
+ kind: 'length';
61
+ min?: number;
62
+ max?: number;
63
+ } | {
64
+ kind: 'pattern';
65
+ regex: string;
66
+ } | {
67
+ kind: 'email';
68
+ } | {
69
+ kind: 'url';
70
+ } | {
71
+ kind: 'nullable';
72
+ } | {
73
+ kind: 'immutable';
74
+ };
75
+ export declare class EntityParser {
76
+ /**
77
+ * Parse Entity DSL specification into structured EntitySpec
78
+ */
79
+ parse(spec: string): EntitySpec;
80
+ /**
81
+ * Parse comma-separated field definitions
82
+ */
83
+ private parseFields;
84
+ /**
85
+ * Split fields by commas, respecting parentheses nesting
86
+ */
87
+ private splitFields;
88
+ /**
89
+ * Parse single field definition
90
+ * Format: name:type:constraint1:constraint2
91
+ */
92
+ private parseField;
93
+ /**
94
+ * Parse field type (primitive, reference, enum, array, computed)
95
+ */
96
+ private parseType;
97
+ /**
98
+ * Parse field constraint (unique, required, hashed, min, max, default, length, pattern, email, url, nullable, immutable)
99
+ */
100
+ private parseConstraint;
101
+ }
102
+ /**
103
+ * Validate Entity DSL specification
104
+ */
105
+ export declare function validateSpec(spec: string): {
106
+ valid: boolean;
107
+ errors?: string[];
108
+ parsed?: EntitySpec;
109
+ };
110
+ /**
111
+ * Helper to check if field has specific constraint
112
+ */
113
+ export declare function hasConstraint(field: Field, kind: Constraint['kind']): boolean;
114
+ /**
115
+ * Helper to get constraint value
116
+ */
117
+ export declare function getConstraintValue<T extends Constraint>(field: Field, kind: T['kind']): T | undefined;
@@ -0,0 +1,233 @@
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 = fieldsStr.trim() ? 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, array, computed)
88
+ */
89
+ parseType(typeStr) {
90
+ // Check for reference with relation: refMany(EntityName) or refOne(EntityName)
91
+ // oneToMany = entity has many of this type (e.g., User has many Posts)
92
+ // manyToMany = entities have many-to-many relationship
93
+ const refManyMatch = typeStr.match(/^refMany\(([A-Z][a-zA-Z0-9]*)\)$/);
94
+ if (refManyMatch) {
95
+ return { kind: 'reference', entity: refManyMatch[1], relation: 'manyToMany' };
96
+ }
97
+ const refOneMatch = typeStr.match(/^refOne\(([A-Z][a-zA-Z0-9]*)\)$/);
98
+ if (refOneMatch) {
99
+ return { kind: 'reference', entity: refOneMatch[1], relation: 'oneToMany' };
100
+ }
101
+ // Check for reference: ref(EntityName) - default simple reference
102
+ const refMatch = typeStr.match(/^ref\(([A-Z][a-zA-Z0-9]*)\)$/);
103
+ if (refMatch) {
104
+ return { kind: 'reference', entity: refMatch[1] };
105
+ }
106
+ // Check for array: array(string), array(integer), etc.
107
+ const arrayMatch = typeStr.match(/^array\((string|integer|decimal|boolean)\)$/);
108
+ if (arrayMatch) {
109
+ const itemType = arrayMatch[1];
110
+ return { kind: 'array', itemType };
111
+ }
112
+ // Check for computed: computed(expression)
113
+ const computedMatch = typeStr.match(/^computed\((.+)\)$/);
114
+ if (computedMatch) {
115
+ return { kind: 'computed', expression: computedMatch[1] };
116
+ }
117
+ // Check for enum: enum(val1,val2,val3)
118
+ const enumMatch = typeStr.match(/^enum\((.+)\)$/);
119
+ if (enumMatch) {
120
+ const values = enumMatch[1].split(',').map(v => v.trim());
121
+ if (values.length === 0) {
122
+ throw new Error('Enum must have at least one value');
123
+ }
124
+ return { kind: 'enum', values };
125
+ }
126
+ // Primitive types (including new email, url, json)
127
+ const primitives = ['string', 'integer', 'decimal', 'boolean', 'datetime', 'email', 'url', 'json'];
128
+ if (primitives.includes(typeStr)) {
129
+ return { kind: 'primitive', value: typeStr };
130
+ }
131
+ throw new Error(`Unknown field type: ${typeStr}`);
132
+ }
133
+ /**
134
+ * Parse field constraint (unique, required, hashed, min, max, default, length, pattern, email, url, nullable, immutable)
135
+ */
136
+ parseConstraint(constraintStr) {
137
+ // Simple constraints
138
+ if (constraintStr === 'unique')
139
+ return { kind: 'unique' };
140
+ if (constraintStr === 'required')
141
+ return { kind: 'required' };
142
+ if (constraintStr === 'hashed')
143
+ return { kind: 'hashed' };
144
+ if (constraintStr === 'index')
145
+ return { kind: 'index' };
146
+ if (constraintStr === 'email')
147
+ return { kind: 'email' };
148
+ if (constraintStr === 'url')
149
+ return { kind: 'url' };
150
+ if (constraintStr === 'nullable')
151
+ return { kind: 'nullable' };
152
+ if (constraintStr === 'immutable')
153
+ return { kind: 'immutable' };
154
+ // Parameterized constraints: min(18), max(100), default(true), length(10,100), pattern(/regex/)
155
+ const paramMatch = constraintStr.match(/^([a-z]+)\((.+)\)$/);
156
+ if (paramMatch) {
157
+ const [, kind, value] = paramMatch;
158
+ if (kind === 'min') {
159
+ const numValue = Number(value);
160
+ if (isNaN(numValue)) {
161
+ throw new Error(`Invalid min constraint value: ${value}`);
162
+ }
163
+ return { kind: 'min', value: numValue };
164
+ }
165
+ if (kind === 'max') {
166
+ const numValue = Number(value);
167
+ if (isNaN(numValue)) {
168
+ throw new Error(`Invalid max constraint value: ${value}`);
169
+ }
170
+ return { kind: 'max', value: numValue };
171
+ }
172
+ if (kind === 'default') {
173
+ return { kind: 'default', value };
174
+ }
175
+ if (kind === 'length') {
176
+ // Support both length(max) and length(min,max)
177
+ const parts = value.split(',').map(v => v.trim());
178
+ if (parts.length === 1) {
179
+ const maxValue = Number(parts[0]);
180
+ if (isNaN(maxValue)) {
181
+ throw new Error(`Invalid length constraint value: ${parts[0]}`);
182
+ }
183
+ return { kind: 'length', max: maxValue };
184
+ }
185
+ else if (parts.length === 2) {
186
+ const minValue = Number(parts[0]);
187
+ const maxValue = Number(parts[1]);
188
+ if (isNaN(minValue) || isNaN(maxValue)) {
189
+ throw new Error(`Invalid length constraint values: ${value}`);
190
+ }
191
+ return { kind: 'length', min: minValue, max: maxValue };
192
+ }
193
+ else {
194
+ throw new Error(`Invalid length constraint format: ${value}`);
195
+ }
196
+ }
197
+ if (kind === 'pattern') {
198
+ // Remove surrounding slashes if present
199
+ const regex = value.replace(/^\/(.+)\/$/, '$1');
200
+ return { kind: 'pattern', regex };
201
+ }
202
+ }
203
+ throw new Error(`Unknown constraint: ${constraintStr}`);
204
+ }
205
+ }
206
+ /**
207
+ * Validate Entity DSL specification
208
+ */
209
+ export function validateSpec(spec) {
210
+ try {
211
+ const parser = new EntityParser();
212
+ const parsed = parser.parse(spec);
213
+ return { valid: true, parsed };
214
+ }
215
+ catch (error) {
216
+ return {
217
+ valid: false,
218
+ errors: [error instanceof Error ? error.message : String(error)],
219
+ };
220
+ }
221
+ }
222
+ /**
223
+ * Helper to check if field has specific constraint
224
+ */
225
+ export function hasConstraint(field, kind) {
226
+ return field.constraints.some(c => c.kind === kind);
227
+ }
228
+ /**
229
+ * Helper to get constraint value
230
+ */
231
+ export function getConstraintValue(field, kind) {
232
+ return field.constraints.find(c => c.kind === kind);
233
+ }