create-theokit 1.0.7 → 1.0.9
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/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/AGENTS.md +41 -51
- package/templates/default/CLAUDE.md +25 -0
- package/templates/default/README.md.tmpl +53 -51
- package/templates/default/_gitignore +31 -0
- package/templates/default/app/page.tsx +78 -39
- package/templates/default/app.ts +24 -0
- package/templates/default/dot-claude/rules/theokit-conventions.md +36 -0
- package/templates/default/dot-claude/settings.json +23 -0
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +105 -0
- package/templates/default/dot-claude/skills/theokit-config/SKILL.md +128 -0
- package/templates/default/dot-claude/skills/theokit-database/SKILL.md +112 -0
- package/templates/default/dot-claude/skills/theokit-frontend/SKILL.md +89 -0
- package/templates/default/dot-claude/skills/theokit-routes/SKILL.md +89 -0
- package/templates/default/drizzle.config.ts +10 -0
- package/templates/default/eslint.config.mjs +5 -1
- package/templates/default/package.json.tmpl +13 -3
- package/templates/default/public/index.html +70 -0
- package/templates/default/server/agents/assistant.agent.ts +6 -2
- package/templates/default/server/controllers/tasks.controller.ts +5 -13
- package/templates/default/server/db/index.ts +21 -0
- package/templates/default/server/db/schema.ts +13 -0
- package/templates/default/server/db/seed.ts +29 -0
- package/templates/default/server/index.ts +2 -18
- package/templates/default/server/routes/tasks/[id].ts +36 -0
- package/templates/default/server/routes/tasks/index.ts +20 -0
- package/templates/default/server/toolboxes/task.tools.ts +2 -1
- package/templates/default/tests/tasks.test.ts +38 -0
- package/templates/default/theo.config.ts +0 -6
|
@@ -2,36 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, type FormEvent } from 'react'
|
|
4
4
|
|
|
5
|
-
interface Task {
|
|
6
|
-
|
|
5
|
+
interface Task {
|
|
6
|
+
id: number
|
|
7
|
+
title: string
|
|
8
|
+
priority: 'high' | 'medium' | 'low'
|
|
9
|
+
done: boolean
|
|
10
|
+
}
|
|
7
11
|
|
|
8
12
|
export default function Page() {
|
|
9
13
|
const [tasks, setTasks] = useState<Task[]>([])
|
|
10
|
-
const [role, setRole] = useState<Role>('user')
|
|
11
14
|
const [title, setTitle] = useState('')
|
|
12
15
|
const [priority, setPriority] = useState<Task['priority']>('medium')
|
|
13
16
|
const [formError, setFormError] = useState('')
|
|
14
17
|
|
|
15
|
-
const hdrs = useCallback((): Record<string, string> => {
|
|
16
|
-
const h: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
17
|
-
if (role) h['x-role'] = role
|
|
18
|
-
return h
|
|
19
|
-
}, [role])
|
|
20
|
-
|
|
21
18
|
const loadTasks = useCallback(async () => {
|
|
22
19
|
const res = await fetch('/api/tasks')
|
|
23
20
|
if (res.ok) setTasks(await res.json())
|
|
24
21
|
}, [])
|
|
25
22
|
|
|
26
|
-
useEffect(() => {
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
loadTasks()
|
|
25
|
+
}, [loadTasks])
|
|
27
26
|
|
|
28
27
|
const createTask = async (e: FormEvent) => {
|
|
29
28
|
e.preventDefault()
|
|
30
29
|
setFormError('')
|
|
31
30
|
if (!title.trim()) return
|
|
32
|
-
const res = await fetch('/api/tasks', {
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
const res = await fetch('/api/tasks', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
body: JSON.stringify({ title, priority }),
|
|
35
|
+
})
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const b = await res.json()
|
|
38
|
+
setFormError(b.error?.issues?.[0]?.message ?? `Error ${res.status}`)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
35
41
|
setTitle('')
|
|
36
42
|
loadTasks()
|
|
37
43
|
}
|
|
@@ -45,10 +51,20 @@ export default function Page() {
|
|
|
45
51
|
<h1>TheoKit</h1>
|
|
46
52
|
<p className="tagline">Build the app your agent lives in.</p>
|
|
47
53
|
<nav className="ctas">
|
|
48
|
-
<a
|
|
54
|
+
<a
|
|
55
|
+
href="https://usetheo.dev"
|
|
56
|
+
target="_blank"
|
|
57
|
+
rel="noopener noreferrer"
|
|
58
|
+
className="btn primary"
|
|
59
|
+
>
|
|
49
60
|
Get Started
|
|
50
61
|
</a>
|
|
51
|
-
<a
|
|
62
|
+
<a
|
|
63
|
+
href="https://github.com/usetheodev/theokit"
|
|
64
|
+
target="_blank"
|
|
65
|
+
rel="noopener noreferrer"
|
|
66
|
+
className="btn secondary"
|
|
67
|
+
>
|
|
52
68
|
Documentation
|
|
53
69
|
</a>
|
|
54
70
|
</nav>
|
|
@@ -57,34 +73,46 @@ export default function Page() {
|
|
|
57
73
|
</p>
|
|
58
74
|
</header>
|
|
59
75
|
|
|
60
|
-
{/* Role */}
|
|
61
|
-
<div className="role-bar">
|
|
62
|
-
<label htmlFor="role">Role:</label>
|
|
63
|
-
<select id="role" value={role} onChange={e => setRole(e.target.value as Role)}>
|
|
64
|
-
<option value="">None (public)</option>
|
|
65
|
-
<option value="user">User</option>
|
|
66
|
-
<option value="admin">Admin</option>
|
|
67
|
-
</select>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
76
|
{/* Tasks */}
|
|
71
77
|
<section className="card">
|
|
72
|
-
<h2>
|
|
78
|
+
<h2>
|
|
79
|
+
Tasks <span className="badge">defineRoute + Drizzle</span>
|
|
80
|
+
</h2>
|
|
73
81
|
<table>
|
|
74
|
-
<thead
|
|
82
|
+
<thead>
|
|
83
|
+
<tr>
|
|
84
|
+
<th>Task</th>
|
|
85
|
+
<th>Priority</th>
|
|
86
|
+
<th>Status</th>
|
|
87
|
+
</tr>
|
|
88
|
+
</thead>
|
|
75
89
|
<tbody>
|
|
76
|
-
{tasks.map(t => (
|
|
90
|
+
{tasks.map((t) => (
|
|
77
91
|
<tr key={t.id} className={t.done ? 'done' : ''}>
|
|
78
|
-
<td>
|
|
79
|
-
|
|
92
|
+
<td>
|
|
93
|
+
{t.done ? '✅ ' : '○ '}
|
|
94
|
+
{t.title}
|
|
95
|
+
</td>
|
|
96
|
+
<td>
|
|
97
|
+
<span className={`prio prio-${t.priority}`}>{t.priority}</span>
|
|
98
|
+
</td>
|
|
80
99
|
<td>{t.done ? 'Done' : 'To do'}</td>
|
|
81
100
|
</tr>
|
|
82
101
|
))}
|
|
83
102
|
</tbody>
|
|
84
103
|
</table>
|
|
85
104
|
<form onSubmit={createTask} className="create-bar">
|
|
86
|
-
<input
|
|
87
|
-
|
|
105
|
+
<input
|
|
106
|
+
value={title}
|
|
107
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
108
|
+
placeholder="New task..."
|
|
109
|
+
required
|
|
110
|
+
minLength={3}
|
|
111
|
+
/>
|
|
112
|
+
<select
|
|
113
|
+
value={priority}
|
|
114
|
+
onChange={(e) => setPriority(e.target.value as Task['priority'])}
|
|
115
|
+
>
|
|
88
116
|
<option value="medium">Medium</option>
|
|
89
117
|
<option value="high">High</option>
|
|
90
118
|
<option value="low">Low</option>
|
|
@@ -97,12 +125,16 @@ export default function Page() {
|
|
|
97
125
|
{/* Features */}
|
|
98
126
|
<div className="grid features">
|
|
99
127
|
<div className="feature">
|
|
100
|
-
<h3
|
|
101
|
-
<p>
|
|
128
|
+
<h3>defineRoute</h3>
|
|
129
|
+
<p>
|
|
130
|
+
Typed API routes with Zod validation. See <code>server/routes/tasks/</code>
|
|
131
|
+
</p>
|
|
102
132
|
</div>
|
|
103
133
|
<div className="feature">
|
|
104
|
-
<h3>
|
|
105
|
-
<p>
|
|
134
|
+
<h3>Drizzle + SQLite</h3>
|
|
135
|
+
<p>
|
|
136
|
+
Type-safe database with zero config. Schema in <code>server/db/schema.ts</code>
|
|
137
|
+
</p>
|
|
106
138
|
</div>
|
|
107
139
|
<div className="feature">
|
|
108
140
|
<h3>@Agent + @Tool</h3>
|
|
@@ -116,11 +148,18 @@ export default function Page() {
|
|
|
116
148
|
|
|
117
149
|
{/* Footer */}
|
|
118
150
|
<footer className="footer">
|
|
119
|
-
Powered by
|
|
151
|
+
Powered by{' '}
|
|
152
|
+
<a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
153
|
+
TheoKit
|
|
154
|
+
</a>
|
|
120
155
|
{' · '}
|
|
121
|
-
<a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
|
|
156
|
+
<a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
|
|
157
|
+
GitHub
|
|
158
|
+
</a>
|
|
122
159
|
{' · '}
|
|
123
|
-
<a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
160
|
+
<a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
161
|
+
Discord
|
|
162
|
+
</a>
|
|
124
163
|
</footer>
|
|
125
164
|
</div>
|
|
126
165
|
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TheoKit App — convention over configuration.
|
|
3
|
+
*
|
|
4
|
+
* "The framework that reduces noise for humans + AI."
|
|
5
|
+
*
|
|
6
|
+
* Backend classes registered in server/index.ts (one barrel, like Rails).
|
|
7
|
+
* Routes inferred from class names. Zero manual wiring.
|
|
8
|
+
*/
|
|
9
|
+
import 'reflect-metadata'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { TheoApp } from '@theokit/http/app'
|
|
12
|
+
import { TasksController, AssistantAgent, TaskTools } from './server/index.js'
|
|
13
|
+
|
|
14
|
+
let html: string | undefined
|
|
15
|
+
try { html = readFileSync(new URL('./public/index.html', import.meta.url), 'utf-8') } catch { /* no frontend */ }
|
|
16
|
+
|
|
17
|
+
const app = await TheoApp.create({
|
|
18
|
+
controllers: [TasksController],
|
|
19
|
+
agents: [AssistantAgent],
|
|
20
|
+
providers: [TaskTools],
|
|
21
|
+
html,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
await app.listen(3000)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# TheoKit Conventions
|
|
2
|
+
|
|
3
|
+
## Imports
|
|
4
|
+
|
|
5
|
+
- Use `theokit/server/define` for defineRoute, defineAction, defineWebSocket
|
|
6
|
+
- Use `theokit/client` for theoFetch, createAppClient
|
|
7
|
+
- Use `theokit/server/auth` for session/auth APIs
|
|
8
|
+
- NEVER import from `theokit/dist/...` or `theokit/src/...`
|
|
9
|
+
- NEVER import internal modules: `theokit/core`, `theokit/vite-plugin`, `theokit/adapters/*`
|
|
10
|
+
|
|
11
|
+
## Validation
|
|
12
|
+
|
|
13
|
+
- Zod is the single source of truth for types and validation
|
|
14
|
+
- Define schema ONCE with `z.object(...)`, derive types with `z.infer<>`
|
|
15
|
+
- NEVER duplicate a Zod schema as a manual TypeScript interface
|
|
16
|
+
- NEVER parse request body manually — use `body:` in defineRoute
|
|
17
|
+
|
|
18
|
+
## Routes
|
|
19
|
+
|
|
20
|
+
- File at `server/routes/tasks/[id].ts` maps to `/api/tasks/:id`
|
|
21
|
+
- Export HTTP method handlers: `export const GET = defineRoute({...})`
|
|
22
|
+
- Use `params: z.object({...})` for URL params, `body:` for request body
|
|
23
|
+
- Use `status: 201` for creation responses, not manual `res.status()`
|
|
24
|
+
|
|
25
|
+
## Types
|
|
26
|
+
|
|
27
|
+
- No `any` in production code
|
|
28
|
+
- No `@ts-ignore` or `@ts-expect-error`
|
|
29
|
+
- No `as` type assertions — use Zod schemas or type guards
|
|
30
|
+
|
|
31
|
+
## Database
|
|
32
|
+
|
|
33
|
+
- Schema lives in `server/db/schema.ts` (Drizzle ORM)
|
|
34
|
+
- Connection in `server/db/index.ts`
|
|
35
|
+
- Seeds in `server/db/seed.ts`
|
|
36
|
+
- Use `npx drizzle-kit push` for dev, `npx drizzle-kit generate` for prod migrations
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm run *)",
|
|
5
|
+
"Bash(npx theokit *)",
|
|
6
|
+
"Bash(npx vitest *)",
|
|
7
|
+
"Bash(npx tsc *)",
|
|
8
|
+
"Bash(npx eslint *)",
|
|
9
|
+
"Bash(npx drizzle-kit *)",
|
|
10
|
+
"Bash(git status)",
|
|
11
|
+
"Bash(git diff *)",
|
|
12
|
+
"Bash(git log *)"
|
|
13
|
+
],
|
|
14
|
+
"deny": [
|
|
15
|
+
"Read(.env*)",
|
|
16
|
+
"Read(**/.env*)",
|
|
17
|
+
"Bash(sudo *)",
|
|
18
|
+
"Bash(rm -rf *)",
|
|
19
|
+
"Bash(git push --force *)",
|
|
20
|
+
"Bash(git reset --hard *)"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theokit-agents
|
|
3
|
+
description: TheoKit agent/LLM integration — @Agent, @Tool, @Toolbox decorators, streaming, memory
|
|
4
|
+
user-invocable: false
|
|
5
|
+
paths:
|
|
6
|
+
- "**/*agent*"
|
|
7
|
+
- "**/*Agent*"
|
|
8
|
+
- "**/*tool*"
|
|
9
|
+
- "**/*Tool*"
|
|
10
|
+
- "**/*toolbox*"
|
|
11
|
+
- "**/*Toolbox*"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# TheoKit Agents & Tools
|
|
15
|
+
|
|
16
|
+
## @Agent Decorator
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Agent, MainLoop, Hook, Memory, Budget } from '@theokit/agents'
|
|
20
|
+
|
|
21
|
+
@Agent({
|
|
22
|
+
model: 'openai/gpt-4o-mini', // Required: LLM model
|
|
23
|
+
systemPrompt: 'You are a helpful task assistant.',
|
|
24
|
+
})
|
|
25
|
+
@Memory({ provider: 'built-in', scope: 'per-user' }) // Optional
|
|
26
|
+
@Budget({ maxCostUsd: 1.00, window: 'daily' }) // Optional
|
|
27
|
+
export class AssistantAgent {
|
|
28
|
+
@MainLoop({ strategy: 'react', maxIterations: 5 })
|
|
29
|
+
async run() {
|
|
30
|
+
// Framework handles the LLM loop
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Hook('before:llm-call')
|
|
34
|
+
async onBeforeLLM(ctx) {
|
|
35
|
+
// Intercept before each LLM call
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Convention: `AssistantAgent` class name maps to `GET/POST /api/agents/assistant`.
|
|
41
|
+
|
|
42
|
+
## @Tool Decorator
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { Toolbox, Tool } from '@theokit/agents'
|
|
46
|
+
import { z } from 'zod'
|
|
47
|
+
|
|
48
|
+
@Toolbox()
|
|
49
|
+
export class TaskTools {
|
|
50
|
+
@Tool({
|
|
51
|
+
name: 'list_tasks',
|
|
52
|
+
description: 'List all tasks, optionally filtered by status',
|
|
53
|
+
input: z.object({
|
|
54
|
+
done: z.boolean().optional(),
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
async listTasks({ done }: { done?: boolean }) {
|
|
58
|
+
const all = db.select().from(tasks).all()
|
|
59
|
+
return done !== undefined ? all.filter(t => t.done === done) : all
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Tool({
|
|
63
|
+
name: 'create_task',
|
|
64
|
+
description: 'Create a new task with a title',
|
|
65
|
+
input: z.object({
|
|
66
|
+
title: z.string().min(1),
|
|
67
|
+
}),
|
|
68
|
+
})
|
|
69
|
+
async createTask({ title }: { title: string }) {
|
|
70
|
+
return db.insert(tasks).values({ title }).returning().get()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Frontend — useAgentStream
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { useAgentStream } from 'theokit/client'
|
|
79
|
+
|
|
80
|
+
function ChatUI() {
|
|
81
|
+
const { status, events, send } = useAgentStream('/api/agents/assistant')
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
{events.map(e => <p key={e.id}>{e.content}</p>)}
|
|
86
|
+
<button onClick={() => send({ message: 'Hello' })}>Send</button>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Rules
|
|
93
|
+
|
|
94
|
+
- Tool `name` and `description` are ALWAYS explicit — never inferred from method names (G4)
|
|
95
|
+
- Tool `input` uses Zod schema — same pattern as defineRoute
|
|
96
|
+
- `@UseGuards()` works on agents (shared with HTTP pipeline)
|
|
97
|
+
- `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings, not enforced at runtime)
|
|
98
|
+
- Agent runtime is `@theokit/sdk` — NEVER call LLM APIs directly via fetch
|
|
99
|
+
|
|
100
|
+
## Anti-patterns
|
|
101
|
+
|
|
102
|
+
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use @Agent + @Tool
|
|
103
|
+
- NEVER reimplement tool calling loop — the SDK handles it
|
|
104
|
+
- NEVER store conversations manually — use @Memory
|
|
105
|
+
- NEVER infer tool capability from method name — always provide explicit `name` + `description`
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theokit-config
|
|
3
|
+
description: TheoKit configuration — defineConfig, plugins, security, storage, agents, build targets
|
|
4
|
+
user-invocable: false
|
|
5
|
+
paths:
|
|
6
|
+
- "theo.config*"
|
|
7
|
+
- "**/*config*"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TheoKit Configuration
|
|
11
|
+
|
|
12
|
+
## theo.config.ts
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { defineConfig } from 'theokit'
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
// Basic
|
|
19
|
+
name: 'my-app', // DNS-1123 format (lowercase + hyphens)
|
|
20
|
+
port: 3000, // Dev + production port
|
|
21
|
+
|
|
22
|
+
// SSR (default: false)
|
|
23
|
+
ssr: false,
|
|
24
|
+
|
|
25
|
+
// Security (defaults are secure)
|
|
26
|
+
security: {
|
|
27
|
+
csrf: true, // CSRF protection (default: true)
|
|
28
|
+
csp: 'report-only', // Content Security Policy
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Agent runtime
|
|
32
|
+
agents: {
|
|
33
|
+
maxRegistries: 100,
|
|
34
|
+
registry: {
|
|
35
|
+
maxAgents: 100,
|
|
36
|
+
idleTimeoutMs: 30 * 60_000,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// DevTools overlay (dev only)
|
|
41
|
+
devtools: true,
|
|
42
|
+
|
|
43
|
+
// Plugins
|
|
44
|
+
plugins: [],
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Common Configuration Patterns
|
|
49
|
+
|
|
50
|
+
### Adding CORS
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { defineConfig } from 'theokit'
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
// CORS is handled by the framework — configure in route-level or globally
|
|
57
|
+
security: {
|
|
58
|
+
cors: {
|
|
59
|
+
origin: ['http://localhost:3000', 'https://myapp.com'],
|
|
60
|
+
credentials: true,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Storage (Postgres + Redis)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
export default defineConfig({
|
|
70
|
+
storage: {
|
|
71
|
+
postgres: [{ url: process.env.DATABASE_URL }],
|
|
72
|
+
redis: [{ url: process.env.REDIS_URL }],
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Rate Limiting
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
rateLimit: {
|
|
82
|
+
global: { max: 100, windowMs: 60_000 },
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### OpenAPI Generation
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
export default defineConfig({
|
|
91
|
+
openapi: {
|
|
92
|
+
title: 'My App API',
|
|
93
|
+
version: '1.0.0',
|
|
94
|
+
outDir: 'docs/api',
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## CLI Commands
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx theokit dev # Start dev server with HMR
|
|
103
|
+
npx theokit build # Build for Node.js
|
|
104
|
+
npx theokit build --target=node # Explicit target
|
|
105
|
+
npx theokit start # Run production build
|
|
106
|
+
npx theokit routes # List all discovered endpoints
|
|
107
|
+
npx theokit generate route tasks # Scaffold a new route
|
|
108
|
+
npx theokit generate resource posts title:string # Scaffold CRUD resource
|
|
109
|
+
npx theokit db migrate # Run database migrations
|
|
110
|
+
npx theokit db seed # Seed database
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Environment Variables
|
|
114
|
+
|
|
115
|
+
- `PORT` — Server port (overrides config)
|
|
116
|
+
- `HOST` — Server host
|
|
117
|
+
- `NODE_ENV` — `development` | `production`
|
|
118
|
+
- `DATABASE_URL` — Postgres connection string (when using postgres storage)
|
|
119
|
+
- `REDIS_URL` — Redis connection string (when using redis storage)
|
|
120
|
+
|
|
121
|
+
Env vars are loaded from `.env` (dev) and `.env.production` (build). NEVER commit `.env` files.
|
|
122
|
+
|
|
123
|
+
## Anti-patterns
|
|
124
|
+
|
|
125
|
+
- NEVER hardcode secrets in theo.config.ts — use environment variables
|
|
126
|
+
- NEVER set `security.csrf: false` in production
|
|
127
|
+
- NEVER use `ssr: true` without understanding hydration (start with `false`)
|
|
128
|
+
- NEVER add plugins that don't match `defineTheoPlugin` interface
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theokit-database
|
|
3
|
+
description: TheoKit database — Drizzle ORM, SQLite schema, migrations, seeds, db commands
|
|
4
|
+
user-invocable: false
|
|
5
|
+
paths:
|
|
6
|
+
- "**/*schema*"
|
|
7
|
+
- "**/*db*"
|
|
8
|
+
- "**/drizzle*"
|
|
9
|
+
- "**/*migration*"
|
|
10
|
+
- "**/*seed*"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# TheoKit Database (Drizzle + SQLite)
|
|
14
|
+
|
|
15
|
+
## Schema Definition
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// server/db/schema.ts
|
|
19
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
|
|
20
|
+
|
|
21
|
+
export const tasks = sqliteTable('tasks', {
|
|
22
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
23
|
+
title: text('title').notNull(),
|
|
24
|
+
done: integer('done', { mode: 'boolean' }).notNull().default(false),
|
|
25
|
+
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const users = sqliteTable('users', {
|
|
29
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
30
|
+
email: text('email').notNull().unique(),
|
|
31
|
+
name: text('name').notNull(),
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## DB Connection
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// server/db/index.ts
|
|
39
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
|
40
|
+
import Database from 'better-sqlite3'
|
|
41
|
+
import * as schema from './schema.js'
|
|
42
|
+
|
|
43
|
+
const sqlite = new Database('data/dev.db')
|
|
44
|
+
export const db = drizzle(sqlite, { schema })
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Common Queries
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { db } from '@/server/db'
|
|
51
|
+
import { tasks } from '@/server/db/schema'
|
|
52
|
+
import { eq } from 'drizzle-orm'
|
|
53
|
+
|
|
54
|
+
// Select all
|
|
55
|
+
db.select().from(tasks).all()
|
|
56
|
+
|
|
57
|
+
// Select by ID
|
|
58
|
+
db.select().from(tasks).where(eq(tasks.id, 1)).get()
|
|
59
|
+
|
|
60
|
+
// Insert + return
|
|
61
|
+
db.insert(tasks).values({ title: 'New task' }).returning().get()
|
|
62
|
+
|
|
63
|
+
// Update
|
|
64
|
+
db.update(tasks).set({ done: true }).where(eq(tasks.id, 1)).run()
|
|
65
|
+
|
|
66
|
+
// Delete
|
|
67
|
+
db.delete(tasks).where(eq(tasks.id, 1)).run()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Seeds
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// server/db/seed.ts
|
|
74
|
+
import { db } from './index.js'
|
|
75
|
+
import { tasks } from './schema.js'
|
|
76
|
+
|
|
77
|
+
await db.insert(tasks).values([
|
|
78
|
+
{ title: 'Learn TheoKit', done: false },
|
|
79
|
+
{ title: 'Build an agent', done: false },
|
|
80
|
+
]).run()
|
|
81
|
+
|
|
82
|
+
console.log('Seeded database')
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Run: `npm run seed` or `npx tsx server/db/seed.ts`
|
|
86
|
+
|
|
87
|
+
## CLI Commands
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx drizzle-kit push # Apply schema changes to dev DB (no migration files)
|
|
91
|
+
npx drizzle-kit generate # Generate SQL migration files (for production)
|
|
92
|
+
npm run seed # Run seed script
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Scaffolding Resources
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx theokit generate resource posts title:string published:boolean
|
|
99
|
+
# Creates: server/db/schema.ts (appends table)
|
|
100
|
+
# server/routes/posts/index.ts (GET + POST)
|
|
101
|
+
# server/routes/posts/[id].ts (GET + PUT + DELETE)
|
|
102
|
+
# tests/posts.test.ts (smoke test)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Supported field types: `string`, `text`, `number`, `boolean`
|
|
106
|
+
|
|
107
|
+
## Anti-patterns
|
|
108
|
+
|
|
109
|
+
- NEVER use raw SQL for schema — use Drizzle's schema builder
|
|
110
|
+
- NEVER delete `data/dev.db` to apply changes — use `npx drizzle-kit push`
|
|
111
|
+
- NEVER put DB connection logic in route files — import from `server/db/index.ts`
|
|
112
|
+
- NEVER use `id`, `createdAt`, or `created_at` as field names in `generate resource` — they're auto-added
|