@claudetools/tools 0.8.10 → 0.9.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/astro.d.ts +18 -0
- package/dist/codedna/generators/astro.js +91 -0
- package/dist/codedna/generators/authjs.d.ts +18 -0
- package/dist/codedna/generators/authjs.js +68 -0
- package/dist/codedna/generators/better-auth.d.ts +18 -0
- package/dist/codedna/generators/better-auth.js +62 -0
- package/dist/codedna/generators/drizzle-orm.d.ts +18 -0
- package/dist/codedna/generators/drizzle-orm.js +65 -0
- package/dist/codedna/generators/elysia-api.d.ts +12 -0
- package/dist/codedna/generators/elysia-api.js +64 -0
- package/dist/codedna/generators/hono-api.d.ts +12 -0
- package/dist/codedna/generators/hono-api.js +64 -0
- package/dist/codedna/generators/lucia-auth.d.ts +18 -0
- package/dist/codedna/generators/lucia-auth.js +69 -0
- package/dist/codedna/generators/prisma.d.ts +18 -0
- package/dist/codedna/generators/prisma.js +64 -0
- package/dist/codedna/generators/react-router-v7.d.ts +18 -0
- package/dist/codedna/generators/react-router-v7.js +77 -0
- package/dist/codedna/generators/react19-shadcn.d.ts +21 -0
- package/dist/codedna/generators/react19-shadcn.js +367 -0
- package/dist/codedna/generators/sveltekit.d.ts +18 -0
- package/dist/codedna/generators/sveltekit.js +73 -0
- package/dist/codedna/generators/tanstack-start-drizzle.d.ts +92 -0
- package/dist/codedna/generators/tanstack-start-drizzle.js +824 -0
- package/dist/codedna/generators/trpc-api.d.ts +12 -0
- package/dist/codedna/generators/trpc-api.js +64 -0
- package/dist/codedna/index.d.ts +31 -0
- package/dist/codedna/index.js +39 -0
- package/dist/codedna/kappa-api-generator.d.ts +89 -0
- package/dist/codedna/kappa-api-generator.js +493 -0
- package/dist/codedna/kappa-ast.d.ts +552 -0
- package/dist/codedna/kappa-ast.js +141 -0
- package/dist/codedna/kappa-cli.d.ts +2 -0
- package/dist/codedna/kappa-cli.js +302 -0
- package/dist/codedna/kappa-component-generator.d.ts +47 -0
- package/dist/codedna/kappa-component-generator.js +295 -0
- package/dist/codedna/kappa-design-generator.d.ts +52 -0
- package/dist/codedna/kappa-design-generator.js +365 -0
- package/dist/codedna/kappa-drizzle-generator.d.ts +45 -0
- package/dist/codedna/kappa-drizzle-generator.js +355 -0
- package/dist/codedna/kappa-form-generator.d.ts +51 -0
- package/dist/codedna/kappa-form-generator.js +319 -0
- package/dist/codedna/kappa-lexer.d.ts +268 -0
- package/dist/codedna/kappa-lexer.js +757 -0
- package/dist/codedna/kappa-page-generator.d.ts +57 -0
- package/dist/codedna/kappa-page-generator.js +338 -0
- package/dist/codedna/kappa-parser.d.ts +261 -0
- package/dist/codedna/kappa-parser.js +2547 -0
- package/dist/codedna/kappa-provenance.d.ts +101 -0
- package/dist/codedna/kappa-provenance.js +199 -0
- package/dist/codedna/kappa-types-generator.d.ts +37 -0
- package/dist/codedna/kappa-types-generator.js +159 -0
- package/dist/codedna/kappa-validator.d.ts +86 -0
- package/dist/codedna/kappa-validator.js +638 -0
- package/dist/codedna/kappa-zod-generator.d.ts +32 -0
- package/dist/codedna/kappa-zod-generator.js +216 -0
- package/dist/handlers/codedna-handlers.d.ts +1 -1
- package/dist/handlers/kappa-handlers.d.ts +116 -0
- package/dist/handlers/kappa-handlers.js +465 -0
- package/dist/handlers/tool-handlers.js +121 -0
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +166 -9
- package/dist/tools.js +199 -0
- package/docs/research/2026-01-02-codedna-il-specification.md +639 -0
- package/docs/research/2026-01-02-codedna-v2-research.md +943 -0
- package/docs/research/2026-01-02-computation-foundations.md +564 -0
- package/docs/research/2026-01-02-hardware-description.md +814 -0
- package/docs/research/2026-01-02-kappa-specification.md +697 -0
- package/docs/research/2026-01-02-kappa-tanstack-example.md +527 -0
- package/docs/research/2026-01-02-kappa-v2-synthesis.md +406 -0
- package/docs/research/2026-01-02-kappa-v2.5-specification.md +1218 -0
- package/docs/research/2026-01-02-kappa-v3-specification.md +1864 -0
- package/docs/research/2026-01-02-kappa-whitepaper.md +662 -0
- package/docs/research/2026-01-02-logic-constraint.md +731 -0
- package/docs/research/2026-01-02-quantum-computation.md +635 -0
- package/package.json +4 -2
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Prisma Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generate Prisma schema, client extensions, and typed queries.
|
|
6
|
+
// Industry-standard ORM with excellent DX and type safety.
|
|
7
|
+
//
|
|
8
|
+
import { BaseGenerator } from './base.js';
|
|
9
|
+
export class PrismaGenerator extends BaseGenerator {
|
|
10
|
+
getGeneratorId() {
|
|
11
|
+
return 'prisma';
|
|
12
|
+
}
|
|
13
|
+
getRequiredTemplates(options) {
|
|
14
|
+
const templates = [
|
|
15
|
+
'model.prisma.j2',
|
|
16
|
+
'types.ts.j2',
|
|
17
|
+
];
|
|
18
|
+
if (options.clientExtensions) {
|
|
19
|
+
templates.push('client.ts.j2');
|
|
20
|
+
}
|
|
21
|
+
// Service layer with typed queries
|
|
22
|
+
templates.push('service.ts.j2');
|
|
23
|
+
if (options.seedFile) {
|
|
24
|
+
templates.push('seed.ts.j2');
|
|
25
|
+
}
|
|
26
|
+
if (this.isEnabled(options, 'tests')) {
|
|
27
|
+
templates.push('test.ts.j2');
|
|
28
|
+
}
|
|
29
|
+
return templates;
|
|
30
|
+
}
|
|
31
|
+
getFileMapping(entity, options) {
|
|
32
|
+
const entityLower = entity.name.toLowerCase();
|
|
33
|
+
const mapping = {
|
|
34
|
+
[`prisma/models/${entityLower}.prisma`]: 'model.prisma.j2',
|
|
35
|
+
[`src/types/${entityLower}.ts`]: 'types.ts.j2',
|
|
36
|
+
[`src/services/${entityLower}.service.ts`]: 'service.ts.j2',
|
|
37
|
+
};
|
|
38
|
+
if (options.clientExtensions) {
|
|
39
|
+
mapping['src/lib/prisma.ts'] = 'client.ts.j2';
|
|
40
|
+
}
|
|
41
|
+
if (options.seedFile) {
|
|
42
|
+
mapping['prisma/seed.ts'] = 'seed.ts.j2';
|
|
43
|
+
}
|
|
44
|
+
if (this.isEnabled(options, 'tests')) {
|
|
45
|
+
mapping[`src/services/__tests__/${entityLower}.test.ts`] = 'test.ts.j2';
|
|
46
|
+
}
|
|
47
|
+
return mapping;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generate Prisma schema from entity specification
|
|
51
|
+
*/
|
|
52
|
+
async generate(entity, options = {}) {
|
|
53
|
+
// Set defaults - Prisma specific
|
|
54
|
+
const opts = {
|
|
55
|
+
datasource: 'postgresql',
|
|
56
|
+
clientExtensions: true,
|
|
57
|
+
seedFile: false,
|
|
58
|
+
database: 'postgresql',
|
|
59
|
+
...options,
|
|
60
|
+
};
|
|
61
|
+
// Generate files
|
|
62
|
+
return super.generate(entity, opts);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseGenerator } from './base.js';
|
|
2
|
+
import { EntitySpec } from '../parser.js';
|
|
3
|
+
import { GenerateFrontendOptions } from '../types.js';
|
|
4
|
+
interface ReactRouterV7Options extends GenerateFrontendOptions {
|
|
5
|
+
loaders?: boolean;
|
|
6
|
+
actions?: boolean;
|
|
7
|
+
streaming?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class ReactRouterV7Generator extends BaseGenerator {
|
|
10
|
+
protected getGeneratorId(): string;
|
|
11
|
+
protected getRequiredTemplates(options: ReactRouterV7Options): string[];
|
|
12
|
+
protected getFileMapping(entity: EntitySpec, options: ReactRouterV7Options): Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Generate React Router v7 routes from entity specification
|
|
15
|
+
*/
|
|
16
|
+
generate(entity: EntitySpec, options?: ReactRouterV7Options): Promise<import('../types.js').GenerationResult>;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// React Router v7 Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generate React Router v7 routes with loaders, actions, and type-safe params.
|
|
6
|
+
// Supports the new framework mode with server-side rendering.
|
|
7
|
+
//
|
|
8
|
+
import { BaseGenerator } from './base.js';
|
|
9
|
+
export class ReactRouterV7Generator extends BaseGenerator {
|
|
10
|
+
getGeneratorId() {
|
|
11
|
+
return 'react-router-v7';
|
|
12
|
+
}
|
|
13
|
+
getRequiredTemplates(options) {
|
|
14
|
+
const templates = [
|
|
15
|
+
'route.tsx.j2',
|
|
16
|
+
'types.ts.j2',
|
|
17
|
+
'schema.ts.j2',
|
|
18
|
+
];
|
|
19
|
+
if (options.loaders !== false) {
|
|
20
|
+
templates.push('loader.ts.j2');
|
|
21
|
+
}
|
|
22
|
+
if (options.actions !== false) {
|
|
23
|
+
templates.push('action.ts.j2');
|
|
24
|
+
}
|
|
25
|
+
if (this.isEnabled(options, 'forms', true)) {
|
|
26
|
+
templates.push('form.tsx.j2');
|
|
27
|
+
}
|
|
28
|
+
if (this.isEnabled(options, 'tables', true)) {
|
|
29
|
+
templates.push('table.tsx.j2');
|
|
30
|
+
}
|
|
31
|
+
if (this.isEnabled(options, 'tests')) {
|
|
32
|
+
templates.push('test.tsx.j2');
|
|
33
|
+
}
|
|
34
|
+
return templates;
|
|
35
|
+
}
|
|
36
|
+
getFileMapping(entity, options) {
|
|
37
|
+
const entityLower = entity.name.toLowerCase();
|
|
38
|
+
const entityPlural = entityLower + 's';
|
|
39
|
+
const mapping = {
|
|
40
|
+
[`app/routes/${entityPlural}/route.tsx`]: 'route.tsx.j2',
|
|
41
|
+
[`app/types/${entityLower}.ts`]: 'types.ts.j2',
|
|
42
|
+
[`app/schemas/${entityLower}.schema.ts`]: 'schema.ts.j2',
|
|
43
|
+
};
|
|
44
|
+
if (options.loaders !== false) {
|
|
45
|
+
mapping[`app/routes/${entityPlural}/loader.ts`] = 'loader.ts.j2';
|
|
46
|
+
}
|
|
47
|
+
if (options.actions !== false) {
|
|
48
|
+
mapping[`app/routes/${entityPlural}/action.ts`] = 'action.ts.j2';
|
|
49
|
+
}
|
|
50
|
+
if (this.isEnabled(options, 'forms', true)) {
|
|
51
|
+
mapping[`app/components/${entity.name}Form.tsx`] = 'form.tsx.j2';
|
|
52
|
+
}
|
|
53
|
+
if (this.isEnabled(options, 'tables', true)) {
|
|
54
|
+
mapping[`app/components/${entity.name}Table.tsx`] = 'table.tsx.j2';
|
|
55
|
+
}
|
|
56
|
+
if (this.isEnabled(options, 'tests')) {
|
|
57
|
+
mapping[`app/routes/${entityPlural}/__tests__/route.test.tsx`] = 'test.tsx.j2';
|
|
58
|
+
}
|
|
59
|
+
return mapping;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate React Router v7 routes from entity specification
|
|
63
|
+
*/
|
|
64
|
+
async generate(entity, options = {}) {
|
|
65
|
+
// Set defaults - React Router v7 specific
|
|
66
|
+
const opts = {
|
|
67
|
+
forms: true,
|
|
68
|
+
tables: true,
|
|
69
|
+
loaders: true,
|
|
70
|
+
actions: true,
|
|
71
|
+
streaming: false,
|
|
72
|
+
...options,
|
|
73
|
+
};
|
|
74
|
+
// Generate files
|
|
75
|
+
return super.generate(entity, opts);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseGenerator, GeneratedFile, GeneratorOptions } from './base.js';
|
|
2
|
+
import { EntitySpec } from '../parser.js';
|
|
3
|
+
export interface React19ShadcnOptions extends GeneratorOptions {
|
|
4
|
+
componentPattern?: 'compound' | 'simple';
|
|
5
|
+
formPattern?: 'controlled' | 'uncontrolled';
|
|
6
|
+
includeTable?: boolean;
|
|
7
|
+
includeModal?: boolean;
|
|
8
|
+
includeDrawer?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class React19ShadcnGenerator extends BaseGenerator {
|
|
11
|
+
private engine;
|
|
12
|
+
private options;
|
|
13
|
+
constructor(options?: React19ShadcnOptions);
|
|
14
|
+
generate(entity: EntitySpec): Promise<GeneratedFile[]>;
|
|
15
|
+
private generateFormComponent;
|
|
16
|
+
private generateTableComponent;
|
|
17
|
+
private generateModalComponent;
|
|
18
|
+
private generateDrawerComponent;
|
|
19
|
+
private generateCardComponent;
|
|
20
|
+
private generateTypes;
|
|
21
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// React 19 + ShadcnUI Component Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generates modern React components using:
|
|
6
|
+
// - React 19 features (useOptimistic, useActionState, useFormStatus)
|
|
7
|
+
// - ShadcnUI components (Button, Input, Select, Table, etc.)
|
|
8
|
+
// - Compound component patterns
|
|
9
|
+
// - TypeScript types
|
|
10
|
+
// - Accessible components (ARIA)
|
|
11
|
+
//
|
|
12
|
+
import { BaseGenerator } from './base.js';
|
|
13
|
+
import { TemplateEngine, buildContext } from '../template-engine.js';
|
|
14
|
+
export class React19ShadcnGenerator extends BaseGenerator {
|
|
15
|
+
engine;
|
|
16
|
+
options;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
super();
|
|
19
|
+
this.engine = new TemplateEngine();
|
|
20
|
+
this.options = {
|
|
21
|
+
componentPattern: options.componentPattern || 'compound',
|
|
22
|
+
formPattern: options.formPattern || 'controlled',
|
|
23
|
+
includeTable: options.includeTable ?? true,
|
|
24
|
+
includeModal: options.includeModal ?? true,
|
|
25
|
+
includeDrawer: options.includeDrawer ?? false,
|
|
26
|
+
...options,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async generate(entity) {
|
|
30
|
+
const files = [];
|
|
31
|
+
const context = buildContext(entity, this.options);
|
|
32
|
+
// Form component with React 19 hooks
|
|
33
|
+
files.push(await this.generateFormComponent(entity, context));
|
|
34
|
+
// Data table with sorting/filtering
|
|
35
|
+
if (this.options.includeTable) {
|
|
36
|
+
files.push(await this.generateTableComponent(entity, context));
|
|
37
|
+
}
|
|
38
|
+
// Modal component
|
|
39
|
+
if (this.options.includeModal) {
|
|
40
|
+
files.push(await this.generateModalComponent(entity, context));
|
|
41
|
+
}
|
|
42
|
+
// Drawer component
|
|
43
|
+
if (this.options.includeDrawer) {
|
|
44
|
+
files.push(await this.generateDrawerComponent(entity, context));
|
|
45
|
+
}
|
|
46
|
+
// Card component
|
|
47
|
+
files.push(await this.generateCardComponent(entity, context));
|
|
48
|
+
// Types
|
|
49
|
+
files.push(await this.generateTypes(entity, context));
|
|
50
|
+
return files;
|
|
51
|
+
}
|
|
52
|
+
async generateFormComponent(entity, context) {
|
|
53
|
+
const template = `// ${entity.name} Form Component (React 19 + ShadcnUI)
|
|
54
|
+
// Generated by CodeDNA
|
|
55
|
+
|
|
56
|
+
import { useActionState, useOptimistic } from 'react';
|
|
57
|
+
import { Button } from '@/components/ui/button';
|
|
58
|
+
import { Input } from '@/components/ui/input';
|
|
59
|
+
import { Label } from '@/components/ui/label';
|
|
60
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
61
|
+
import { ${entity.name} } from '@/types/${entity.name.toLowerCase()}';
|
|
62
|
+
|
|
63
|
+
interface ${entity.name}FormProps {
|
|
64
|
+
initialData?: Partial<${entity.name}>;
|
|
65
|
+
onSubmit: (data: FormData) => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function ${entity.name}Form({ initialData, onSubmit }: ${entity.name}FormProps) {
|
|
69
|
+
const [state, formAction, isPending] = useActionState(
|
|
70
|
+
async (prevState: any, formData: FormData) => {
|
|
71
|
+
try {
|
|
72
|
+
await onSubmit(formData);
|
|
73
|
+
return { success: true, error: null };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return { success: false, error: error instanceof Error ? error.message : 'Failed to submit' };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{ success: false, error: null }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<form action={formAction} className="space-y-6">
|
|
83
|
+
${entity.fields.map(field => {
|
|
84
|
+
if (field.type.kind === 'primitive') {
|
|
85
|
+
return ` <div className="space-y-2">
|
|
86
|
+
<Label htmlFor="${field.name}">${field.name.charAt(0).toUpperCase() + field.name.slice(1)}</Label>
|
|
87
|
+
<Input
|
|
88
|
+
id="${field.name}"
|
|
89
|
+
name="${field.name}"
|
|
90
|
+
type="${field.type.value === 'email' ? 'email' : field.type.value === 'integer' || field.type.value === 'decimal' ? 'number' : 'text'}"
|
|
91
|
+
defaultValue={initialData?.${field.name} || ''}
|
|
92
|
+
${field.constraints.some(c => c.kind === 'required') ? 'required' : ''}
|
|
93
|
+
disabled={isPending}
|
|
94
|
+
/>
|
|
95
|
+
</div>`;
|
|
96
|
+
}
|
|
97
|
+
else if (field.type.kind === 'enum') {
|
|
98
|
+
return ` <div className="space-y-2">
|
|
99
|
+
<Label htmlFor="${field.name}">${field.name.charAt(0).toUpperCase() + field.name.slice(1)}</Label>
|
|
100
|
+
<Select name="${field.name}" defaultValue={initialData?.${field.name}} disabled={isPending}>
|
|
101
|
+
<SelectTrigger>
|
|
102
|
+
<SelectValue placeholder="Select ${field.name}" />
|
|
103
|
+
</SelectTrigger>
|
|
104
|
+
<SelectContent>
|
|
105
|
+
${field.type.values.map(v => ` <SelectItem value="${v}">${v}</SelectItem>`).join('\n')}
|
|
106
|
+
</SelectContent>
|
|
107
|
+
</Select>
|
|
108
|
+
</div>`;
|
|
109
|
+
}
|
|
110
|
+
return '';
|
|
111
|
+
}).filter(Boolean).join('\n\n')}
|
|
112
|
+
|
|
113
|
+
{state.error && (
|
|
114
|
+
<div className="text-sm text-red-600">{state.error}</div>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
<div className="flex gap-4">
|
|
118
|
+
<Button type="submit" disabled={isPending}>
|
|
119
|
+
{isPending ? 'Saving...' : 'Save'}
|
|
120
|
+
</Button>
|
|
121
|
+
<Button type="button" variant="outline" disabled={isPending}>
|
|
122
|
+
Cancel
|
|
123
|
+
</Button>
|
|
124
|
+
</div>
|
|
125
|
+
</form>
|
|
126
|
+
);
|
|
127
|
+
}`;
|
|
128
|
+
return {
|
|
129
|
+
path: `src/components/${entity.name}Form.tsx`,
|
|
130
|
+
content: template,
|
|
131
|
+
language: 'typescript',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async generateTableComponent(entity, context) {
|
|
135
|
+
const template = `// ${entity.name} Table Component (React 19 + ShadcnUI)
|
|
136
|
+
// Generated by CodeDNA
|
|
137
|
+
|
|
138
|
+
import { useState } from 'react';
|
|
139
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
|
140
|
+
import { Button } from '@/components/ui/button';
|
|
141
|
+
import { ${entity.name} } from '@/types/${entity.name.toLowerCase()}';
|
|
142
|
+
|
|
143
|
+
interface ${entity.name}TableProps {
|
|
144
|
+
data: ${entity.name}[];
|
|
145
|
+
onEdit?: (item: ${entity.name}) => void;
|
|
146
|
+
onDelete?: (id: number) => void;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function ${entity.name}Table({ data, onEdit, onDelete }: ${entity.name}TableProps) {
|
|
150
|
+
const [sortField, setSortField] = useState<keyof ${entity.name} | null>(null);
|
|
151
|
+
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
|
152
|
+
|
|
153
|
+
const sortedData = [...data].sort((a, b) => {
|
|
154
|
+
if (!sortField) return 0;
|
|
155
|
+
const aVal = a[sortField];
|
|
156
|
+
const bVal = b[sortField];
|
|
157
|
+
if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1;
|
|
158
|
+
if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1;
|
|
159
|
+
return 0;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const handleSort = (field: keyof ${entity.name}) => {
|
|
163
|
+
if (sortField === field) {
|
|
164
|
+
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
|
165
|
+
} else {
|
|
166
|
+
setSortField(field);
|
|
167
|
+
setSortDirection('asc');
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<Table>
|
|
173
|
+
<TableHeader>
|
|
174
|
+
<TableRow>
|
|
175
|
+
<TableHead onClick={() => handleSort('id')} className="cursor-pointer">
|
|
176
|
+
ID {sortField === 'id' && (sortDirection === 'asc' ? '↑' : '↓')}
|
|
177
|
+
</TableHead>
|
|
178
|
+
${entity.fields.slice(0, 5).map(field => ` <TableHead onClick={() => handleSort('${field.name}' as keyof ${entity.name})} className="cursor-pointer">
|
|
179
|
+
${field.name.charAt(0).toUpperCase() + field.name.slice(1)} {sortField === '${field.name}' && (sortDirection === 'asc' ? '↑' : '↓')}
|
|
180
|
+
</TableHead>`).join('\n')}
|
|
181
|
+
<TableHead>Actions</TableHead>
|
|
182
|
+
</TableRow>
|
|
183
|
+
</TableHeader>
|
|
184
|
+
<TableBody>
|
|
185
|
+
{sortedData.map((item) => (
|
|
186
|
+
<TableRow key={item.id}>
|
|
187
|
+
<TableCell>{item.id}</TableCell>
|
|
188
|
+
${entity.fields.slice(0, 5).map(field => ` <TableCell>{item.${field.name}}</TableCell>`).join('\n')}
|
|
189
|
+
<TableCell>
|
|
190
|
+
<div className="flex gap-2">
|
|
191
|
+
{onEdit && (
|
|
192
|
+
<Button variant="outline" size="sm" onClick={() => onEdit(item)}>
|
|
193
|
+
Edit
|
|
194
|
+
</Button>
|
|
195
|
+
)}
|
|
196
|
+
{onDelete && (
|
|
197
|
+
<Button variant="destructive" size="sm" onClick={() => onDelete(item.id)}>
|
|
198
|
+
Delete
|
|
199
|
+
</Button>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</TableCell>
|
|
203
|
+
</TableRow>
|
|
204
|
+
))}
|
|
205
|
+
</TableBody>
|
|
206
|
+
</Table>
|
|
207
|
+
);
|
|
208
|
+
}`;
|
|
209
|
+
return {
|
|
210
|
+
path: `src/components/${entity.name}Table.tsx`,
|
|
211
|
+
content: template,
|
|
212
|
+
language: 'typescript',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
async generateModalComponent(entity, context) {
|
|
216
|
+
const template = `// ${entity.name} Modal Component (React 19 + ShadcnUI)
|
|
217
|
+
// Generated by CodeDNA
|
|
218
|
+
|
|
219
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
220
|
+
import { ${entity.name}Form } from './${entity.name}Form';
|
|
221
|
+
import { ${entity.name} } from '@/types/${entity.name.toLowerCase()}';
|
|
222
|
+
|
|
223
|
+
interface ${entity.name}ModalProps {
|
|
224
|
+
open: boolean;
|
|
225
|
+
onOpenChange: (open: boolean) => void;
|
|
226
|
+
initialData?: Partial<${entity.name}>;
|
|
227
|
+
onSubmit: (data: FormData) => Promise<void>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function ${entity.name}Modal({ open, onOpenChange, initialData, onSubmit }: ${entity.name}ModalProps) {
|
|
231
|
+
const handleSubmit = async (data: FormData) => {
|
|
232
|
+
await onSubmit(data);
|
|
233
|
+
onOpenChange(false);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
238
|
+
<DialogContent>
|
|
239
|
+
<DialogHeader>
|
|
240
|
+
<DialogTitle>{initialData ? 'Edit' : 'Create'} ${entity.name}</DialogTitle>
|
|
241
|
+
</DialogHeader>
|
|
242
|
+
<${entity.name}Form initialData={initialData} onSubmit={handleSubmit} />
|
|
243
|
+
</DialogContent>
|
|
244
|
+
</Dialog>
|
|
245
|
+
);
|
|
246
|
+
}`;
|
|
247
|
+
return {
|
|
248
|
+
path: `src/components/${entity.name}Modal.tsx`,
|
|
249
|
+
content: template,
|
|
250
|
+
language: 'typescript',
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
async generateDrawerComponent(entity, context) {
|
|
254
|
+
const template = `// ${entity.name} Drawer Component (React 19 + ShadcnUI)
|
|
255
|
+
// Generated by CodeDNA
|
|
256
|
+
|
|
257
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
|
258
|
+
import { ${entity.name}Form } from './${entity.name}Form';
|
|
259
|
+
import { ${entity.name} } from '@/types/${entity.name.toLowerCase()}';
|
|
260
|
+
|
|
261
|
+
interface ${entity.name}DrawerProps {
|
|
262
|
+
open: boolean;
|
|
263
|
+
onOpenChange: (open: boolean) => void;
|
|
264
|
+
initialData?: Partial<${entity.name}>;
|
|
265
|
+
onSubmit: (data: FormData) => Promise<void>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function ${entity.name}Drawer({ open, onOpenChange, initialData, onSubmit }: ${entity.name}DrawerProps) {
|
|
269
|
+
const handleSubmit = async (data: FormData) => {
|
|
270
|
+
await onSubmit(data);
|
|
271
|
+
onOpenChange(false);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
276
|
+
<SheetContent>
|
|
277
|
+
<SheetHeader>
|
|
278
|
+
<SheetTitle>{initialData ? 'Edit' : 'Create'} ${entity.name}</SheetTitle>
|
|
279
|
+
</SheetHeader>
|
|
280
|
+
<div className="mt-6">
|
|
281
|
+
<${entity.name}Form initialData={initialData} onSubmit={handleSubmit} />
|
|
282
|
+
</div>
|
|
283
|
+
</SheetContent>
|
|
284
|
+
</Sheet>
|
|
285
|
+
);
|
|
286
|
+
}`;
|
|
287
|
+
return {
|
|
288
|
+
path: `src/components/${entity.name}Drawer.tsx`,
|
|
289
|
+
content: template,
|
|
290
|
+
language: 'typescript',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async generateCardComponent(entity, context) {
|
|
294
|
+
const template = `// ${entity.name} Card Component (React 19 + ShadcnUI)
|
|
295
|
+
// Generated by CodeDNA
|
|
296
|
+
|
|
297
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
298
|
+
import { ${entity.name} } from '@/types/${entity.name.toLowerCase()}';
|
|
299
|
+
|
|
300
|
+
interface ${entity.name}CardProps {
|
|
301
|
+
item: ${entity.name};
|
|
302
|
+
onEdit?: () => void;
|
|
303
|
+
onDelete?: () => void;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function ${entity.name}Card({ item, onEdit, onDelete }: ${entity.name}CardProps) {
|
|
307
|
+
return (
|
|
308
|
+
<Card>
|
|
309
|
+
<CardHeader>
|
|
310
|
+
<CardTitle>${entity.name} #{item.id}</CardTitle>
|
|
311
|
+
${entity.fields.slice(0, 1).map(field => ` <CardDescription>{item.${field.name}}</CardDescription>`).join('\n')}
|
|
312
|
+
</CardHeader>
|
|
313
|
+
<CardContent>
|
|
314
|
+
<dl className="space-y-2">
|
|
315
|
+
${entity.fields.slice(1, 4).map(field => ` <div>
|
|
316
|
+
<dt className="text-sm font-medium text-gray-500">${field.name.charAt(0).toUpperCase() + field.name.slice(1)}</dt>
|
|
317
|
+
<dd className="text-sm text-gray-900">{item.${field.name}}</dd>
|
|
318
|
+
</div>`).join('\n')}
|
|
319
|
+
</dl>
|
|
320
|
+
</CardContent>
|
|
321
|
+
</Card>
|
|
322
|
+
);
|
|
323
|
+
}`;
|
|
324
|
+
return {
|
|
325
|
+
path: `src/components/${entity.name}Card.tsx`,
|
|
326
|
+
content: template,
|
|
327
|
+
language: 'typescript',
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
async generateTypes(entity, context) {
|
|
331
|
+
const template = `// ${entity.name} Types
|
|
332
|
+
// Generated by CodeDNA
|
|
333
|
+
|
|
334
|
+
export interface ${entity.name} {
|
|
335
|
+
id: number;
|
|
336
|
+
${entity.fields.map(field => {
|
|
337
|
+
let type = 'string';
|
|
338
|
+
if (field.type.kind === 'primitive') {
|
|
339
|
+
if (field.type.value === 'integer' || field.type.value === 'decimal')
|
|
340
|
+
type = 'number';
|
|
341
|
+
else if (field.type.value === 'boolean')
|
|
342
|
+
type = 'boolean';
|
|
343
|
+
else if (field.type.value === 'datetime')
|
|
344
|
+
type = 'Date';
|
|
345
|
+
}
|
|
346
|
+
else if (field.type.kind === 'enum') {
|
|
347
|
+
type = field.type.values.map(v => `'${v}'`).join(' | ');
|
|
348
|
+
}
|
|
349
|
+
else if (field.type.kind === 'array') {
|
|
350
|
+
type = `${field.type.itemType}[]`;
|
|
351
|
+
}
|
|
352
|
+
const nullable = !field.constraints.some(c => c.kind === 'required');
|
|
353
|
+
return ` ${field.name}: ${type}${nullable ? ' | null' : ''};`;
|
|
354
|
+
}).join('\n')}
|
|
355
|
+
createdAt: Date;
|
|
356
|
+
updatedAt: Date;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export type Create${entity.name}Input = Omit<${entity.name}, 'id' | 'createdAt' | 'updatedAt'>;
|
|
360
|
+
export type Update${entity.name}Input = Partial<Create${entity.name}Input>;`;
|
|
361
|
+
return {
|
|
362
|
+
path: `src/types/${entity.name.toLowerCase()}.ts`,
|
|
363
|
+
content: template,
|
|
364
|
+
language: 'typescript',
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseGenerator } from './base.js';
|
|
2
|
+
import { EntitySpec } from '../parser.js';
|
|
3
|
+
import { GenerateFrontendOptions } from '../types.js';
|
|
4
|
+
interface SvelteKitOptions extends GenerateFrontendOptions {
|
|
5
|
+
ssr?: boolean;
|
|
6
|
+
formActions?: boolean;
|
|
7
|
+
superforms?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class SvelteKitGenerator extends BaseGenerator {
|
|
10
|
+
protected getGeneratorId(): string;
|
|
11
|
+
protected getRequiredTemplates(options: SvelteKitOptions): string[];
|
|
12
|
+
protected getFileMapping(entity: EntitySpec, options: SvelteKitOptions): Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Generate SvelteKit pages and components from entity specification
|
|
15
|
+
*/
|
|
16
|
+
generate(entity: EntitySpec, options?: SvelteKitOptions): Promise<import('../types.js').GenerationResult>;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SvelteKit Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generate SvelteKit pages, components, server routes, and form actions.
|
|
6
|
+
// Full-stack TypeScript with server-side rendering and form handling.
|
|
7
|
+
//
|
|
8
|
+
import { BaseGenerator } from './base.js';
|
|
9
|
+
export class SvelteKitGenerator extends BaseGenerator {
|
|
10
|
+
getGeneratorId() {
|
|
11
|
+
return 'sveltekit';
|
|
12
|
+
}
|
|
13
|
+
getRequiredTemplates(options) {
|
|
14
|
+
const templates = [
|
|
15
|
+
'page.svelte.j2',
|
|
16
|
+
'page.server.ts.j2',
|
|
17
|
+
'schema.ts.j2',
|
|
18
|
+
'types.ts.j2',
|
|
19
|
+
];
|
|
20
|
+
if (this.isEnabled(options, 'forms', true)) {
|
|
21
|
+
templates.push('form.svelte.j2');
|
|
22
|
+
}
|
|
23
|
+
if (this.isEnabled(options, 'tables', true)) {
|
|
24
|
+
templates.push('table.svelte.j2');
|
|
25
|
+
}
|
|
26
|
+
if (options.superforms) {
|
|
27
|
+
templates.push('superform.svelte.j2');
|
|
28
|
+
}
|
|
29
|
+
if (this.isEnabled(options, 'tests')) {
|
|
30
|
+
templates.push('test.ts.j2');
|
|
31
|
+
}
|
|
32
|
+
return templates;
|
|
33
|
+
}
|
|
34
|
+
getFileMapping(entity, options) {
|
|
35
|
+
const entityLower = entity.name.toLowerCase();
|
|
36
|
+
const entityPlural = entityLower + 's';
|
|
37
|
+
const mapping = {
|
|
38
|
+
[`src/routes/${entityPlural}/+page.svelte`]: 'page.svelte.j2',
|
|
39
|
+
[`src/routes/${entityPlural}/+page.server.ts`]: 'page.server.ts.j2',
|
|
40
|
+
[`src/lib/schemas/${entityLower}.schema.ts`]: 'schema.ts.j2',
|
|
41
|
+
[`src/lib/types/${entityLower}.ts`]: 'types.ts.j2',
|
|
42
|
+
};
|
|
43
|
+
if (this.isEnabled(options, 'forms', true)) {
|
|
44
|
+
mapping[`src/lib/components/${entity.name}Form.svelte`] = 'form.svelte.j2';
|
|
45
|
+
}
|
|
46
|
+
if (this.isEnabled(options, 'tables', true)) {
|
|
47
|
+
mapping[`src/lib/components/${entity.name}Table.svelte`] = 'table.svelte.j2';
|
|
48
|
+
}
|
|
49
|
+
if (options.superforms) {
|
|
50
|
+
mapping[`src/lib/components/${entity.name}SuperForm.svelte`] = 'superform.svelte.j2';
|
|
51
|
+
}
|
|
52
|
+
if (this.isEnabled(options, 'tests')) {
|
|
53
|
+
mapping[`src/routes/${entityPlural}/__tests__/page.test.ts`] = 'test.ts.j2';
|
|
54
|
+
}
|
|
55
|
+
return mapping;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generate SvelteKit pages and components from entity specification
|
|
59
|
+
*/
|
|
60
|
+
async generate(entity, options = {}) {
|
|
61
|
+
// Set defaults - SvelteKit specific
|
|
62
|
+
const opts = {
|
|
63
|
+
forms: true,
|
|
64
|
+
tables: true,
|
|
65
|
+
ssr: true,
|
|
66
|
+
formActions: true,
|
|
67
|
+
superforms: false,
|
|
68
|
+
...options,
|
|
69
|
+
};
|
|
70
|
+
// Generate files
|
|
71
|
+
return super.generate(entity, opts);
|
|
72
|
+
}
|
|
73
|
+
}
|