@xano/developer-mcp 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/README.md +28 -0
- package/dist/index.js +42 -1
- package/dist/templates/xanoscript-index.js +7 -0
- package/package.json +1 -1
- package/xanoscript_docs/README.md +38 -5
- package/xanoscript_docs/addons.md +285 -0
- package/xanoscript_docs/agents.md +84 -0
- package/xanoscript_docs/database.md +160 -0
- package/xanoscript_docs/debugging.md +342 -0
- package/xanoscript_docs/functions.md +172 -0
- package/xanoscript_docs/integrations.md +376 -7
- package/xanoscript_docs/performance.md +407 -0
- package/xanoscript_docs/realtime.md +382 -0
- package/xanoscript_docs/schema.md +292 -0
- package/xanoscript_docs/security.md +550 -0
- package/xanoscript_docs/streaming.md +318 -0
- package/xanoscript_docs/syntax.md +267 -0
- package/xanoscript_docs/triggers.md +354 -52
- package/xanoscript_docs/types.md +66 -21
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "functions/**/*.xs, apis/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Performance Optimization
|
|
6
|
+
|
|
7
|
+
Best practices for building fast, efficient XanoScript applications.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
| Area | Key Techniques |
|
|
12
|
+
|------|----------------|
|
|
13
|
+
| Database | Indexes, query optimization, pagination |
|
|
14
|
+
| Caching | Redis, response caching |
|
|
15
|
+
| Loops | Bulk operations, parallel execution |
|
|
16
|
+
| Filters | Efficient data transformations |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Database Optimization
|
|
21
|
+
|
|
22
|
+
### Use Indexes
|
|
23
|
+
|
|
24
|
+
Ensure frequently queried columns are indexed in your table definitions.
|
|
25
|
+
|
|
26
|
+
```xs
|
|
27
|
+
table "order" {
|
|
28
|
+
int id { primary_key = true }
|
|
29
|
+
int user_id { index = true }
|
|
30
|
+
timestamp created_at { index = true }
|
|
31
|
+
text status { index = true }
|
|
32
|
+
// Compound index for common query pattern
|
|
33
|
+
index = [["user_id", "created_at"]]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Query Only What You Need
|
|
38
|
+
|
|
39
|
+
```xs
|
|
40
|
+
// Good: Select specific fields
|
|
41
|
+
db.query "user" {
|
|
42
|
+
where = $db.user.is_active == true
|
|
43
|
+
select = ["id", "name", "email"]
|
|
44
|
+
} as $users
|
|
45
|
+
|
|
46
|
+
// Avoid: Selecting all fields when only a few are needed
|
|
47
|
+
db.query "user" {
|
|
48
|
+
where = $db.user.is_active == true
|
|
49
|
+
} as $users // Fetches all columns
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Use Appropriate Return Types
|
|
53
|
+
|
|
54
|
+
```xs
|
|
55
|
+
// Need count only? Use count return type
|
|
56
|
+
db.query "order" {
|
|
57
|
+
where = $db.order.user_id == $auth.id
|
|
58
|
+
return = { type: "count" }
|
|
59
|
+
} as $order_count
|
|
60
|
+
|
|
61
|
+
// Need existence check? Use exists return type
|
|
62
|
+
db.query "user" {
|
|
63
|
+
where = $db.user.email == $input.email
|
|
64
|
+
return = { type: "exists" }
|
|
65
|
+
} as $exists
|
|
66
|
+
|
|
67
|
+
// Don't fetch full records just to count them
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Pagination for Large Results
|
|
71
|
+
|
|
72
|
+
```xs
|
|
73
|
+
// Always paginate large result sets
|
|
74
|
+
db.query "product" {
|
|
75
|
+
where = $db.product.is_active == true
|
|
76
|
+
return = {
|
|
77
|
+
type: "list",
|
|
78
|
+
paging: {
|
|
79
|
+
page: $input.page,
|
|
80
|
+
per_page: 25,
|
|
81
|
+
totals: true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
sort = { created_at: "desc" }
|
|
85
|
+
} as $products
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Avoid N+1 Queries
|
|
89
|
+
|
|
90
|
+
```xs
|
|
91
|
+
// Bad: Query inside loop (N+1)
|
|
92
|
+
foreach ($orders) {
|
|
93
|
+
each as $order {
|
|
94
|
+
db.get "user" {
|
|
95
|
+
field_name = "id"
|
|
96
|
+
field_value = $order.user_id
|
|
97
|
+
} as $user
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Good: Use join or batch fetch
|
|
102
|
+
db.query "order" {
|
|
103
|
+
join = {
|
|
104
|
+
user: {
|
|
105
|
+
table: "user",
|
|
106
|
+
where: $db.order.user_id == $db.user.id
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
eval = {
|
|
110
|
+
user_name: $db.user.name,
|
|
111
|
+
user_email: $db.user.email
|
|
112
|
+
}
|
|
113
|
+
} as $orders
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Use Bulk Operations
|
|
117
|
+
|
|
118
|
+
```xs
|
|
119
|
+
// Bad: Individual inserts in loop
|
|
120
|
+
foreach ($items) {
|
|
121
|
+
each as $item {
|
|
122
|
+
db.add "order_item" { data = $item }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Good: Bulk insert
|
|
127
|
+
db.bulk.add "order_item" {
|
|
128
|
+
data = $items
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Caching Strategies
|
|
135
|
+
|
|
136
|
+
### Redis Caching
|
|
137
|
+
|
|
138
|
+
```xs
|
|
139
|
+
// Check cache first
|
|
140
|
+
redis.get { key = "user:" ~ $input.user_id } as $cached
|
|
141
|
+
|
|
142
|
+
conditional {
|
|
143
|
+
if ($cached != null) {
|
|
144
|
+
var $user { value = $cached }
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
db.get "user" {
|
|
148
|
+
field_name = "id"
|
|
149
|
+
field_value = $input.user_id
|
|
150
|
+
} as $user
|
|
151
|
+
|
|
152
|
+
// Cache for 5 minutes
|
|
153
|
+
redis.set {
|
|
154
|
+
key = "user:" ~ $input.user_id
|
|
155
|
+
data = $user
|
|
156
|
+
ttl = 300
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Cache Invalidation
|
|
163
|
+
|
|
164
|
+
```xs
|
|
165
|
+
function "update_user" {
|
|
166
|
+
input { int user_id, object data }
|
|
167
|
+
stack {
|
|
168
|
+
db.patch "user" {
|
|
169
|
+
field_name = "id"
|
|
170
|
+
field_value = $input.user_id
|
|
171
|
+
data = $input.data
|
|
172
|
+
} as $user
|
|
173
|
+
|
|
174
|
+
// Invalidate cache
|
|
175
|
+
redis.del { key = "user:" ~ $input.user_id }
|
|
176
|
+
}
|
|
177
|
+
response = $user
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Computed Value Caching
|
|
182
|
+
|
|
183
|
+
```xs
|
|
184
|
+
// Cache expensive computations
|
|
185
|
+
redis.get { key = "stats:daily:" ~ now|format_timestamp:"Y-m-d" } as $cached_stats
|
|
186
|
+
|
|
187
|
+
conditional {
|
|
188
|
+
if ($cached_stats == null) {
|
|
189
|
+
// Expensive aggregation
|
|
190
|
+
db.query "order" {
|
|
191
|
+
where = $db.order.created_at >= now|transform_timestamp:"start of day"
|
|
192
|
+
} as $orders
|
|
193
|
+
|
|
194
|
+
var $stats {
|
|
195
|
+
value = {
|
|
196
|
+
count: $orders|count,
|
|
197
|
+
total: $orders|map:$$.total|sum,
|
|
198
|
+
avg: $orders|map:$$.total|avg
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
redis.set {
|
|
203
|
+
key = "stats:daily:" ~ now|format_timestamp:"Y-m-d"
|
|
204
|
+
data = $stats
|
|
205
|
+
ttl = 300
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
var $cached_stats { value = $stats }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Parallel Execution
|
|
216
|
+
|
|
217
|
+
### Use Group for Independent Operations
|
|
218
|
+
|
|
219
|
+
```xs
|
|
220
|
+
// Execute in parallel
|
|
221
|
+
group {
|
|
222
|
+
db.query "user" { return = { type: "count" } } as $user_count
|
|
223
|
+
db.query "order" { return = { type: "count" } } as $order_count
|
|
224
|
+
db.query "product" { return = { type: "count" } } as $product_count
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// All counts available after group completes
|
|
228
|
+
response = {
|
|
229
|
+
users: $user_count,
|
|
230
|
+
orders: $order_count,
|
|
231
|
+
products: $product_count
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Parallel API Calls
|
|
236
|
+
|
|
237
|
+
```xs
|
|
238
|
+
group {
|
|
239
|
+
api.request { url = "https://api1.example.com/data" } as $data1
|
|
240
|
+
api.request { url = "https://api2.example.com/data" } as $data2
|
|
241
|
+
api.request { url = "https://api3.example.com/data" } as $data3
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Efficient Data Transformations
|
|
248
|
+
|
|
249
|
+
### Use Filter Chains Wisely
|
|
250
|
+
|
|
251
|
+
```xs
|
|
252
|
+
// Good: Single pass with chained filters
|
|
253
|
+
$data|filter:$$.active|map:$$.name|unique
|
|
254
|
+
|
|
255
|
+
// Avoid: Multiple passes over data
|
|
256
|
+
var $active { value = $data|filter:$$.active }
|
|
257
|
+
var $names { value = $active|map:$$.name }
|
|
258
|
+
var $unique { value = $names|unique }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Early Filtering
|
|
262
|
+
|
|
263
|
+
```xs
|
|
264
|
+
// Filter in database, not in code
|
|
265
|
+
db.query "product" {
|
|
266
|
+
where = $db.product.is_active == true && $db.product.price > 0
|
|
267
|
+
} as $products
|
|
268
|
+
|
|
269
|
+
// Not: Fetch all then filter
|
|
270
|
+
db.query "product" as $all_products
|
|
271
|
+
var $products { value = $all_products|filter:$$.is_active && $$.price > 0 }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Limit Data Transfer
|
|
275
|
+
|
|
276
|
+
```xs
|
|
277
|
+
// Only fetch needed fields from external API
|
|
278
|
+
api.request {
|
|
279
|
+
url = "https://api.example.com/users"
|
|
280
|
+
params = { fields: "id,name,email" }
|
|
281
|
+
} as $response
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Rate Limiting
|
|
287
|
+
|
|
288
|
+
### Protect Endpoints
|
|
289
|
+
|
|
290
|
+
```xs
|
|
291
|
+
redis.ratelimit {
|
|
292
|
+
key = "api:" ~ $auth.id
|
|
293
|
+
max = 100
|
|
294
|
+
ttl = 60
|
|
295
|
+
error = "Rate limit exceeded. Try again in 1 minute."
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Tiered Limits
|
|
300
|
+
|
|
301
|
+
```xs
|
|
302
|
+
// Different limits by user tier
|
|
303
|
+
var $limit {
|
|
304
|
+
value = conditional {
|
|
305
|
+
if ($auth.tier == "premium") { 1000 }
|
|
306
|
+
elseif ($auth.tier == "pro") { 500 }
|
|
307
|
+
else { 100 }
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
redis.ratelimit {
|
|
312
|
+
key = "api:" ~ $auth.id
|
|
313
|
+
max = $limit
|
|
314
|
+
ttl = 60
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Response Optimization
|
|
321
|
+
|
|
322
|
+
### Stream Large Responses
|
|
323
|
+
|
|
324
|
+
```xs
|
|
325
|
+
// Stream instead of loading into memory
|
|
326
|
+
db.query "log" {
|
|
327
|
+
where = $db.log.created_at >= $input.start
|
|
328
|
+
return = { type: "stream" }
|
|
329
|
+
} as $logs
|
|
330
|
+
|
|
331
|
+
api.stream {
|
|
332
|
+
format = "jsonl"
|
|
333
|
+
value = $logs
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Compress Responses
|
|
338
|
+
|
|
339
|
+
Large JSON responses are automatically compressed when clients support it.
|
|
340
|
+
|
|
341
|
+
### Paginate API Responses
|
|
342
|
+
|
|
343
|
+
```xs
|
|
344
|
+
query "list_products" {
|
|
345
|
+
input {
|
|
346
|
+
int page?=1
|
|
347
|
+
int per_page?=25 filters=max:100
|
|
348
|
+
}
|
|
349
|
+
stack {
|
|
350
|
+
db.query "product" {
|
|
351
|
+
return = {
|
|
352
|
+
type: "list",
|
|
353
|
+
paging: {
|
|
354
|
+
page: $input.page,
|
|
355
|
+
per_page: $input.per_page,
|
|
356
|
+
totals: true
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} as $products
|
|
360
|
+
}
|
|
361
|
+
response = $products
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Query Analysis
|
|
368
|
+
|
|
369
|
+
### Log Slow Queries
|
|
370
|
+
|
|
371
|
+
```xs
|
|
372
|
+
var $start { value = now|to_ms }
|
|
373
|
+
|
|
374
|
+
db.query "product" {
|
|
375
|
+
where = $db.product.category == $input.category
|
|
376
|
+
} as $products
|
|
377
|
+
|
|
378
|
+
var $duration { value = (now|to_ms) - $start }
|
|
379
|
+
|
|
380
|
+
conditional {
|
|
381
|
+
if ($duration > 100) {
|
|
382
|
+
debug.log {
|
|
383
|
+
label = "SLOW_QUERY"
|
|
384
|
+
value = {
|
|
385
|
+
query: "product by category",
|
|
386
|
+
duration_ms: $duration,
|
|
387
|
+
result_count: $products|count
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Best Practices Summary
|
|
397
|
+
|
|
398
|
+
1. **Index frequently queried columns** - Check query patterns
|
|
399
|
+
2. **Use appropriate return types** - count, exists, single
|
|
400
|
+
3. **Paginate large results** - Never return unbounded lists
|
|
401
|
+
4. **Avoid N+1 queries** - Use joins or batch fetching
|
|
402
|
+
5. **Use bulk operations** - For batch inserts/updates
|
|
403
|
+
6. **Cache expensive operations** - Redis with appropriate TTL
|
|
404
|
+
7. **Execute in parallel** - Group independent operations
|
|
405
|
+
8. **Filter early** - In database, not application code
|
|
406
|
+
9. **Stream large responses** - Don't load into memory
|
|
407
|
+
10. **Monitor performance** - Log slow operations
|