@xano/developer-mcp 1.0.57 → 1.0.59
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 +15 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/xanoscript_docs.d.ts +9 -0
- package/dist/tools/xanoscript_docs.js +27 -0
- package/dist/xanoscript.d.ts +5 -1
- package/dist/xanoscript.js +70 -6
- package/dist/xanoscript.test.js +5 -3
- package/dist/xanoscript_docs/README.md +9 -43
- package/dist/xanoscript_docs/addons.md +0 -2
- package/dist/xanoscript_docs/agents.md +2 -35
- package/dist/xanoscript_docs/apis.md +3 -6
- package/dist/xanoscript_docs/branch.md +0 -2
- package/dist/xanoscript_docs/database.md +3 -7
- package/dist/xanoscript_docs/debugging.md +1 -264
- package/dist/xanoscript_docs/docs_index.json +22 -0
- package/dist/xanoscript_docs/essentials.md +1 -9
- package/dist/xanoscript_docs/frontend.md +1 -138
- package/dist/xanoscript_docs/functions.md +3 -7
- package/dist/xanoscript_docs/mcp-servers.md +1 -2
- package/dist/xanoscript_docs/middleware.md +1 -3
- package/dist/xanoscript_docs/performance.md +8 -198
- package/dist/xanoscript_docs/realtime.md +11 -161
- package/dist/xanoscript_docs/run.md +2 -184
- package/dist/xanoscript_docs/schema.md +1 -3
- package/dist/xanoscript_docs/security.md +82 -313
- package/dist/xanoscript_docs/streaming.md +2 -37
- package/dist/xanoscript_docs/survival.md +161 -0
- package/dist/xanoscript_docs/syntax.md +0 -6
- package/dist/xanoscript_docs/tables.md +3 -5
- package/dist/xanoscript_docs/tasks.md +1 -3
- package/dist/xanoscript_docs/tools.md +1 -3
- package/dist/xanoscript_docs/triggers.md +3 -69
- package/dist/xanoscript_docs/types.md +3 -4
- package/dist/xanoscript_docs/unit-testing.md +1 -55
- package/dist/xanoscript_docs/workflow-tests.md +8 -35
- package/dist/xanoscript_docs/working.md +667 -0
- package/dist/xanoscript_docs/workspace.md +0 -2
- package/package.json +2 -2
|
@@ -137,143 +137,20 @@ db.bulk.add "order_item" {
|
|
|
137
137
|
|
|
138
138
|
## Caching Strategies
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
```xs
|
|
143
|
-
// Check cache first
|
|
144
|
-
redis.get { key = "user:" ~ $input.user_id } as $cached
|
|
145
|
-
|
|
146
|
-
conditional {
|
|
147
|
-
if ($cached != null) {
|
|
148
|
-
var $user { value = $cached }
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
db.get "user" {
|
|
152
|
-
field_name = "id"
|
|
153
|
-
field_value = $input.user_id
|
|
154
|
-
} as $user
|
|
155
|
-
|
|
156
|
-
// Cache for 5 minutes
|
|
157
|
-
redis.set {
|
|
158
|
-
key = "user:" ~ $input.user_id
|
|
159
|
-
data = $user
|
|
160
|
-
ttl = 300
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Cache Invalidation
|
|
167
|
-
|
|
168
|
-
```xs
|
|
169
|
-
function "update_user" {
|
|
170
|
-
input { int user_id, object data }
|
|
171
|
-
stack {
|
|
172
|
-
db.patch "user" {
|
|
173
|
-
field_name = "id"
|
|
174
|
-
field_value = $input.user_id
|
|
175
|
-
data = $input.data
|
|
176
|
-
} as $user
|
|
177
|
-
|
|
178
|
-
// Invalidate cache
|
|
179
|
-
redis.del { key = "user:" ~ $input.user_id }
|
|
180
|
-
}
|
|
181
|
-
response = $user
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Computed Value Caching
|
|
186
|
-
|
|
187
|
-
```xs
|
|
188
|
-
// Cache expensive computations
|
|
189
|
-
redis.get { key = "stats:daily:" ~ now|format_timestamp:"Y-m-d" } as $cached_stats
|
|
190
|
-
|
|
191
|
-
conditional {
|
|
192
|
-
if ($cached_stats == null) {
|
|
193
|
-
// Expensive aggregation
|
|
194
|
-
db.query "order" {
|
|
195
|
-
where = $db.order.created_at >= now|transform_timestamp:"start of day"
|
|
196
|
-
} as $orders
|
|
197
|
-
|
|
198
|
-
var $stats {
|
|
199
|
-
value = {
|
|
200
|
-
count: $orders|count,
|
|
201
|
-
total: $orders|map:$$.total|sum,
|
|
202
|
-
avg: $orders|map:$$.total|avg
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
redis.set {
|
|
207
|
-
key = "stats:daily:" ~ now|format_timestamp:"Y-m-d"
|
|
208
|
-
data = $stats
|
|
209
|
-
ttl = 300
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
var $cached_stats { value = $stats }
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## Organizing with Group
|
|
220
|
-
|
|
221
|
-
The `group` statement is an organizational block for visually grouping related statements. It does **not** create parallel execution or a new scope — variables declared inside a group are accessible outside it.
|
|
222
|
-
|
|
223
|
-
```xs
|
|
224
|
-
// Use group to organize related variable initialization
|
|
225
|
-
group {
|
|
226
|
-
stack {
|
|
227
|
-
var $total {
|
|
228
|
-
value = 0
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
var $count {
|
|
232
|
-
value = 0
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// $total and $count are accessible here
|
|
238
|
-
```
|
|
140
|
+
> See `xanoscript_docs({ topic: "integrations/redis" })` for full Redis caching patterns (get/set, invalidation, computed value caching).
|
|
239
141
|
|
|
240
142
|
---
|
|
241
143
|
|
|
242
144
|
## Efficient Data Transformations
|
|
243
145
|
|
|
244
|
-
### Use Filter Chains Wisely
|
|
245
|
-
|
|
246
146
|
```xs
|
|
247
147
|
// Good: Single pass with chained filters
|
|
248
148
|
$data|filter:$$.active|map:$$.name|unique
|
|
249
149
|
|
|
250
|
-
// Avoid: Multiple passes over data
|
|
251
|
-
var $active { value = $data|filter:$$.active }
|
|
252
|
-
var $names { value = $active|map:$$.name }
|
|
253
|
-
var $unique { value = $names|unique }
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Early Filtering
|
|
257
|
-
|
|
258
|
-
```xs
|
|
259
150
|
// Filter in database, not in code
|
|
260
151
|
db.query "product" {
|
|
261
152
|
where = $db.product.is_active == true && $db.product.price > 0
|
|
262
153
|
} as $products
|
|
263
|
-
|
|
264
|
-
// Not: Fetch all then filter
|
|
265
|
-
db.query "product" as $all_products
|
|
266
|
-
var $products { value = $all_products|filter:$$.is_active && $$.price > 0 }
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### Limit Data Transfer
|
|
270
|
-
|
|
271
|
-
```xs
|
|
272
|
-
// Only fetch needed fields from external API
|
|
273
|
-
api.request {
|
|
274
|
-
url = "https://api.example.com/users"
|
|
275
|
-
params = { fields: "id,name,email" }
|
|
276
|
-
} as $result
|
|
277
154
|
```
|
|
278
155
|
|
|
279
156
|
---
|
|
@@ -308,92 +185,25 @@ redis.ratelimit {
|
|
|
308
185
|
|
|
309
186
|
## Response Optimization
|
|
310
187
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
```xs
|
|
314
|
-
// Stream instead of loading into memory
|
|
315
|
-
db.query "log" {
|
|
316
|
-
where = $db.log.created_at >= $input.start
|
|
317
|
-
return = { type: "stream" }
|
|
318
|
-
} as $logs
|
|
319
|
-
|
|
320
|
-
api.stream {
|
|
321
|
-
format = "jsonl"
|
|
322
|
-
value = $logs
|
|
323
|
-
}
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Compress Responses
|
|
327
|
-
|
|
328
|
-
Large JSON responses are automatically compressed when clients support it.
|
|
188
|
+
For large responses, use `return = { type: "stream" }` with `api.stream` to avoid loading into memory. Large JSON responses are automatically compressed when clients support it.
|
|
329
189
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
```xs
|
|
333
|
-
query "list_products" {
|
|
334
|
-
input {
|
|
335
|
-
int page?=1
|
|
336
|
-
int per_page?=25 filters=max:100
|
|
337
|
-
}
|
|
338
|
-
stack {
|
|
339
|
-
db.query "product" {
|
|
340
|
-
return = {
|
|
341
|
-
type: "list",
|
|
342
|
-
paging: {
|
|
343
|
-
page: $input.page,
|
|
344
|
-
per_page: $input.per_page,
|
|
345
|
-
totals: true
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
} as $products
|
|
349
|
-
}
|
|
350
|
-
response = $products
|
|
351
|
-
}
|
|
352
|
-
```
|
|
190
|
+
> See `xanoscript_docs({ topic: "streaming" })` for streaming patterns.
|
|
353
191
|
|
|
354
192
|
---
|
|
355
193
|
|
|
356
194
|
## Query Analysis
|
|
357
195
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
```xs
|
|
361
|
-
var $start { value = now|to_ms }
|
|
362
|
-
|
|
363
|
-
db.query "product" {
|
|
364
|
-
where = $db.product.category == $input.category
|
|
365
|
-
} as $products
|
|
366
|
-
|
|
367
|
-
var $duration { value = (now|to_ms) - $start }
|
|
196
|
+
Use `now|to_ms` before and after operations to measure duration, and `debug.log` to log slow queries.
|
|
368
197
|
|
|
369
|
-
|
|
370
|
-
if ($duration > 100) {
|
|
371
|
-
debug.log {
|
|
372
|
-
label = "SLOW_QUERY"
|
|
373
|
-
value = {
|
|
374
|
-
query: "product by category",
|
|
375
|
-
duration_ms: $duration,
|
|
376
|
-
result_count: $products|count
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
```
|
|
198
|
+
> See `xanoscript_docs({ topic: "debugging" })` for timing and logging patterns.
|
|
382
199
|
|
|
383
200
|
---
|
|
384
201
|
|
|
385
202
|
## Best Practices Summary
|
|
386
203
|
|
|
387
|
-
1. **Index frequently queried columns** -
|
|
388
|
-
2. **
|
|
389
|
-
3. **Paginate large results** - Never return unbounded lists
|
|
390
|
-
4. **Avoid N+1 queries** - Use joins or batch fetching
|
|
391
|
-
5. **Use bulk operations** - For batch inserts/updates
|
|
392
|
-
6. **Cache expensive operations** - Redis with appropriate TTL
|
|
393
|
-
7. **Use group for organization** - Group related statements for readability
|
|
394
|
-
8. **Filter early** - In database, not application code
|
|
395
|
-
9. **Stream large responses** - Don't load into memory
|
|
396
|
-
10. **Monitor performance** - Log slow operations
|
|
204
|
+
1. **Index frequently queried columns** - Use `select` for only needed fields; use `count`/`exists` return types
|
|
205
|
+
2. **Avoid N+1 queries** - Use joins or bulk operations (`db.bulk.add`, `db.bulk.edit`)
|
|
206
|
+
3. **Paginate large results** - Never return unbounded lists; cache expensive computations with Redis
|
|
397
207
|
|
|
398
208
|
---
|
|
399
209
|
|
|
@@ -265,135 +265,18 @@ Handle events from connected clients using `realtime_trigger`. For complete trig
|
|
|
265
265
|
|
|
266
266
|
---
|
|
267
267
|
|
|
268
|
-
## Common
|
|
269
|
-
|
|
270
|
-
### Chat Application
|
|
268
|
+
## Common Pattern: Broadcast After Database Write
|
|
271
269
|
|
|
272
270
|
```xs
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
stack {
|
|
279
|
-
// Save message
|
|
280
|
-
db.add "message" {
|
|
281
|
-
data = {
|
|
282
|
-
room_id: $input.room_id,
|
|
283
|
-
sender_id: $auth.id,
|
|
284
|
-
content: $input.content,
|
|
285
|
-
created_at: now
|
|
286
|
-
}
|
|
287
|
-
} as $message
|
|
288
|
-
|
|
289
|
-
// Broadcast to room
|
|
290
|
-
api.realtime_event {
|
|
291
|
-
channel = "room:" ~ $input.room_id
|
|
292
|
-
event = "new_message"
|
|
293
|
-
data = {
|
|
294
|
-
id: $message.id,
|
|
295
|
-
sender_id: $auth.id,
|
|
296
|
-
content: $input.content,
|
|
297
|
-
created_at: $message.created_at
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
response = $message
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Live Dashboard Updates
|
|
306
|
-
|
|
307
|
-
```xs
|
|
308
|
-
function "update_metrics" {
|
|
309
|
-
stack {
|
|
310
|
-
// Calculate metrics
|
|
311
|
-
db.query "order" {
|
|
312
|
-
where = $db.order.created_at >= now|transform_timestamp:"-1 hour"
|
|
313
|
-
} as $recent_orders
|
|
314
|
-
|
|
315
|
-
var $metrics {
|
|
316
|
-
value = {
|
|
317
|
-
orders_last_hour: $recent_orders|count,
|
|
318
|
-
revenue_last_hour: $recent_orders|map:$$.total|sum,
|
|
319
|
-
updated_at: now
|
|
320
|
-
}
|
|
321
|
-
}
|
|
271
|
+
// Save to database, then broadcast to subscribers
|
|
272
|
+
db.add "message" {
|
|
273
|
+
data = { room_id: $input.room_id, sender_id: $auth.id, content: $input.content }
|
|
274
|
+
} as $message
|
|
322
275
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
data = $metrics
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
response = $metrics
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Collaborative Editing
|
|
335
|
-
|
|
336
|
-
```xs
|
|
337
|
-
realtime_trigger "document_edit" {
|
|
338
|
-
channel = "document:*"
|
|
339
|
-
event = "operation"
|
|
340
|
-
stack {
|
|
341
|
-
// Apply operation to document
|
|
342
|
-
function.run "apply_document_op" {
|
|
343
|
-
input = {
|
|
344
|
-
document_id: $input.document_id,
|
|
345
|
-
operation: $input.operation,
|
|
346
|
-
user_id: $auth.id
|
|
347
|
-
}
|
|
348
|
-
} as $result
|
|
349
|
-
|
|
350
|
-
// Broadcast to other editors
|
|
351
|
-
api.realtime_event {
|
|
352
|
-
channel = $channel
|
|
353
|
-
event = "remote_operation"
|
|
354
|
-
data = {
|
|
355
|
-
operation: $input.operation,
|
|
356
|
-
user_id: $auth.id,
|
|
357
|
-
version: $result.version
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Notification System
|
|
365
|
-
|
|
366
|
-
```xs
|
|
367
|
-
function "notify_user" {
|
|
368
|
-
input {
|
|
369
|
-
int user_id
|
|
370
|
-
text type
|
|
371
|
-
text title
|
|
372
|
-
text body
|
|
373
|
-
json? data
|
|
374
|
-
}
|
|
375
|
-
stack {
|
|
376
|
-
// Save notification
|
|
377
|
-
db.add "notification" {
|
|
378
|
-
data = {
|
|
379
|
-
user_id: $input.user_id,
|
|
380
|
-
type: $input.type,
|
|
381
|
-
title: $input.title,
|
|
382
|
-
body: $input.body,
|
|
383
|
-
data: $input.data,
|
|
384
|
-
read: false,
|
|
385
|
-
created_at: now
|
|
386
|
-
}
|
|
387
|
-
} as $notification
|
|
388
|
-
|
|
389
|
-
// Push to user
|
|
390
|
-
api.realtime_event {
|
|
391
|
-
channel = "user:" ~ $input.user_id
|
|
392
|
-
event = "notification"
|
|
393
|
-
data = $notification
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
response = $notification
|
|
276
|
+
api.realtime_event {
|
|
277
|
+
channel = "room:" ~ $input.room_id
|
|
278
|
+
event = "new_message"
|
|
279
|
+
data = $message
|
|
397
280
|
}
|
|
398
281
|
```
|
|
399
282
|
|
|
@@ -401,39 +284,9 @@ function "notify_user" {
|
|
|
401
284
|
|
|
402
285
|
## Subscription Management
|
|
403
286
|
|
|
404
|
-
Clients subscribe to channels client-side. Server controls what events to send.
|
|
287
|
+
Clients subscribe to channels client-side. Server controls what events to send. Validate channel access with preconditions before broadcasting.
|
|
405
288
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
```xs
|
|
409
|
-
// Validate user can access channel before sending
|
|
410
|
-
function "send_to_room" {
|
|
411
|
-
input {
|
|
412
|
-
int room_id
|
|
413
|
-
text event
|
|
414
|
-
json data
|
|
415
|
-
}
|
|
416
|
-
stack {
|
|
417
|
-
// Check membership
|
|
418
|
-
db.query "room_member" {
|
|
419
|
-
where = $db.room_member.room_id == $input.room_id
|
|
420
|
-
&& $db.room_member.user_id == $auth.id
|
|
421
|
-
return = { type: "exists" }
|
|
422
|
-
} as $is_member
|
|
423
|
-
|
|
424
|
-
precondition ($is_member) {
|
|
425
|
-
error_type = "accessdenied"
|
|
426
|
-
error = "Not a member of this room"
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
api.realtime_event {
|
|
430
|
-
channel = "room:" ~ $input.room_id
|
|
431
|
-
event = $input.event
|
|
432
|
-
data = $input.data
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
```
|
|
289
|
+
> See `xanoscript_docs({ topic: "security" })` for authorization patterns.
|
|
437
290
|
|
|
438
291
|
---
|
|
439
292
|
|
|
@@ -442,6 +295,3 @@ function "send_to_room" {
|
|
|
442
295
|
1. **Use channel namespacing** - `type:id` format for clarity
|
|
443
296
|
2. **Keep payloads small** - Send IDs, let client fetch details
|
|
444
297
|
3. **Validate before broadcast** - Don't trust client event data
|
|
445
|
-
4. **Use triggers for client events** - Handle incoming realtime events
|
|
446
|
-
5. **Consider fan-out carefully** - Large broadcasts can be expensive
|
|
447
|
-
6. **Implement presence** - Track who's connected to channels
|
|
@@ -56,16 +56,6 @@ run.job "Random Dad Joke" {
|
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
### With Empty Input (Alternative)
|
|
60
|
-
```xs
|
|
61
|
-
run.job "Random Dad Joke" {
|
|
62
|
-
main = {
|
|
63
|
-
name: "fetch_dad_joke"
|
|
64
|
-
input: {}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
59
|
### With Input Parameters
|
|
70
60
|
```xs
|
|
71
61
|
run.job "Average of values" {
|
|
@@ -134,16 +124,6 @@ run.service "Random Dad Joke" {
|
|
|
134
124
|
}
|
|
135
125
|
```
|
|
136
126
|
|
|
137
|
-
### With Empty Input (Alternative)
|
|
138
|
-
```xs
|
|
139
|
-
run.service "Random Dad Joke" {
|
|
140
|
-
pre = {
|
|
141
|
-
name: "fetch_dad_joke"
|
|
142
|
-
input: {}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
127
|
### With Initialization
|
|
148
128
|
```xs
|
|
149
129
|
run.service "email proxy" {
|
|
@@ -173,169 +153,9 @@ run.service "webhook listener" {
|
|
|
173
153
|
|
|
174
154
|
## Supporting Files
|
|
175
155
|
|
|
176
|
-
Jobs and services can include supporting tables and functions.
|
|
177
|
-
|
|
178
|
-
### Table with Seed Data
|
|
179
|
-
```xs
|
|
180
|
-
table users {
|
|
181
|
-
auth = false
|
|
182
|
-
schema {
|
|
183
|
-
int id
|
|
184
|
-
text name
|
|
185
|
-
text email
|
|
186
|
-
timestamp created_at?=now
|
|
187
|
-
}
|
|
188
|
-
index = [
|
|
189
|
-
{type: "primary", field: [{name: "id"}]}
|
|
190
|
-
]
|
|
191
|
-
items = [
|
|
192
|
-
{"id": 1, "name": "Alice", "email": "alice@example.com"}
|
|
193
|
-
{"id": 2, "name": "Bob", "email": "bob@example.com"}
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Function Definition
|
|
199
|
-
```xs
|
|
200
|
-
function "process_data" {
|
|
201
|
-
input {
|
|
202
|
-
json data
|
|
203
|
-
}
|
|
204
|
-
stack {
|
|
205
|
-
db.query users {
|
|
206
|
-
return = { type: "list" }
|
|
207
|
-
} as $users
|
|
208
|
-
}
|
|
209
|
-
response = $users
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Complete Job Example
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
run.xs
|
|
219
|
-
table/
|
|
220
|
-
└── users.xs
|
|
221
|
-
function/
|
|
222
|
-
└── migrate_users.xs
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### run.xs
|
|
226
|
-
```xs
|
|
227
|
-
run.job "Data Migration" {
|
|
228
|
-
main = {
|
|
229
|
-
name: "migrate_users"
|
|
230
|
-
input: {
|
|
231
|
-
batch_size: 100
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
env = ["source_db_url", "webhook_url"]
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### table/users.xs
|
|
239
|
-
```xs
|
|
240
|
-
table users {
|
|
241
|
-
auth = false
|
|
242
|
-
schema {
|
|
243
|
-
int id
|
|
244
|
-
text name
|
|
245
|
-
text email
|
|
246
|
-
timestamp migrated_at?
|
|
247
|
-
}
|
|
248
|
-
index = [
|
|
249
|
-
{type: "primary", field: [{name: "id"}]}
|
|
250
|
-
]
|
|
251
|
-
items = [
|
|
252
|
-
{"id": 1, "name": "Alice", "email": "alice@example.com"}
|
|
253
|
-
{"id": 2, "name": "Bob", "email": "bob@example.com"}
|
|
254
|
-
]
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### function/migrate_users.xs
|
|
259
|
-
```xs
|
|
260
|
-
function "migrate_users" {
|
|
261
|
-
input {
|
|
262
|
-
int batch_size
|
|
263
|
-
}
|
|
264
|
-
stack {
|
|
265
|
-
db.query users {
|
|
266
|
-
where = $db.users.migrated_at == null
|
|
267
|
-
paging = { page: 1, per_page: $input.batch_size }
|
|
268
|
-
} as $pending
|
|
269
|
-
|
|
270
|
-
foreach ($pending) {
|
|
271
|
-
each as $user {
|
|
272
|
-
db.edit users {
|
|
273
|
-
field_name = "id"
|
|
274
|
-
field_value = $user.id
|
|
275
|
-
data = { migrated_at: now }
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
response = { migrated: $pending|count }
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## Complete Service Example
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
run.xs
|
|
290
|
-
table/
|
|
291
|
-
└── event.xs
|
|
292
|
-
api/
|
|
293
|
-
└── events/
|
|
294
|
-
├── api_group.xs
|
|
295
|
-
├── list_get.xs
|
|
296
|
-
└── add_post.xs
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### run.xs
|
|
300
|
-
```xs
|
|
301
|
-
run.service "Event Tracker" {
|
|
302
|
-
pre = {
|
|
303
|
-
name: "init_tracker"
|
|
304
|
-
input: {}
|
|
305
|
-
}
|
|
306
|
-
env = ["api_key"]
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### table/event.xs
|
|
311
|
-
```xs
|
|
312
|
-
table event {
|
|
313
|
-
auth = false
|
|
314
|
-
schema {
|
|
315
|
-
int id
|
|
316
|
-
timestamp created_at?=now
|
|
317
|
-
text name
|
|
318
|
-
}
|
|
319
|
-
index = [
|
|
320
|
-
{type: "primary", field: [{name: "id"}]}
|
|
321
|
-
]
|
|
322
|
-
items = []
|
|
323
|
-
}
|
|
324
|
-
```
|
|
156
|
+
Jobs and services can include supporting tables and functions in `table/` and `function/` subdirectories.
|
|
325
157
|
|
|
326
|
-
|
|
327
|
-
```xs
|
|
328
|
-
query list verb=GET {
|
|
329
|
-
api_group = "events"
|
|
330
|
-
stack {
|
|
331
|
-
db.query event {
|
|
332
|
-
sort = { created_at: "desc" }
|
|
333
|
-
return = { type: "list" }
|
|
334
|
-
} as $events
|
|
335
|
-
}
|
|
336
|
-
response = $events
|
|
337
|
-
}
|
|
338
|
-
```
|
|
158
|
+
> See `xanoscript_docs({ topic: "tables" })` and `xanoscript_docs({ topic: "functions" })` for table/function syntax.
|
|
339
159
|
|
|
340
160
|
---
|
|
341
161
|
|
|
@@ -364,8 +184,6 @@ query list verb=GET {
|
|
|
364
184
|
1. **Name clearly** - Indicate purpose: `data-migration`, `email-proxy`
|
|
365
185
|
2. **Use env for secrets** - Never hardcode API keys or credentials
|
|
366
186
|
3. **Keep self-contained** - Include all required tables and functions
|
|
367
|
-
4. **Seed test data** - Use `items` in table definitions for testing
|
|
368
|
-
5. **Validate inputs** - Use preconditions in functions for input validation
|
|
369
187
|
|
|
370
188
|
---
|
|
371
189
|
|
|
@@ -287,6 +287,4 @@ try_catch {
|
|
|
287
287
|
|
|
288
288
|
1. **Define schemas upfront** - Use input blocks when structure is known
|
|
289
289
|
2. **Use schema.parse for dynamic data** - External APIs, user-generated content
|
|
290
|
-
3. **Validate at boundaries** - Parse external input, trust internal data
|
|
291
|
-
4. **Provide clear error messages** - Use error_message for user-facing errors
|
|
292
|
-
5. **Use immutable for config** - Prevent accidental modification
|
|
290
|
+
3. **Validate at boundaries** - Parse external input, trust internal data; provide clear error messages
|