create-theokit 1.25.1 → 1.25.3
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/package.json +1 -1
- package/templates/default/CLAUDE.md +2 -2
- package/templates/default/docs/CUSTOMIZATION.md +1 -1
- package/templates/default/dot-claude/rules/theokit-conventions.md +3 -3
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +46 -26
- package/templates/default/dot-claude/skills/theokit-config/SKILL.md +68 -58
- package/templates/default/dot-claude/skills/theokit-gateways/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-routes/SKILL.md +51 -45
package/package.json
CHANGED
|
@@ -8,13 +8,13 @@ This project includes TheoKit-aware skills that activate automatically when you
|
|
|
8
8
|
|
|
9
9
|
| Skill | Triggers when editing | What it provides |
|
|
10
10
|
| ---------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
-
| theokit-routes | `server/routes/**` |
|
|
11
|
+
| theokit-routes | `server/routes/**` | the route() builder, Zod validation, HTTP methods, dynamic params |
|
|
12
12
|
| theokit-gateways | `server/routes/**`, `**/*webhook*` | handleChannelWebhook, the `@theokit/gateway-*` adapters, signature validation, the onMessage seam |
|
|
13
13
|
| theokit-agents | `**/*agent*`, `**/*tool*`, `**/*Agent*`, `**/*Tool*` | @Agent, @Tool, @Toolbox decorators, LLM integration |
|
|
14
14
|
| theokit-database | `**/*schema*`, `**/*db*`, `**/drizzle*`, `**/*migration*`, `**/*seed*` | Drizzle ORM, SQLite, schema patterns, migrations |
|
|
15
15
|
| theokit-frontend | `app/**` | File-based routing, layouts, theoFetch, useAgent |
|
|
16
16
|
| theokit-ui | `app/**`, `**/*Chat*`, `**/*Sidebar*`, `**/*Theme*` | @theokit/ui AI components: ChatThread, ChatMessage, ToolCallCard, theming (generic primitives like CodeBlock/Sidebar come from @usetheo/ui) |
|
|
17
|
-
| theokit-config | `theo.config*`, `**/*config*` |
|
|
17
|
+
| theokit-config | `theo.config*`, `**/*config*` | the config() builder, plugins, security, storage |
|
|
18
18
|
|
|
19
19
|
### Settings
|
|
20
20
|
|
|
@@ -17,7 +17,7 @@ Common changes, and where they go. See [ARCHITECTURE](./ARCHITECTURE.md) for the
|
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
19
|
// agents/tools/echo.ts
|
|
20
|
-
import { tool } from 'theokit/server'
|
|
20
|
+
import { tool } from 'theokit/server/define'
|
|
21
21
|
import { z } from 'zod'
|
|
22
22
|
|
|
23
23
|
export const echoTool = tool('echo')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Imports
|
|
4
4
|
|
|
5
|
-
- Use `theokit/server/define` for
|
|
5
|
+
- Use `theokit/server/define` for `route()`, `action()`, `websocket()`, `tool()`
|
|
6
6
|
- Use `theokit/client` for theoFetch, createAppClient
|
|
7
7
|
- Use `theokit/server/auth` for session/auth APIs
|
|
8
8
|
- NEVER import from `theokit/dist/...` or `theokit/src/...`
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
- Zod is the single source of truth for types and validation
|
|
14
14
|
- Define schema ONCE with `z.object(...)`, derive types with `z.infer<>`
|
|
15
15
|
- NEVER duplicate a Zod schema as a manual TypeScript interface
|
|
16
|
-
- NEVER parse request body manually — use
|
|
16
|
+
- NEVER parse request body manually — use `.body(z.object({ … }))` on the `route()` chain
|
|
17
17
|
|
|
18
18
|
## Routes
|
|
19
19
|
|
|
20
20
|
- File at `server/routes/tasks/[id].ts` maps to `/api/tasks/:id`
|
|
21
|
-
- Export HTTP method handlers: `export const GET =
|
|
21
|
+
- Export HTTP method handlers: `export const GET = route().policy('public').handler(…).build()`
|
|
22
22
|
- Every method declares `policy` — who may call it. `'public'` is a valid answer and an explicit one;
|
|
23
23
|
omitting it fails the build with the file named
|
|
24
24
|
- Use `params: z.object({...})` for URL params, `body:` for request body
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: theokit-agents
|
|
3
|
-
description: TheoKit agent/LLM integration — agents/*.ts convention (
|
|
3
|
+
description: TheoKit agent/LLM integration — agents/*.ts convention (AgentBuilder), the tool() builder, capabilities (advanced/DI), useAgent client hook
|
|
4
4
|
user-invocable: false
|
|
5
5
|
paths:
|
|
6
6
|
- '**/*agent*'
|
|
@@ -20,14 +20,14 @@ Create an `agents/<name>.ts` file at the project root. It is automatically serve
|
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
22
|
// agents/chat.ts
|
|
23
|
-
import {
|
|
23
|
+
import { AgentBuilder } from '@theokit/agents'
|
|
24
24
|
import { z } from 'zod'
|
|
25
25
|
|
|
26
|
-
export default
|
|
27
|
-
input
|
|
28
|
-
model
|
|
29
|
-
system
|
|
30
|
-
|
|
26
|
+
export default AgentBuilder.create()
|
|
27
|
+
.input(z.object({ message: z.string() }))
|
|
28
|
+
.model('openrouter/openai/gpt-4o-mini')
|
|
29
|
+
.system('You are a helpful assistant.')
|
|
30
|
+
.build()
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
The endpoint streams the ai-sdk `UIMessageStream` that `useAgent` (client hook) consumes.
|
|
@@ -58,32 +58,52 @@ export const assistantAgent = applyCapabilities([
|
|
|
58
58
|
// The framework runs the LLM loop via @theokit/sdk.
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
## Tools —
|
|
61
|
+
## Tools — `tool()`
|
|
62
|
+
|
|
63
|
+
Declare a tool with the `tool()` builder from `theokit/server/define`, then chain it onto the agent
|
|
64
|
+
with `.tool(…)`. It is the same API `agents/tools/weather.ts` in this project uses — read that file
|
|
65
|
+
for a working one.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// agents/tools/current-time.ts
|
|
69
|
+
import { tool } from 'theokit/server/define'
|
|
70
|
+
import { z } from 'zod'
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
export const currentTimeTool = tool('current_time')
|
|
73
|
+
.describe('Return the current ISO timestamp')
|
|
74
|
+
.input(z.object({}))
|
|
75
|
+
.execute(async () => new Date().toISOString())
|
|
76
|
+
.build()
|
|
77
|
+
```
|
|
65
78
|
|
|
66
79
|
```typescript
|
|
67
80
|
// agents/chat.ts
|
|
68
|
-
import {
|
|
69
|
-
import { defineAgentTool } from 'theokit/server'
|
|
81
|
+
import { AgentBuilder } from '@theokit/agents'
|
|
70
82
|
import { z } from 'zod'
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
name: 'current_time',
|
|
74
|
-
description: 'Return the current ISO timestamp',
|
|
75
|
-
inputSchema: z.object({}),
|
|
76
|
-
handler: async () => new Date().toISOString(),
|
|
77
|
-
})
|
|
84
|
+
import { currentTimeTool } from './tools/current-time.js'
|
|
78
85
|
|
|
79
|
-
export default
|
|
80
|
-
input
|
|
81
|
-
model
|
|
82
|
-
system
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
export default AgentBuilder.create()
|
|
87
|
+
.input(z.object({ message: z.string() }))
|
|
88
|
+
.model('openrouter/openai/gpt-4o-mini')
|
|
89
|
+
.system('You are a helpful assistant.')
|
|
90
|
+
.tool(currentTimeTool)
|
|
91
|
+
.build()
|
|
85
92
|
```
|
|
86
93
|
|
|
94
|
+
A tool is pure metadata plus a handler: it describes a capability and does local or HTTP work, and
|
|
95
|
+
it NEVER calls an LLM — the agent decides when to invoke it.
|
|
96
|
+
|
|
97
|
+
**Import from `theokit/server/define`, not `theokit/server`.** The umbrella subpath still resolves
|
|
98
|
+
and prints a deprecation warning naming a removal release; every symbol lives under a domain
|
|
99
|
+
subpath (`define`, `auth`, `http`, `security`, …).
|
|
100
|
+
|
|
101
|
+
> **`defineAgentTool` does not exist.** Earlier versions of this skill taught it. The name is still
|
|
102
|
+
> declared in the published `.d.ts`, so an editor will autocomplete it and `tsc` will accept it —
|
|
103
|
+
> and there is no runtime export behind it on any subpath, so the call throws on the first request
|
|
104
|
+
> (usetheokit/theokit#542). If you find it in older code or in a generated snippet, replace it with
|
|
105
|
+
> the `tool()` builder above.
|
|
106
|
+
|
|
87
107
|
### Toolbox class (advanced — state + injected dependencies)
|
|
88
108
|
|
|
89
109
|
A toolbox declares its tools as DATA and keeps handlers as ordinary methods, so the class can hold
|
|
@@ -177,7 +197,7 @@ Before writing custom tools, check if they already exist:
|
|
|
177
197
|
## Rules
|
|
178
198
|
|
|
179
199
|
- Tool `name` and `description` are ALWAYS explicit — never inferred from method names (G4)
|
|
180
|
-
- Tool
|
|
200
|
+
- Tool `.input()` takes a Zod schema — same pattern as `route().body(…)`
|
|
181
201
|
- `@UseGuards()` works on agents (shared with HTTP pipeline)
|
|
182
202
|
- `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings)
|
|
183
203
|
- Agent runtime is `@theokit/sdk` — NEVER call LLM APIs directly via fetch
|
|
@@ -185,7 +205,7 @@ Before writing custom tools, check if they already exist:
|
|
|
185
205
|
|
|
186
206
|
## Anti-patterns
|
|
187
207
|
|
|
188
|
-
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use `
|
|
208
|
+
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use `AgentBuilder.create()`
|
|
189
209
|
- NEVER reimplement tool calling loop — the SDK handles it
|
|
190
210
|
- NEVER reimplement file/search/shell tools — use `@theokit/sdk-tools` (readFile, writeFile, search, etc.)
|
|
191
211
|
- NEVER store conversations manually — SDK persistence is automatic (the SDK owns storage)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: theokit-config
|
|
3
|
-
description: TheoKit configuration —
|
|
3
|
+
description: TheoKit configuration — the config() builder, plugins, security, storage, agents, build targets
|
|
4
4
|
user-invocable: false
|
|
5
5
|
paths:
|
|
6
6
|
- 'theo.config*'
|
|
@@ -12,37 +12,39 @@ paths:
|
|
|
12
12
|
## theo.config.ts
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
export default
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
15
|
+
import { config } from 'theokit'
|
|
16
|
+
|
|
17
|
+
export default config()
|
|
18
|
+
.set({
|
|
19
|
+
// Basic
|
|
20
|
+
name: 'my-app', // DNS-1123 format (lowercase + hyphens)
|
|
21
|
+
port: 3000, // Dev + production port
|
|
22
|
+
|
|
23
|
+
// SSR (default: false)
|
|
24
|
+
ssr: false,
|
|
25
|
+
|
|
26
|
+
// Security (defaults are secure)
|
|
27
|
+
security: {
|
|
28
|
+
csrf: true, // CSRF protection (default: true)
|
|
29
|
+
csp: 'report-only', // Content Security Policy
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Agent runtime
|
|
33
|
+
agents: {
|
|
34
|
+
maxRegistries: 100,
|
|
35
|
+
registry: {
|
|
36
|
+
maxAgents: 100,
|
|
37
|
+
idleTimeoutMs: 30 * 60_000,
|
|
38
|
+
},
|
|
37
39
|
},
|
|
38
|
-
},
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
// DevTools overlay (dev only)
|
|
42
|
+
devtools: true,
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
})
|
|
44
|
+
// Plugins
|
|
45
|
+
plugins: [],
|
|
46
|
+
})
|
|
47
|
+
.build()
|
|
46
48
|
```
|
|
47
49
|
|
|
48
50
|
## Common Configuration Patterns
|
|
@@ -50,50 +52,58 @@ export default defineConfig({
|
|
|
50
52
|
### Adding CORS
|
|
51
53
|
|
|
52
54
|
```typescript
|
|
53
|
-
import {
|
|
54
|
-
|
|
55
|
-
export default
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
import { config } from 'theokit'
|
|
56
|
+
|
|
57
|
+
export default config()
|
|
58
|
+
.set({
|
|
59
|
+
// CORS is handled by the framework — configure in route-level or globally
|
|
60
|
+
security: {
|
|
61
|
+
cors: {
|
|
62
|
+
origin: ['http://localhost:3000', 'https://myapp.com'],
|
|
63
|
+
credentials: true,
|
|
64
|
+
},
|
|
61
65
|
},
|
|
62
|
-
}
|
|
63
|
-
|
|
66
|
+
})
|
|
67
|
+
.build()
|
|
64
68
|
```
|
|
65
69
|
|
|
66
70
|
### Storage (Postgres + Redis)
|
|
67
71
|
|
|
68
72
|
```typescript
|
|
69
|
-
export default
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
73
|
+
export default config()
|
|
74
|
+
.set({
|
|
75
|
+
storage: {
|
|
76
|
+
postgres: [{ url: process.env.DATABASE_URL }],
|
|
77
|
+
redis: [{ url: process.env.REDIS_URL }],
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
.build()
|
|
75
81
|
```
|
|
76
82
|
|
|
77
83
|
### Rate Limiting
|
|
78
84
|
|
|
79
85
|
```typescript
|
|
80
|
-
export default
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
86
|
+
export default config()
|
|
87
|
+
.set({
|
|
88
|
+
rateLimit: {
|
|
89
|
+
global: { max: 100, windowMs: 60_000 },
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
.build()
|
|
85
93
|
```
|
|
86
94
|
|
|
87
95
|
### OpenAPI Generation
|
|
88
96
|
|
|
89
97
|
```typescript
|
|
90
|
-
export default
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
98
|
+
export default config()
|
|
99
|
+
.set({
|
|
100
|
+
openapi: {
|
|
101
|
+
title: 'My App API',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
outDir: '.theokit',
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
.build()
|
|
97
107
|
```
|
|
98
108
|
|
|
99
109
|
## CLI Commands
|
|
@@ -125,4 +135,4 @@ Env vars are loaded from `.env` (dev) and `.env.production` (build). NEVER commi
|
|
|
125
135
|
- NEVER hardcode secrets in theo.config.ts — use environment variables
|
|
126
136
|
- NEVER set `security.csrf: false` in production
|
|
127
137
|
- NEVER use `ssr: true` without understanding hydration (start with `false`)
|
|
128
|
-
- NEVER add plugins that don't match `
|
|
138
|
+
- NEVER add plugins that don't match `TheoPlugin` interface
|
|
@@ -71,7 +71,7 @@ Developing against any of this needs a public URL. `theo.config.ts` has `allowed
|
|
|
71
71
|
that — see the framework README.
|
|
72
72
|
|
|
73
73
|
**Give it a `Request` whose body has not been read.** It calls `request.json()` itself, so a wrapper
|
|
74
|
-
that has already parsed the body — `
|
|
74
|
+
that has already parsed the body — `route().body(…)` offers a parsed `body` in its handler context —
|
|
75
75
|
leaves nothing for it to read. Mount it where you still hold the original request, or pass a clone.
|
|
76
76
|
|
|
77
77
|
`ChannelMessage` is `{ agent, platform, payload }`. There is no `request` inside `onMessage`, because
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: theokit-routes
|
|
3
|
-
description: TheoKit server routes —
|
|
3
|
+
description: TheoKit server routes — the route() builder, Zod validation, HTTP methods, dynamic params, error handling
|
|
4
4
|
user-invocable: false
|
|
5
5
|
paths:
|
|
6
6
|
- 'server/routes/**'
|
|
@@ -9,41 +9,47 @@ paths:
|
|
|
9
9
|
|
|
10
10
|
# TheoKit Routes
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## The `route()` builder
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
|
-
import {
|
|
15
|
+
import { route } from 'theokit/server/define'
|
|
16
16
|
import { z } from 'zod'
|
|
17
17
|
|
|
18
|
-
// GET
|
|
19
|
-
export const GET =
|
|
20
|
-
policy
|
|
21
|
-
params
|
|
22
|
-
query
|
|
23
|
-
handler
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
status
|
|
36
|
-
handler
|
|
37
|
-
// body is fully typed from Zod schema
|
|
18
|
+
// GET — no body, optional params/query
|
|
19
|
+
export const GET = route()
|
|
20
|
+
.policy('public') // who may call it — required
|
|
21
|
+
.params(z.object({ id: z.coerce.number() })) // URL params
|
|
22
|
+
.query(z.object({ page: z.coerce.number().optional() })) // query string
|
|
23
|
+
.handler(({ params, query }) => ({ id: params.id, page: query?.page }))
|
|
24
|
+
.build()
|
|
25
|
+
|
|
26
|
+
// POST — body validation + custom status
|
|
27
|
+
export const POST = route()
|
|
28
|
+
.policy(({ subject }) => subject !== null) // any authenticated caller
|
|
29
|
+
.body(
|
|
30
|
+
z.object({
|
|
31
|
+
title: z.string().min(3),
|
|
32
|
+
done: z.boolean().default(false),
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
.status(201)
|
|
36
|
+
.handler(({ body }) => {
|
|
37
|
+
// body is fully typed from the Zod schema
|
|
38
38
|
return db.insert(tasks).values(body).returning().get()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// PUT, DELETE follow the same pattern
|
|
43
|
-
export const PUT = defineRoute({ policy: 'public', body: z.object({...}), handler: ({body, params}) => {...} })
|
|
44
|
-
export const DELETE = defineRoute({ policy: 'public', params: z.object({id: z.coerce.number()}), handler: ({params}) => {...} })
|
|
39
|
+
})
|
|
40
|
+
.build()
|
|
45
41
|
```
|
|
46
42
|
|
|
43
|
+
The chain is `.policy()`, `.params()`, `.query()`, `.body()`, `.status()`, `.response()`,
|
|
44
|
+
`.csrf()`, `.handler()`, and `.build()` closes it. `server/routes/health.ts` in this project is a
|
|
45
|
+
working one — read it rather than this block if the two ever disagree.
|
|
46
|
+
|
|
47
|
+
`.csrf(false)` opts a single route out of CSRF enforcement. It is for endpoints that legitimately
|
|
48
|
+
receive third-party POSTs — a Stripe or WhatsApp webhook, an OAuth callback — which authenticate by
|
|
49
|
+
signature rather than by session. `policy('public')` answers a different question (may an
|
|
50
|
+
unauthenticated caller reach this) and does NOT lift the CSRF gate: without `.csrf(false)` a webhook
|
|
51
|
+
is refused `CSRF_INVALID` before its signature is ever checked.
|
|
52
|
+
|
|
47
53
|
## policy — who may call this route
|
|
48
54
|
|
|
49
55
|
Required on every exported method. The scanner refuses a route file that omits it and names the
|
|
@@ -68,40 +74,40 @@ headers and no cookies: identity arrives as `subject`, established by the transp
|
|
|
68
74
|
| `server/routes/tasks/[id].ts` | `/api/tasks/:id` | Dynamic param |
|
|
69
75
|
| `server/routes/users/[...slug].ts` | `/api/users/*` | Catch-all |
|
|
70
76
|
|
|
71
|
-
##
|
|
77
|
+
## `action()` (Server Actions)
|
|
72
78
|
|
|
73
79
|
```typescript
|
|
74
|
-
import {
|
|
80
|
+
import { action } from 'theokit/server/define'
|
|
75
81
|
import { z } from 'zod'
|
|
76
82
|
|
|
77
|
-
export const createTask =
|
|
78
|
-
input
|
|
79
|
-
handler
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
})
|
|
83
|
+
export const createTask = action()
|
|
84
|
+
.input(z.object({ title: z.string() }))
|
|
85
|
+
.handler(({ input }) => db.insert(tasks).values(input).returning().get())
|
|
86
|
+
.build()
|
|
83
87
|
```
|
|
84
88
|
|
|
89
|
+
The chain is `.input()`, `.accept()`, `.csrf()`, `.handler()`, and `.build()`.
|
|
90
|
+
|
|
85
91
|
## Error Handling
|
|
86
92
|
|
|
87
93
|
```typescript
|
|
88
|
-
import { TheoError } from 'theokit'
|
|
94
|
+
import { TheoError } from 'theokit/server/http'
|
|
89
95
|
|
|
90
|
-
export const GET =
|
|
91
|
-
policy
|
|
92
|
-
handler
|
|
96
|
+
export const GET = route()
|
|
97
|
+
.policy('public')
|
|
98
|
+
.handler(({ params }) => {
|
|
93
99
|
const task = db.select().from(tasks).where(eq(tasks.id, params.id)).get()
|
|
94
100
|
if (!task) throw new TheoError({ code: 'NOT_FOUND', message: 'Task not found' })
|
|
95
101
|
return task
|
|
96
|
-
}
|
|
97
|
-
|
|
102
|
+
})
|
|
103
|
+
.build()
|
|
98
104
|
```
|
|
99
105
|
|
|
100
106
|
Valid error codes: `BAD_REQUEST` (400), `UNAUTHORIZED` (401), `FORBIDDEN` (403), `NOT_FOUND` (404), `CONFLICT` (409), `UNPROCESSABLE_ENTITY` (422), `TOO_MANY_REQUESTS` (429), `INTERNAL_SERVER_ERROR` (500).
|
|
101
107
|
|
|
102
108
|
## Anti-patterns
|
|
103
109
|
|
|
104
|
-
- NEVER use `res.status().json()` — use
|
|
105
|
-
- NEVER parse `req.body` manually — use
|
|
110
|
+
- NEVER use `res.status().json()` — use `.status(201)` on the `route()` chain
|
|
111
|
+
- NEVER parse `req.body` manually — use `.body(z.object({ … }))` on the chain
|
|
106
112
|
- NEVER create routes outside `server/routes/` — they won't be discovered
|
|
107
113
|
- NEVER export non-HTTP-method names — only `GET`, `POST`, `PUT`, `DELETE`, `PATCH`
|