@xano/developer-mcp 1.0.0 → 1.0.2
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/README.md +96 -31
- package/dist/index.js +335 -222
- package/dist/templates/init-workspace.d.ts +10 -0
- package/dist/templates/init-workspace.js +292 -0
- package/dist/templates/xanoscript-index.d.ts +9 -0
- package/dist/templates/xanoscript-index.js +61 -0
- package/package.json +4 -2
- package/xanoscript_docs/README.md +107 -1
- package/xanoscript_docs/agents.md +329 -0
- package/xanoscript_docs/apis.md +343 -0
- package/xanoscript_docs/database.md +417 -0
- package/xanoscript_docs/ephemeral.md +333 -0
- package/xanoscript_docs/frontend.md +291 -0
- package/xanoscript_docs/functions.md +232 -2035
- package/xanoscript_docs/integrations.md +439 -0
- package/xanoscript_docs/mcp-servers.md +190 -0
- package/xanoscript_docs/plan.md +192 -0
- package/xanoscript_docs/syntax.md +314 -0
- package/xanoscript_docs/tables.md +270 -0
- package/xanoscript_docs/tasks.md +254 -0
- package/xanoscript_docs/testing.md +335 -0
- package/xanoscript_docs/tools.md +305 -0
- package/xanoscript_docs/types.md +297 -0
- package/xanoscript_docs/version.json +2 -1
- package/xanoscript_docs/api_query_examples.md +0 -1255
- package/xanoscript_docs/api_query_guideline.md +0 -129
- package/xanoscript_docs/build_from_lovable.md +0 -715
- package/xanoscript_docs/db_query_guideline.md +0 -427
- package/xanoscript_docs/ephemeral_environment_guideline.md +0 -529
- package/xanoscript_docs/expression_guideline.md +0 -1086
- package/xanoscript_docs/frontend_guideline.md +0 -67
- package/xanoscript_docs/function_examples.md +0 -1406
- package/xanoscript_docs/function_guideline.md +0 -130
- package/xanoscript_docs/input_guideline.md +0 -227
- package/xanoscript_docs/mcp_server_examples.md +0 -36
- package/xanoscript_docs/mcp_server_guideline.md +0 -69
- package/xanoscript_docs/query_filter.md +0 -489
- package/xanoscript_docs/table_examples.md +0 -586
- package/xanoscript_docs/table_guideline.md +0 -137
- package/xanoscript_docs/task_examples.md +0 -511
- package/xanoscript_docs/task_guideline.md +0 -103
- package/xanoscript_docs/tips_and_tricks.md +0 -144
- package/xanoscript_docs/tool_examples.md +0 -69
- package/xanoscript_docs/tool_guideline.md +0 -139
- package/xanoscript_docs/unit_testing_guideline.md +0 -328
- package/xanoscript_docs/workspace.md +0 -17
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "agents/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Agents
|
|
6
|
+
|
|
7
|
+
AI-powered agents that use LLMs to perform tasks autonomously.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
agent "<name>" {
|
|
13
|
+
canonical = "<unique-id>"
|
|
14
|
+
description = "What this agent does"
|
|
15
|
+
llm = {
|
|
16
|
+
type: "<provider>"
|
|
17
|
+
system_prompt: "Agent instructions"
|
|
18
|
+
prompt: "{{ $args.message }}"
|
|
19
|
+
max_steps: 5
|
|
20
|
+
}
|
|
21
|
+
tools = [{ name: "<tool-name>" }]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### LLM Providers
|
|
26
|
+
| Provider | Type Value |
|
|
27
|
+
|----------|------------|
|
|
28
|
+
| Xano Free (Gemini) | `xano-free` |
|
|
29
|
+
| Google Gemini | `google-genai` |
|
|
30
|
+
| OpenAI | `openai` |
|
|
31
|
+
| Anthropic | `anthropic` |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Basic Structure
|
|
36
|
+
|
|
37
|
+
```xs
|
|
38
|
+
agent "Customer Support" {
|
|
39
|
+
canonical = "support-agent-v1"
|
|
40
|
+
description = "Handles customer inquiries"
|
|
41
|
+
llm = {
|
|
42
|
+
type: "xano-free"
|
|
43
|
+
system_prompt: "You are a helpful customer support agent."
|
|
44
|
+
prompt: "{{ $args.user_message }}"
|
|
45
|
+
max_steps: 5
|
|
46
|
+
temperature: 0.7
|
|
47
|
+
}
|
|
48
|
+
tools = [
|
|
49
|
+
{ name: "get_order_status" },
|
|
50
|
+
{ name: "create_ticket" }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Calling Agents
|
|
58
|
+
|
|
59
|
+
```xs
|
|
60
|
+
ai.agent.run "Customer Support" {
|
|
61
|
+
args = {}|set:"user_message":$input.message
|
|
62
|
+
allow_tool_execution = true
|
|
63
|
+
} as $response
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## LLM Configuration
|
|
69
|
+
|
|
70
|
+
### Common Properties
|
|
71
|
+
|
|
72
|
+
```xs
|
|
73
|
+
llm = {
|
|
74
|
+
type: "<provider>" # Required
|
|
75
|
+
system_prompt: "..." # Agent persona and rules
|
|
76
|
+
prompt: "{{ $args.input }}" # User input template
|
|
77
|
+
max_steps: 5 # Max LLM calls per run
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Dynamic Variables
|
|
82
|
+
- `{{ $args.<name> }}` - Runtime arguments
|
|
83
|
+
- `{{ $env.<name> }}` - Environment variables (for API keys)
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Provider Configurations
|
|
88
|
+
|
|
89
|
+
### Xano Free (for testing)
|
|
90
|
+
```xs
|
|
91
|
+
llm = {
|
|
92
|
+
type: "xano-free"
|
|
93
|
+
system_prompt: "You are a helpful assistant."
|
|
94
|
+
prompt: "{{ $args.message }}"
|
|
95
|
+
max_steps: 3
|
|
96
|
+
temperature: 0
|
|
97
|
+
search_grounding: false # Google Search grounding
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Google Gemini
|
|
102
|
+
```xs
|
|
103
|
+
llm = {
|
|
104
|
+
type: "google-genai"
|
|
105
|
+
api_key: "{{ $env.GEMINI_API_KEY }}"
|
|
106
|
+
model: "gemini-2.5-flash"
|
|
107
|
+
system_prompt: "You are a helpful assistant."
|
|
108
|
+
prompt: "{{ $args.message }}"
|
|
109
|
+
max_steps: 5
|
|
110
|
+
temperature: 0.2
|
|
111
|
+
thinking_tokens: 10000 # Extended thinking
|
|
112
|
+
include_thoughts: true
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### OpenAI
|
|
117
|
+
```xs
|
|
118
|
+
llm = {
|
|
119
|
+
type: "openai"
|
|
120
|
+
api_key: "{{ $env.OPENAI_API_KEY }}"
|
|
121
|
+
model: "gpt-5-mini"
|
|
122
|
+
system_prompt: "You are a helpful assistant."
|
|
123
|
+
prompt: "{{ $args.message }}"
|
|
124
|
+
max_steps: 5
|
|
125
|
+
temperature: 0.8
|
|
126
|
+
reasoning_effort: "medium" # low, medium, high
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**OpenAI-Compatible APIs:**
|
|
131
|
+
```xs
|
|
132
|
+
llm = {
|
|
133
|
+
type: "openai"
|
|
134
|
+
api_key: "{{ $env.GROQ_API_KEY }}"
|
|
135
|
+
baseURL: "https://api.groq.com/openai/v1"
|
|
136
|
+
model: "llama-3.3-70b-versatile"
|
|
137
|
+
compatibility: "compatible" # Required for non-OpenAI
|
|
138
|
+
...
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Supported: Groq, Mistral, OpenRouter, X.AI
|
|
143
|
+
|
|
144
|
+
### Anthropic Claude
|
|
145
|
+
```xs
|
|
146
|
+
llm = {
|
|
147
|
+
type: "anthropic"
|
|
148
|
+
api_key: "{{ $env.ANTHROPIC_API_KEY }}"
|
|
149
|
+
model: "claude-sonnet-4-5-20250929"
|
|
150
|
+
system_prompt: "You are a helpful assistant."
|
|
151
|
+
prompt: "{{ $args.message }}"
|
|
152
|
+
max_steps: 8
|
|
153
|
+
temperature: 0.3
|
|
154
|
+
send_reasoning: true # Include thinking blocks
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Structured Outputs
|
|
161
|
+
|
|
162
|
+
Force JSON response format (disables tools):
|
|
163
|
+
|
|
164
|
+
```xs
|
|
165
|
+
agent "Classifier" {
|
|
166
|
+
canonical = "classifier-v1"
|
|
167
|
+
llm = {
|
|
168
|
+
type: "openai"
|
|
169
|
+
api_key: "{{ $env.OPENAI_API_KEY }}"
|
|
170
|
+
model: "gpt-5-mini"
|
|
171
|
+
system_prompt: "Classify the sentiment of the text."
|
|
172
|
+
prompt: "{{ $args.text }}"
|
|
173
|
+
structured_outputs: true
|
|
174
|
+
|
|
175
|
+
output {
|
|
176
|
+
enum sentiment { values = ["positive", "negative", "neutral"] }
|
|
177
|
+
decimal confidence filters=min:0|max:1
|
|
178
|
+
text reasoning?
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
tools = []
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Tools
|
|
188
|
+
|
|
189
|
+
Reference tools by name from `tools/` directory:
|
|
190
|
+
|
|
191
|
+
```xs
|
|
192
|
+
tools = [
|
|
193
|
+
{ name: "get_user_by_email" },
|
|
194
|
+
{ name: "update_order_status" },
|
|
195
|
+
{ name: "send_notification" }
|
|
196
|
+
]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Important:** Do not describe tools in system_prompt or prompt. Tool descriptions are automatically provided to the LLM.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Prompting
|
|
204
|
+
|
|
205
|
+
### Using Twig Templates
|
|
206
|
+
```xs
|
|
207
|
+
llm = {
|
|
208
|
+
prompt: """
|
|
209
|
+
User ID: {{ $args.user_id }}
|
|
210
|
+
Request: {{ $args.message }}
|
|
211
|
+
|
|
212
|
+
{% if $args.is_priority %}
|
|
213
|
+
This is a priority customer. Respond within 5 minutes.
|
|
214
|
+
{% endif %}
|
|
215
|
+
"""
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Available Variables
|
|
220
|
+
```xs
|
|
221
|
+
{{ $args.any_arg }} # Runtime arguments
|
|
222
|
+
{{ $env.MY_VAR }} # Environment variables
|
|
223
|
+
{{ "now"|date("Y-m-d") }} # Current date
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Complete Examples
|
|
229
|
+
|
|
230
|
+
### Task Manager Agent
|
|
231
|
+
```xs
|
|
232
|
+
agent "Task Manager" {
|
|
233
|
+
canonical = "task-mgr-v1"
|
|
234
|
+
description = "Manages user tasks"
|
|
235
|
+
llm = {
|
|
236
|
+
type: "google-genai"
|
|
237
|
+
api_key: "{{ $env.GEMINI_API_KEY }}"
|
|
238
|
+
model: "gemini-2.5-flash"
|
|
239
|
+
system_prompt: """
|
|
240
|
+
You help users manage their tasks. You can:
|
|
241
|
+
- Add new tasks
|
|
242
|
+
- Mark tasks complete
|
|
243
|
+
- List pending tasks
|
|
244
|
+
Always confirm actions with the user.
|
|
245
|
+
"""
|
|
246
|
+
prompt: "User {{ $args.user_id }}: {{ $args.message }}"
|
|
247
|
+
max_steps: 5
|
|
248
|
+
temperature: 0.2
|
|
249
|
+
}
|
|
250
|
+
tools = [
|
|
251
|
+
{ name: "add_task" },
|
|
252
|
+
{ name: "complete_task" },
|
|
253
|
+
{ name: "list_tasks" }
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Code Review Agent
|
|
259
|
+
```xs
|
|
260
|
+
agent "Code Reviewer" {
|
|
261
|
+
canonical = "code-review-v1"
|
|
262
|
+
llm = {
|
|
263
|
+
type: "anthropic"
|
|
264
|
+
api_key: "{{ $env.ANTHROPIC_API_KEY }}"
|
|
265
|
+
model: "claude-sonnet-4-5-20250929"
|
|
266
|
+
system_prompt: """
|
|
267
|
+
You are an expert code reviewer. Analyze code for:
|
|
268
|
+
- Bugs and potential issues
|
|
269
|
+
- Security vulnerabilities
|
|
270
|
+
- Performance problems
|
|
271
|
+
- Code style and best practices
|
|
272
|
+
Provide specific, actionable feedback.
|
|
273
|
+
"""
|
|
274
|
+
prompt: """
|
|
275
|
+
Language: {{ $args.language }}
|
|
276
|
+
Code:
|
|
277
|
+
```
|
|
278
|
+
{{ $args.code }}
|
|
279
|
+
```
|
|
280
|
+
"""
|
|
281
|
+
max_steps: 3
|
|
282
|
+
temperature: 0.1
|
|
283
|
+
send_reasoning: true
|
|
284
|
+
}
|
|
285
|
+
tools = []
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Multi-Tool Research Agent
|
|
290
|
+
```xs
|
|
291
|
+
agent "Research Assistant" {
|
|
292
|
+
canonical = "research-v1"
|
|
293
|
+
llm = {
|
|
294
|
+
type: "openai"
|
|
295
|
+
api_key: "{{ $env.OPENAI_API_KEY }}"
|
|
296
|
+
model: "gpt-5"
|
|
297
|
+
system_prompt: """
|
|
298
|
+
You are a research assistant. Use your tools to:
|
|
299
|
+
1. Search for relevant information
|
|
300
|
+
2. Analyze data
|
|
301
|
+
3. Compile findings into clear summaries
|
|
302
|
+
Always cite your sources.
|
|
303
|
+
"""
|
|
304
|
+
prompt: "Research topic: {{ $args.topic }}"
|
|
305
|
+
max_steps: 10
|
|
306
|
+
temperature: 0.5
|
|
307
|
+
reasoning_effort: "high"
|
|
308
|
+
}
|
|
309
|
+
tools = [
|
|
310
|
+
{ name: "web_search" },
|
|
311
|
+
{ name: "fetch_article" },
|
|
312
|
+
{ name: "analyze_data" },
|
|
313
|
+
{ name: "save_findings" }
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Best Practices
|
|
321
|
+
|
|
322
|
+
1. **Clear system prompts** - Define persona, capabilities, and constraints
|
|
323
|
+
2. **Use appropriate temperature** - Low for factual, higher for creative
|
|
324
|
+
3. **Limit max_steps** - Prevent infinite loops (3-10 typical)
|
|
325
|
+
4. **Don't repeat tool descriptions** - They're auto-injected
|
|
326
|
+
5. **Use environment variables** - Never hardcode API keys
|
|
327
|
+
6. **Keep prompts focused** - One task per agent
|
|
328
|
+
7. **Test with xano-free first** - Free for development
|
|
329
|
+
8. **Use structured outputs** - When you need consistent JSON
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "apis/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# APIs
|
|
6
|
+
|
|
7
|
+
HTTP endpoint definitions in XanoScript.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
query "<path>" verb=<METHOD> {
|
|
13
|
+
description = "What this endpoint does"
|
|
14
|
+
auth = "<table>" # Optional: require authentication
|
|
15
|
+
input { ... }
|
|
16
|
+
stack { ... }
|
|
17
|
+
response = $result
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### HTTP Methods
|
|
22
|
+
`GET`, `POST`, `PUT`, `PATCH`, `DELETE`
|
|
23
|
+
|
|
24
|
+
### File Structure
|
|
25
|
+
```
|
|
26
|
+
apis/
|
|
27
|
+
├── users/ # API group
|
|
28
|
+
│ ├── list.xs # GET /users
|
|
29
|
+
│ ├── create.xs # POST /users
|
|
30
|
+
│ └── {id}.xs # GET/PATCH/DELETE /users/{id}
|
|
31
|
+
└── products/
|
|
32
|
+
└── search.xs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Basic Structure
|
|
38
|
+
|
|
39
|
+
```xs
|
|
40
|
+
query "products" verb=GET {
|
|
41
|
+
description = "List all products"
|
|
42
|
+
input {
|
|
43
|
+
int page?=1 filters=min:1
|
|
44
|
+
int per_page?=20 filters=min:1|max:100
|
|
45
|
+
}
|
|
46
|
+
stack {
|
|
47
|
+
db.query "product" {
|
|
48
|
+
return = { type: "list", paging: { page: $input.page, per_page: $input.per_page } }
|
|
49
|
+
} as $products
|
|
50
|
+
}
|
|
51
|
+
response = $products
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Authentication
|
|
58
|
+
|
|
59
|
+
### Public Endpoint (default)
|
|
60
|
+
```xs
|
|
61
|
+
query "status" verb=GET {
|
|
62
|
+
stack { }
|
|
63
|
+
response = { status: "ok" }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Authenticated Endpoint
|
|
68
|
+
```xs
|
|
69
|
+
query "profile" verb=GET {
|
|
70
|
+
auth = "user" # Requires valid JWT
|
|
71
|
+
stack {
|
|
72
|
+
db.get "user" {
|
|
73
|
+
field_name = "id"
|
|
74
|
+
field_value = $auth.id # User ID from token
|
|
75
|
+
} as $user
|
|
76
|
+
}
|
|
77
|
+
response = $user
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
When `auth` is set:
|
|
82
|
+
- Endpoint requires Bearer token in `Authorization` header
|
|
83
|
+
- `$auth.id` contains authenticated user's ID
|
|
84
|
+
- Invalid/missing token returns 401
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Path Parameters
|
|
89
|
+
|
|
90
|
+
Use `{param}` in the path:
|
|
91
|
+
|
|
92
|
+
```xs
|
|
93
|
+
query "users/{user_id}" verb=GET {
|
|
94
|
+
auth = "user"
|
|
95
|
+
input {
|
|
96
|
+
int user_id { table = "user" }
|
|
97
|
+
}
|
|
98
|
+
stack {
|
|
99
|
+
db.get "user" {
|
|
100
|
+
field_name = "id"
|
|
101
|
+
field_value = $input.user_id
|
|
102
|
+
} as $user
|
|
103
|
+
}
|
|
104
|
+
response = $user
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## CRUD Examples
|
|
111
|
+
|
|
112
|
+
### List (GET)
|
|
113
|
+
```xs
|
|
114
|
+
query "products" verb=GET {
|
|
115
|
+
input {
|
|
116
|
+
text category? filters=trim|lower
|
|
117
|
+
int page?=1
|
|
118
|
+
int per_page?=20
|
|
119
|
+
}
|
|
120
|
+
stack {
|
|
121
|
+
db.query "product" {
|
|
122
|
+
where = $db.product.category ==? $input.category
|
|
123
|
+
sort = { created_at: "desc" }
|
|
124
|
+
return = { type: "list", paging: { page: $input.page, per_page: $input.per_page } }
|
|
125
|
+
} as $products
|
|
126
|
+
}
|
|
127
|
+
response = $products
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Create (POST)
|
|
132
|
+
```xs
|
|
133
|
+
query "products" verb=POST {
|
|
134
|
+
auth = "user"
|
|
135
|
+
input {
|
|
136
|
+
text name filters=trim
|
|
137
|
+
text description? filters=trim
|
|
138
|
+
decimal price filters=min:0
|
|
139
|
+
int category_id { table = "category" }
|
|
140
|
+
}
|
|
141
|
+
stack {
|
|
142
|
+
db.add "product" {
|
|
143
|
+
data = {
|
|
144
|
+
name: $input.name,
|
|
145
|
+
description: $input.description,
|
|
146
|
+
price: $input.price,
|
|
147
|
+
category_id: $input.category_id,
|
|
148
|
+
created_by: $auth.id
|
|
149
|
+
}
|
|
150
|
+
} as $product
|
|
151
|
+
}
|
|
152
|
+
response = $product
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Read (GET with ID)
|
|
157
|
+
```xs
|
|
158
|
+
query "products/{product_id}" verb=GET {
|
|
159
|
+
input {
|
|
160
|
+
int product_id { table = "product" }
|
|
161
|
+
}
|
|
162
|
+
stack {
|
|
163
|
+
db.get "product" {
|
|
164
|
+
field_name = "id"
|
|
165
|
+
field_value = $input.product_id
|
|
166
|
+
} as $product
|
|
167
|
+
|
|
168
|
+
precondition ($product != null) {
|
|
169
|
+
error_type = "notfound"
|
|
170
|
+
error = "Product not found"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
response = $product
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Update (PATCH)
|
|
178
|
+
```xs
|
|
179
|
+
query "products/{product_id}" verb=PATCH {
|
|
180
|
+
auth = "user"
|
|
181
|
+
input {
|
|
182
|
+
int product_id { table = "product" }
|
|
183
|
+
text name? filters=trim
|
|
184
|
+
text description? filters=trim
|
|
185
|
+
decimal price? filters=min:0
|
|
186
|
+
}
|
|
187
|
+
stack {
|
|
188
|
+
var $updates { value = {} }
|
|
189
|
+
|
|
190
|
+
conditional {
|
|
191
|
+
if ($input.name != null) {
|
|
192
|
+
var.update $updates { value = $updates|set:"name":$input.name }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
conditional {
|
|
196
|
+
if ($input.price != null) {
|
|
197
|
+
var.update $updates { value = $updates|set:"price":$input.price }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
db.patch "product" {
|
|
202
|
+
field_name = "id"
|
|
203
|
+
field_value = $input.product_id
|
|
204
|
+
data = $updates
|
|
205
|
+
} as $product
|
|
206
|
+
}
|
|
207
|
+
response = $product
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Delete (DELETE)
|
|
212
|
+
```xs
|
|
213
|
+
query "products/{product_id}" verb=DELETE {
|
|
214
|
+
auth = "user"
|
|
215
|
+
input {
|
|
216
|
+
int product_id { table = "product" }
|
|
217
|
+
}
|
|
218
|
+
stack {
|
|
219
|
+
db.del "product" {
|
|
220
|
+
field_name = "id"
|
|
221
|
+
field_value = $input.product_id
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
response = { success: true }
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Response Types
|
|
231
|
+
|
|
232
|
+
### JSON (default)
|
|
233
|
+
```xs
|
|
234
|
+
response = $data
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### HTML
|
|
238
|
+
```xs
|
|
239
|
+
stack {
|
|
240
|
+
util.set_header {
|
|
241
|
+
value = "Content-Type: text/html; charset=utf-8"
|
|
242
|
+
duplicates = "replace"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
util.template_engine {
|
|
246
|
+
value = """
|
|
247
|
+
<html>
|
|
248
|
+
<body><h1>{{ $var.title }}</h1></body>
|
|
249
|
+
</html>
|
|
250
|
+
"""
|
|
251
|
+
} as $html
|
|
252
|
+
}
|
|
253
|
+
response = $html
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Streaming
|
|
257
|
+
```xs
|
|
258
|
+
stack {
|
|
259
|
+
api.stream { value = $processed_data }
|
|
260
|
+
}
|
|
261
|
+
response = null
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Custom Headers
|
|
267
|
+
|
|
268
|
+
```xs
|
|
269
|
+
stack {
|
|
270
|
+
util.set_header {
|
|
271
|
+
value = "X-Custom-Header: value"
|
|
272
|
+
duplicates = "replace"
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
util.set_header {
|
|
276
|
+
value = "Set-Cookie: session=abc123; HttpOnly; Secure"
|
|
277
|
+
duplicates = "add"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Error Handling
|
|
285
|
+
|
|
286
|
+
### Preconditions
|
|
287
|
+
```xs
|
|
288
|
+
stack {
|
|
289
|
+
precondition ($input.amount > 0) {
|
|
290
|
+
error_type = "inputerror"
|
|
291
|
+
error = "Amount must be positive"
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
precondition ($user != null) {
|
|
295
|
+
error_type = "notfound"
|
|
296
|
+
error = "User not found"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
precondition ($user.id == $auth.id) {
|
|
300
|
+
error_type = "accessdenied"
|
|
301
|
+
error = "Not authorized"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Error Types
|
|
307
|
+
| Type | HTTP Status |
|
|
308
|
+
|------|-------------|
|
|
309
|
+
| `inputerror` | 400 Bad Request |
|
|
310
|
+
| `accessdenied` | 403 Forbidden |
|
|
311
|
+
| `notfound` | 404 Not Found |
|
|
312
|
+
| `standard` | 500 Internal Server Error |
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Pagination Response Format
|
|
317
|
+
|
|
318
|
+
When using `return = { type: "list", paging: {...} }`:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"itemsReceived": 20,
|
|
323
|
+
"curPage": 1,
|
|
324
|
+
"nextPage": 2,
|
|
325
|
+
"prevPage": null,
|
|
326
|
+
"offset": 0,
|
|
327
|
+
"perPage": 20,
|
|
328
|
+
"items": [...]
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Best Practices
|
|
335
|
+
|
|
336
|
+
1. **RESTful design** - Use appropriate HTTP methods
|
|
337
|
+
2. **Consistent naming** - Use lowercase, hyphens for multi-word paths
|
|
338
|
+
3. **Authenticate sensitive operations** - Always auth for writes
|
|
339
|
+
4. **Validate inputs** - Use filters and preconditions
|
|
340
|
+
5. **Return appropriate errors** - Use correct error types
|
|
341
|
+
6. **Paginate lists** - Never return unbounded lists
|
|
342
|
+
7. **Document with description** - Explain what endpoint does
|
|
343
|
+
8. **Group related endpoints** - Organize by resource in api groups
|