@claudetools/tools 0.8.11 → 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/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,527 @@
|
|
|
1
|
+
# Kappa Example: TanStack Start Full-Stack App
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-01-02
|
|
4
|
+
**Purpose:** Demonstrate Kappa's efficiency for specifying a complete TanStack Start application
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## The App: Project Management SaaS
|
|
9
|
+
|
|
10
|
+
A simple project management app with:
|
|
11
|
+
- User authentication (email/password)
|
|
12
|
+
- Dashboard with project list
|
|
13
|
+
- CRUD operations for projects
|
|
14
|
+
- Team member management
|
|
15
|
+
- API routes
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Kappa Specification
|
|
20
|
+
|
|
21
|
+
### Complete App Spec (~150 tokens)
|
|
22
|
+
|
|
23
|
+
```kappa
|
|
24
|
+
# === TYPES ===
|
|
25
|
+
|
|
26
|
+
User = { id: str, email: str, name: str, role: admin | member }
|
|
27
|
+
Project = { id: str, name: str, description: str?, ownerId: str, status: active | archived, createdAt: DateTime }
|
|
28
|
+
Member = { userId: str, projectId: str, role: owner | editor | viewer }
|
|
29
|
+
|
|
30
|
+
# === AUTH ===
|
|
31
|
+
|
|
32
|
+
signup: (email: str, password: str, name: str) -[DB, Auth, Error]-> User
|
|
33
|
+
login: (email: str, password: str) -[DB, Auth, Error]-> { user: User, token: str }
|
|
34
|
+
logout: () -[Auth]-> void
|
|
35
|
+
me: () -[Auth, Error]-> User
|
|
36
|
+
|
|
37
|
+
# === PROJECTS API ===
|
|
38
|
+
|
|
39
|
+
listProjects: () -[DB, Auth]-> [Project]
|
|
40
|
+
getProject: (id: str) -[DB, Auth, Error]-> Project
|
|
41
|
+
createProject: (name: str, description: str?) -[DB, Auth]-> Project
|
|
42
|
+
updateProject: (id: str, name: str?, description: str?, status: str?) -[DB, Auth, Error]-> Project
|
|
43
|
+
deleteProject: (id: str) -[DB, Auth, Error]-> void
|
|
44
|
+
|
|
45
|
+
# === MEMBERS API ===
|
|
46
|
+
|
|
47
|
+
listMembers: (projectId: str) -[DB, Auth]-> [Member & { user: User }]
|
|
48
|
+
addMember: (projectId: str, email: str, role: str) -[DB, Auth, Error]-> Member
|
|
49
|
+
removeMember: (projectId: str, userId: str) -[DB, Auth, Error]-> void
|
|
50
|
+
|
|
51
|
+
# === ROUTES ===
|
|
52
|
+
|
|
53
|
+
routes = {
|
|
54
|
+
"/": redirect("/dashboard") >> requireAuth
|
|
55
|
+
"/login": page(LoginForm) >> guestOnly
|
|
56
|
+
"/signup": page(SignupForm) >> guestOnly
|
|
57
|
+
"/dashboard": page(Dashboard, loader: listProjects) >> requireAuth
|
|
58
|
+
"/projects/:id": page(ProjectDetail, loader: getProject) >> requireAuth
|
|
59
|
+
"/projects/:id/settings": page(ProjectSettings, loader: getProject) >> requireAuth >> requireRole(owner)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# === COMPONENTS ===
|
|
63
|
+
|
|
64
|
+
LoginForm: () -[Form, Auth, Navigate]-> JSX = form({
|
|
65
|
+
fields: { email: email(), password: password() }
|
|
66
|
+
onSubmit: login >> navigate("/dashboard")
|
|
67
|
+
onError: toast.error
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
SignupForm: () -[Form, Auth, Navigate]-> JSX = form({
|
|
71
|
+
fields: { email: email(), password: password(), name: text() }
|
|
72
|
+
onSubmit: signup >> login >> navigate("/dashboard")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
Dashboard: (projects: [Project]) -[UI]-> JSX = layout({
|
|
76
|
+
header: "Projects"
|
|
77
|
+
actions: [button("New Project", onClick: createProjectModal)]
|
|
78
|
+
content: projectList(projects)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
ProjectDetail: (project: Project) -[UI, Query]-> JSX = layout({
|
|
82
|
+
header: project.name
|
|
83
|
+
tabs: [
|
|
84
|
+
tab("Overview", projectOverview(project))
|
|
85
|
+
tab("Members", memberList(project.id))
|
|
86
|
+
tab("Settings", projectSettings(project)) >> show(role == owner)
|
|
87
|
+
]
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Total: ~150 tokens**
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## What CodeDNA Generates
|
|
96
|
+
|
|
97
|
+
### 1. Database Schema (`db/schema.ts`) - Generated
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
101
|
+
|
|
102
|
+
export const users = sqliteTable('users', {
|
|
103
|
+
id: text('id').primaryKey(),
|
|
104
|
+
email: text('email').notNull().unique(),
|
|
105
|
+
name: text('name').notNull(),
|
|
106
|
+
passwordHash: text('password_hash').notNull(),
|
|
107
|
+
role: text('role', { enum: ['admin', 'member'] }).notNull().default('member'),
|
|
108
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().defaultNow(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const projects = sqliteTable('projects', {
|
|
112
|
+
id: text('id').primaryKey(),
|
|
113
|
+
name: text('name').notNull(),
|
|
114
|
+
description: text('description'),
|
|
115
|
+
ownerId: text('owner_id').notNull().references(() => users.id),
|
|
116
|
+
status: text('status', { enum: ['active', 'archived'] }).notNull().default('active'),
|
|
117
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().defaultNow(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export const members = sqliteTable('members', {
|
|
121
|
+
userId: text('user_id').notNull().references(() => users.id),
|
|
122
|
+
projectId: text('project_id').notNull().references(() => projects.id),
|
|
123
|
+
role: text('role', { enum: ['owner', 'editor', 'viewer'] }).notNull(),
|
|
124
|
+
}, (table) => ({
|
|
125
|
+
pk: primaryKey(table.userId, table.projectId),
|
|
126
|
+
}));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. API Routes (`app/api/projects.ts`) - Generated
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { createAPIFileRoute } from '@tanstack/start/api';
|
|
133
|
+
import { z } from 'zod';
|
|
134
|
+
import { db } from '~/db';
|
|
135
|
+
import { projects, members } from '~/db/schema';
|
|
136
|
+
import { requireAuth } from '~/lib/auth';
|
|
137
|
+
import { eq, and } from 'drizzle-orm';
|
|
138
|
+
import { nanoid } from 'nanoid';
|
|
139
|
+
|
|
140
|
+
const createProjectSchema = z.object({
|
|
141
|
+
name: z.string().min(1).max(100),
|
|
142
|
+
description: z.string().max(500).optional(),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const APIRoute = createAPIFileRoute('/api/projects')({
|
|
146
|
+
GET: async ({ request }) => {
|
|
147
|
+
const user = await requireAuth(request);
|
|
148
|
+
|
|
149
|
+
const userProjects = await db
|
|
150
|
+
.select()
|
|
151
|
+
.from(projects)
|
|
152
|
+
.innerJoin(members, eq(members.projectId, projects.id))
|
|
153
|
+
.where(eq(members.userId, user.id));
|
|
154
|
+
|
|
155
|
+
return Response.json({ data: userProjects });
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
POST: async ({ request }) => {
|
|
159
|
+
const user = await requireAuth(request);
|
|
160
|
+
const body = createProjectSchema.parse(await request.json());
|
|
161
|
+
|
|
162
|
+
const id = nanoid();
|
|
163
|
+
const project = await db.insert(projects).values({
|
|
164
|
+
id,
|
|
165
|
+
name: body.name,
|
|
166
|
+
description: body.description,
|
|
167
|
+
ownerId: user.id,
|
|
168
|
+
status: 'active',
|
|
169
|
+
}).returning();
|
|
170
|
+
|
|
171
|
+
// Add creator as owner
|
|
172
|
+
await db.insert(members).values({
|
|
173
|
+
userId: user.id,
|
|
174
|
+
projectId: id,
|
|
175
|
+
role: 'owner',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return Response.json({ data: project[0] }, { status: 201 });
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 3. Route with Loader (`app/routes/dashboard.tsx`) - Generated
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
187
|
+
import { createServerFn } from '@tanstack/start';
|
|
188
|
+
import { db } from '~/db';
|
|
189
|
+
import { projects, members } from '~/db/schema';
|
|
190
|
+
import { requireAuth } from '~/lib/auth';
|
|
191
|
+
import { eq } from 'drizzle-orm';
|
|
192
|
+
|
|
193
|
+
const loadProjects = createServerFn('GET', async () => {
|
|
194
|
+
const user = await requireAuth();
|
|
195
|
+
|
|
196
|
+
const userProjects = await db
|
|
197
|
+
.select({
|
|
198
|
+
id: projects.id,
|
|
199
|
+
name: projects.name,
|
|
200
|
+
description: projects.description,
|
|
201
|
+
status: projects.status,
|
|
202
|
+
createdAt: projects.createdAt,
|
|
203
|
+
})
|
|
204
|
+
.from(projects)
|
|
205
|
+
.innerJoin(members, eq(members.projectId, projects.id))
|
|
206
|
+
.where(eq(members.userId, user.id))
|
|
207
|
+
.orderBy(projects.createdAt);
|
|
208
|
+
|
|
209
|
+
return userProjects;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export const Route = createFileRoute('/dashboard')({
|
|
213
|
+
beforeLoad: async ({ context }) => {
|
|
214
|
+
if (!context.auth.user) {
|
|
215
|
+
throw redirect({ to: '/login' });
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
loader: () => loadProjects(),
|
|
219
|
+
component: Dashboard,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
function Dashboard() {
|
|
223
|
+
const projects = Route.useLoaderData();
|
|
224
|
+
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div className="container mx-auto py-8">
|
|
228
|
+
<div className="flex justify-between items-center mb-8">
|
|
229
|
+
<h1 className="text-3xl font-bold">Projects</h1>
|
|
230
|
+
<Button onClick={() => setIsCreateOpen(true)}>
|
|
231
|
+
New Project
|
|
232
|
+
</Button>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{projects.length === 0 ? (
|
|
236
|
+
<EmptyState
|
|
237
|
+
title="No projects yet"
|
|
238
|
+
description="Create your first project to get started"
|
|
239
|
+
action={<Button onClick={() => setIsCreateOpen(true)}>Create Project</Button>}
|
|
240
|
+
/>
|
|
241
|
+
) : (
|
|
242
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
243
|
+
{projects.map((project) => (
|
|
244
|
+
<ProjectCard key={project.id} project={project} />
|
|
245
|
+
))}
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<CreateProjectModal
|
|
250
|
+
open={isCreateOpen}
|
|
251
|
+
onOpenChange={setIsCreateOpen}
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 4. Login Form (`app/routes/login.tsx`) - Generated
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
262
|
+
import { createServerFn } from '@tanstack/start';
|
|
263
|
+
import { useForm } from '@tanstack/react-form';
|
|
264
|
+
import { zodValidator } from '@tanstack/zod-form-adapter';
|
|
265
|
+
import { z } from 'zod';
|
|
266
|
+
import { db } from '~/db';
|
|
267
|
+
import { users } from '~/db/schema';
|
|
268
|
+
import { eq } from 'drizzle-orm';
|
|
269
|
+
import { verify } from '@node-rs/bcrypt';
|
|
270
|
+
import { createSession } from '~/lib/auth';
|
|
271
|
+
import { toast } from 'sonner';
|
|
272
|
+
|
|
273
|
+
const loginSchema = z.object({
|
|
274
|
+
email: z.string().email('Invalid email address'),
|
|
275
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const loginFn = createServerFn('POST', async (data: z.infer<typeof loginSchema>) => {
|
|
279
|
+
const user = await db
|
|
280
|
+
.select()
|
|
281
|
+
.from(users)
|
|
282
|
+
.where(eq(users.email, data.email))
|
|
283
|
+
.get();
|
|
284
|
+
|
|
285
|
+
if (!user) {
|
|
286
|
+
throw new Error('Invalid email or password');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const valid = await verify(data.password, user.passwordHash);
|
|
290
|
+
if (!valid) {
|
|
291
|
+
throw new Error('Invalid email or password');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const token = await createSession(user.id);
|
|
295
|
+
|
|
296
|
+
return { user: { id: user.id, email: user.email, name: user.name, role: user.role }, token };
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export const Route = createFileRoute('/login')({
|
|
300
|
+
beforeLoad: async ({ context }) => {
|
|
301
|
+
if (context.auth.user) {
|
|
302
|
+
throw redirect({ to: '/dashboard' });
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
component: LoginPage,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
function LoginPage() {
|
|
309
|
+
const navigate = useNavigate();
|
|
310
|
+
|
|
311
|
+
const form = useForm({
|
|
312
|
+
defaultValues: { email: '', password: '' },
|
|
313
|
+
validatorAdapter: zodValidator(),
|
|
314
|
+
validators: { onChange: loginSchema },
|
|
315
|
+
onSubmit: async ({ value }) => {
|
|
316
|
+
try {
|
|
317
|
+
await loginFn(value);
|
|
318
|
+
navigate({ to: '/dashboard' });
|
|
319
|
+
} catch (error) {
|
|
320
|
+
toast.error(error instanceof Error ? error.message : 'Login failed');
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
327
|
+
<Card className="w-full max-w-md">
|
|
328
|
+
<CardHeader>
|
|
329
|
+
<CardTitle>Sign In</CardTitle>
|
|
330
|
+
<CardDescription>Enter your credentials to continue</CardDescription>
|
|
331
|
+
</CardHeader>
|
|
332
|
+
<CardContent>
|
|
333
|
+
<form
|
|
334
|
+
onSubmit={(e) => {
|
|
335
|
+
e.preventDefault();
|
|
336
|
+
form.handleSubmit();
|
|
337
|
+
}}
|
|
338
|
+
className="space-y-4"
|
|
339
|
+
>
|
|
340
|
+
<form.Field name="email">
|
|
341
|
+
{(field) => (
|
|
342
|
+
<div className="space-y-2">
|
|
343
|
+
<Label htmlFor="email">Email</Label>
|
|
344
|
+
<Input
|
|
345
|
+
id="email"
|
|
346
|
+
type="email"
|
|
347
|
+
value={field.state.value}
|
|
348
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
349
|
+
/>
|
|
350
|
+
{field.state.meta.errors && (
|
|
351
|
+
<p className="text-sm text-red-500">{field.state.meta.errors}</p>
|
|
352
|
+
)}
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</form.Field>
|
|
356
|
+
|
|
357
|
+
<form.Field name="password">
|
|
358
|
+
{(field) => (
|
|
359
|
+
<div className="space-y-2">
|
|
360
|
+
<Label htmlFor="password">Password</Label>
|
|
361
|
+
<Input
|
|
362
|
+
id="password"
|
|
363
|
+
type="password"
|
|
364
|
+
value={field.state.value}
|
|
365
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
366
|
+
/>
|
|
367
|
+
{field.state.meta.errors && (
|
|
368
|
+
<p className="text-sm text-red-500">{field.state.meta.errors}</p>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
)}
|
|
372
|
+
</form.Field>
|
|
373
|
+
|
|
374
|
+
<Button type="submit" className="w-full" disabled={form.state.isSubmitting}>
|
|
375
|
+
{form.state.isSubmitting ? 'Signing in...' : 'Sign In'}
|
|
376
|
+
</Button>
|
|
377
|
+
</form>
|
|
378
|
+
</CardContent>
|
|
379
|
+
<CardFooter className="justify-center">
|
|
380
|
+
<p className="text-sm text-muted-foreground">
|
|
381
|
+
Don't have an account? <Link to="/signup" className="text-primary hover:underline">Sign up</Link>
|
|
382
|
+
</p>
|
|
383
|
+
</CardFooter>
|
|
384
|
+
</Card>
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 5. Auth Middleware (`lib/auth.ts`) - Generated
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { getWebRequest } from '@tanstack/start/server';
|
|
394
|
+
import { db } from '~/db';
|
|
395
|
+
import { users } from '~/db/schema';
|
|
396
|
+
import { eq } from 'drizzle-orm';
|
|
397
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
398
|
+
|
|
399
|
+
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);
|
|
400
|
+
|
|
401
|
+
export async function createSession(userId: string): Promise<string> {
|
|
402
|
+
return new SignJWT({ userId })
|
|
403
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
404
|
+
.setIssuedAt()
|
|
405
|
+
.setExpirationTime('7d')
|
|
406
|
+
.sign(JWT_SECRET);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export async function requireAuth() {
|
|
410
|
+
const request = getWebRequest();
|
|
411
|
+
const token = request.headers.get('Authorization')?.replace('Bearer ', '')
|
|
412
|
+
|| request.headers.get('Cookie')?.match(/token=([^;]+)/)?.[1];
|
|
413
|
+
|
|
414
|
+
if (!token) {
|
|
415
|
+
throw new Error('Unauthorized');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const { payload } = await jwtVerify(token, JWT_SECRET);
|
|
420
|
+
const user = await db
|
|
421
|
+
.select({ id: users.id, email: users.email, name: users.name, role: users.role })
|
|
422
|
+
.from(users)
|
|
423
|
+
.where(eq(users.id, payload.userId as string))
|
|
424
|
+
.get();
|
|
425
|
+
|
|
426
|
+
if (!user) {
|
|
427
|
+
throw new Error('User not found');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return user;
|
|
431
|
+
} catch {
|
|
432
|
+
throw new Error('Invalid token');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export async function getOptionalAuth() {
|
|
437
|
+
try {
|
|
438
|
+
return await requireAuth();
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Token Comparison
|
|
448
|
+
|
|
449
|
+
| Component | Traditional (tokens) | Kappa Spec (tokens) | Generated (tokens) |
|
|
450
|
+
|-----------|---------------------|--------------------|--------------------|
|
|
451
|
+
| Types/Schema | 180 | 25 | 180 |
|
|
452
|
+
| Auth API | 250 | 20 | 250 |
|
|
453
|
+
| Projects API | 300 | 25 | 300 |
|
|
454
|
+
| Members API | 200 | 15 | 200 |
|
|
455
|
+
| Routes Config | 150 | 20 | 150 |
|
|
456
|
+
| Login Form | 350 | 20 | 350 |
|
|
457
|
+
| Dashboard | 280 | 15 | 280 |
|
|
458
|
+
| Auth Middleware | 200 | 0 (implied) | 200 |
|
|
459
|
+
| **TOTAL** | **1,910** | **~150** | **1,910** |
|
|
460
|
+
|
|
461
|
+
**Kappa input: 150 tokens**
|
|
462
|
+
**Generated output: 1,910 tokens**
|
|
463
|
+
**Expansion ratio: 12.7x**
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## How CodeDNA Uses Kappa
|
|
468
|
+
|
|
469
|
+
```
|
|
470
|
+
User: "Create a project management app with auth and team features"
|
|
471
|
+
↓
|
|
472
|
+
AI generates Kappa spec (~150 tokens, costs ~$0.005)
|
|
473
|
+
↓
|
|
474
|
+
CodeDNA parses spec, validates types/effects
|
|
475
|
+
↓
|
|
476
|
+
Generators produce:
|
|
477
|
+
- db/schema.ts (Drizzle ORM)
|
|
478
|
+
- app/api/*.ts (API routes)
|
|
479
|
+
- app/routes/*.tsx (Pages with loaders)
|
|
480
|
+
- lib/auth.ts (Auth middleware)
|
|
481
|
+
- components/*.tsx (UI components)
|
|
482
|
+
↓
|
|
483
|
+
Output: Complete TanStack Start app (~1,900 tokens of code)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Without Kappa:** AI generates 1,910 tokens directly → $0.06
|
|
487
|
+
**With Kappa:** AI generates 150 tokens, CodeDNA expands → $0.005
|
|
488
|
+
|
|
489
|
+
**Cost reduction: 92%**
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Why This Works
|
|
494
|
+
|
|
495
|
+
1. **Types are schemas** - `User = { id: str, email: str... }` → Drizzle schema, Zod validators, TypeScript types
|
|
496
|
+
2. **Effects are middleware** - `-[DB, Auth]->` → Database connection, auth check, error handling
|
|
497
|
+
3. **Routes are config** - `"/dashboard": page(Dashboard, loader: listProjects)` → Full TanStack Router setup
|
|
498
|
+
4. **Forms are declarations** - `form({ fields: {...}, onSubmit: ... })` → TanStack Form with validation
|
|
499
|
+
|
|
500
|
+
**Kappa specifies WHAT. CodeDNA generates HOW.**
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Extending the App
|
|
505
|
+
|
|
506
|
+
Adding a new feature in Kappa:
|
|
507
|
+
|
|
508
|
+
```kappa
|
|
509
|
+
# Add comments to projects
|
|
510
|
+
Comment = { id: str, projectId: str, userId: str, text: str, createdAt: DateTime }
|
|
511
|
+
|
|
512
|
+
listComments: (projectId: str) -[DB, Auth]-> [Comment & { user: User }]
|
|
513
|
+
addComment: (projectId: str, text: str) -[DB, Auth]-> Comment
|
|
514
|
+
deleteComment: (id: str) -[DB, Auth, Error]-> void
|
|
515
|
+
|
|
516
|
+
# Add to routes
|
|
517
|
+
"/projects/:id/comments": page(CommentList, loader: listComments) >> requireAuth
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Additional tokens: ~30**
|
|
521
|
+
**Generated code: ~400 tokens**
|
|
522
|
+
|
|
523
|
+
The developer writes 30 tokens. CodeDNA generates 400 tokens of production code with proper error handling, validation, and UI components.
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
*Kappa: Write specs, not boilerplate.*
|