@xano/developer-mcp 1.0.58 → 1.0.60
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 +13 -44
- 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 +10 -2
- package/dist/xanoscript_docs/database.md +3 -7
- package/dist/xanoscript_docs/debugging.md +9 -262
- 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 +4 -8
- package/dist/xanoscript_docs/mcp-servers.md +1 -2
- package/dist/xanoscript_docs/middleware.md +12 -3
- package/dist/xanoscript_docs/performance.md +8 -198
- package/dist/xanoscript_docs/realtime.md +21 -161
- package/dist/xanoscript_docs/run.md +2 -184
- package/dist/xanoscript_docs/schema.md +11 -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 +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# XanoScript Working Reference
|
|
6
|
+
|
|
7
|
+
> Complete reference for common XanoScript development. Call `xanoscript_docs({ topic: "<topic>" })` for specialized topics.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
### Mental Model
|
|
12
|
+
|
|
13
|
+
- Declarative block syntax, not imperative statements
|
|
14
|
+
- One construct per `.xs` file (function, query, table, task, agent, tool, etc.)
|
|
15
|
+
- Pipe `|` applies filters, `~` concatenates strings, `$input.x` accesses parameters
|
|
16
|
+
|
|
17
|
+
### Type Names (CRITICAL)
|
|
18
|
+
|
|
19
|
+
| Use This | NOT This |
|
|
20
|
+
|----------|----------|
|
|
21
|
+
| `text` | ~~string~~ |
|
|
22
|
+
| `int` | ~~integer~~ |
|
|
23
|
+
| `bool` | ~~boolean~~ |
|
|
24
|
+
| `decimal` | ~~float/number~~ |
|
|
25
|
+
| `type[]` | ~~array/list~~ |
|
|
26
|
+
| `json` | ~~object/any~~ |
|
|
27
|
+
|
|
28
|
+
### Reserved Names
|
|
29
|
+
|
|
30
|
+
Cannot be used as variable names: `$response`, `$output`, `$input`, `$auth`, `$env`, `$db`, `$this`, `$result`, `$index`
|
|
31
|
+
|
|
32
|
+
### Variable Access
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$input.field — input parameters (ALWAYS use $input. prefix, never bare $field)
|
|
36
|
+
$var.field — stack variables (shorthand $field also works)
|
|
37
|
+
$auth.id — authenticated user
|
|
38
|
+
$env.MY_VAR — environment variable
|
|
39
|
+
$db.table.field — database field reference (in where clauses)
|
|
40
|
+
$this — current item in loops/maps
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Operators
|
|
44
|
+
|
|
45
|
+
| Category | Operators |
|
|
46
|
+
|----------|-----------|
|
|
47
|
+
| Comparison | `==`, `!=`, `>`, `<`, `>=`, `<=` |
|
|
48
|
+
| Logical | `&&`, `\|\|`, `!` |
|
|
49
|
+
| Math | `+`, `-`, `*`, `/`, `%` |
|
|
50
|
+
| String concat | `~` |
|
|
51
|
+
| Null-safe | `==?`, `!=?`, `>=?`, `<=?` (ignore if null) |
|
|
52
|
+
| Nullish coalescing | `??` |
|
|
53
|
+
| Ternary | `$cond ? "yes" : "no"` |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Common Mistakes
|
|
58
|
+
|
|
59
|
+
### 1. Using `else if` instead of `elseif`
|
|
60
|
+
```xs
|
|
61
|
+
// WRONG: conditional { if (...) { } else if (...) { } }
|
|
62
|
+
// RIGHT:
|
|
63
|
+
conditional {
|
|
64
|
+
if (...) { }
|
|
65
|
+
elseif (...) { }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Missing parentheses in filter concatenation
|
|
70
|
+
```xs
|
|
71
|
+
// WRONG: var $msg { value = $status|to_text ~ " - " ~ $data|json_encode }
|
|
72
|
+
// RIGHT:
|
|
73
|
+
var $msg { value = ($status|to_text) ~ " - " ~ ($data|json_encode) }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Missing parentheses in filter comparisons
|
|
77
|
+
```xs
|
|
78
|
+
// WRONG: if ($array|count > 0) { }
|
|
79
|
+
// RIGHT:
|
|
80
|
+
if (($array|count) > 0) { }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Using `body` instead of `params` for api.request
|
|
84
|
+
```xs
|
|
85
|
+
// WRONG: api.request { url = "..." method = "POST" body = $payload }
|
|
86
|
+
// RIGHT:
|
|
87
|
+
api.request { url = "..." method = "POST" params = $payload } as $result
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 5. Using `default` filter (doesn't exist)
|
|
91
|
+
```xs
|
|
92
|
+
// WRONG: var $value { value = $input.optional|default:"fallback" }
|
|
93
|
+
// RIGHT:
|
|
94
|
+
var $value { value = $input.optional ?? "fallback" }
|
|
95
|
+
// or: var $value { value = $input.optional|first_notnull:"fallback" }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 6. Using reserved variable names
|
|
99
|
+
```xs
|
|
100
|
+
// WRONG: var $response { value = "test" }
|
|
101
|
+
// RIGHT:
|
|
102
|
+
var $api_response { value = "test" }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 7. Wrong type names
|
|
106
|
+
```xs
|
|
107
|
+
// WRONG: input { boolean active integer count string name }
|
|
108
|
+
// RIGHT:
|
|
109
|
+
input { bool active int count text name }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 8. Object literal syntax (using = instead of :)
|
|
113
|
+
```xs
|
|
114
|
+
// WRONG: var $data { value = { customer = $id } }
|
|
115
|
+
// RIGHT:
|
|
116
|
+
var $data { value = { customer: $id } }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 9. Block properties vs object literals
|
|
120
|
+
```xs
|
|
121
|
+
// Block properties: = with NO commas, separate lines
|
|
122
|
+
throw {
|
|
123
|
+
name = "Error"
|
|
124
|
+
value = "message"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Object literals: : with commas
|
|
128
|
+
var $obj { value = { name: "Alice", age: 30 } }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 10. Using $env in run.job input blocks
|
|
132
|
+
```xs
|
|
133
|
+
// WRONG: run.job "my_job" { input { text api_key = $env.API_KEY } }
|
|
134
|
+
// RIGHT — access $env in the stack:
|
|
135
|
+
run.job "my_job" { stack { var $api_key { value = $env.API_KEY } } }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 11. Using `object` type without schema
|
|
139
|
+
```xs
|
|
140
|
+
// WRONG: input { object data }
|
|
141
|
+
// RIGHT — use json for arbitrary data:
|
|
142
|
+
input { json data }
|
|
143
|
+
// Or define a schema:
|
|
144
|
+
input { object data { schema { text name int id } } }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 12. While loop outside of stack block
|
|
148
|
+
```xs
|
|
149
|
+
// WRONG: while (true) { each { ... } }
|
|
150
|
+
// RIGHT:
|
|
151
|
+
stack { while (true) { each { ... } } }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 13. Multiple constructs in one file
|
|
155
|
+
```
|
|
156
|
+
// WRONG — two constructs in one file:
|
|
157
|
+
// function "helper_a" { ... }
|
|
158
|
+
// function "helper_b" { ... }
|
|
159
|
+
// RIGHT — one construct per file (function/helper_a.xs, function/helper_b.xs)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Filters Reference
|
|
165
|
+
|
|
166
|
+
### String Filters
|
|
167
|
+
trim: remove whitespace — `$s|trim`
|
|
168
|
+
ltrim, rtrim: left/right trim — `$s|ltrim`
|
|
169
|
+
to_lower, to_upper: case — `$s|to_lower`
|
|
170
|
+
capitalize: capitalize first — `$s|capitalize`
|
|
171
|
+
strlen: string length — `$s|strlen` (NOT count)
|
|
172
|
+
substr:start:len: substring — `$s|substr:0:5` (NOT slice)
|
|
173
|
+
split:delim: split to array — `$s|split:","`
|
|
174
|
+
replace:old:new: replace — `$s|replace:"foo":"bar"`
|
|
175
|
+
contains:str: check substring — `$s|contains:"@"`
|
|
176
|
+
starts_with, ends_with: prefix/suffix — `$s|starts_with:"http"`
|
|
177
|
+
pad_start:len:char, pad_end: pad — `$s|pad_start:5:"0"`
|
|
178
|
+
|
|
179
|
+
### Array Filters
|
|
180
|
+
first, last: endpoints — `$arr|first`
|
|
181
|
+
count: array length — `$arr|count` (NOT strlen)
|
|
182
|
+
slice:start:end: sub-array — `$arr|slice:0:3` (NOT substr)
|
|
183
|
+
reverse: reverse — `$arr|reverse`
|
|
184
|
+
sort:field:dir: sort — `$arr|sort:"name":"asc"`
|
|
185
|
+
unique: deduplicate — `$arr|unique`
|
|
186
|
+
flatten: flatten nested — `$arr|flatten`
|
|
187
|
+
map:expr: transform — `$arr|map:$$.name`
|
|
188
|
+
filter:expr: keep matching — `$arr|filter:$$.active`
|
|
189
|
+
find:expr: first match — `$arr|find:$$.id == 5`
|
|
190
|
+
some:expr: any match? — `$arr|some:$$.active`
|
|
191
|
+
every:expr: all match? — `$arr|every:$$.valid`
|
|
192
|
+
reduce:init:expr: accumulate — `$arr|reduce:0:$$ + $$2`
|
|
193
|
+
join:delim: array to string — `$arr|join:","`
|
|
194
|
+
push:val: append — `$arr|push:"new"`
|
|
195
|
+
|
|
196
|
+
### Object Filters
|
|
197
|
+
get:key: get property — `$obj|get:"key"` or `$obj|get:"key":"default"`
|
|
198
|
+
set:key:val: set property — `$obj|set:"key":"value"`
|
|
199
|
+
has:key: check key — `$obj|has:"key"`
|
|
200
|
+
keys: get keys — `$obj|keys`
|
|
201
|
+
values: get values — `$obj|values`
|
|
202
|
+
set_ifnotnull:key:val: set if not null — `$obj|set_ifnotnull:"k":$val`
|
|
203
|
+
|
|
204
|
+
### Type Conversion
|
|
205
|
+
to_text: to string — `$n|to_text`
|
|
206
|
+
to_int: to integer — `$s|to_int`
|
|
207
|
+
to_decimal: to float — `$s|to_decimal`
|
|
208
|
+
to_bool: to boolean — `$s|to_bool`
|
|
209
|
+
json_encode: to JSON string — `$obj|json_encode`
|
|
210
|
+
json_decode: from JSON string — `$s|json_decode`
|
|
211
|
+
|
|
212
|
+
### Null/Empty Handling
|
|
213
|
+
is_null: check null — `$v|is_null`
|
|
214
|
+
is_empty: check empty — `$v|is_empty`
|
|
215
|
+
first_notnull:default: fallback — `$v|first_notnull:"default"`
|
|
216
|
+
first_notempty:default: fallback — `$v|first_notempty:"fallback"`
|
|
217
|
+
Nullish coalescing: `$v ?? "default"`
|
|
218
|
+
|
|
219
|
+
### Date/Time
|
|
220
|
+
now — current timestamp
|
|
221
|
+
now|format_timestamp:"Y-m-d":"UTC" — format
|
|
222
|
+
now|transform_timestamp:"-1 day" — offset
|
|
223
|
+
now|to_ms — milliseconds
|
|
224
|
+
|
|
225
|
+
### Math
|
|
226
|
+
round:precision — `$n|round:2`
|
|
227
|
+
ceil, floor — `$n|ceil`
|
|
228
|
+
abs — `$n|abs`
|
|
229
|
+
min:val, max:val — `$n|min:0`
|
|
230
|
+
sum — `$arr|sum` (sum array of numbers)
|
|
231
|
+
avg — `$arr|avg`
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Variables & Input Blocks
|
|
236
|
+
|
|
237
|
+
```xs
|
|
238
|
+
// Declare
|
|
239
|
+
var $name { value = "initial" }
|
|
240
|
+
var $items { value = [] }
|
|
241
|
+
var $data { value = { key: "value" } }
|
|
242
|
+
|
|
243
|
+
// Update
|
|
244
|
+
var.update $name { value = "new value" }
|
|
245
|
+
|
|
246
|
+
// Input block modifiers
|
|
247
|
+
input {
|
|
248
|
+
text name // required, not nullable
|
|
249
|
+
text? bio // required, nullable (can send null)
|
|
250
|
+
text role?="user" // optional with default
|
|
251
|
+
text? note? // optional and nullable
|
|
252
|
+
email contact filters=trim // with validation filter
|
|
253
|
+
text[] tags // array type
|
|
254
|
+
object address { // nested object
|
|
255
|
+
schema {
|
|
256
|
+
text street
|
|
257
|
+
text city
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Common validation filters: `trim`, `to_lower`, `to_upper`, `sanitize_html`, `strip_tags`
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Conditional Logic
|
|
268
|
+
|
|
269
|
+
```xs
|
|
270
|
+
// if / elseif / else (MUST use elseif, NOT else if)
|
|
271
|
+
conditional {
|
|
272
|
+
if ($input.age >= 18) { var $status { value = "adult" } }
|
|
273
|
+
elseif ($input.age >= 13) { var $status { value = "teen" } }
|
|
274
|
+
else { var $status { value = "child" } }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// switch
|
|
278
|
+
switch ($input.status) {
|
|
279
|
+
case ("active") { var $msg { value = "Active" } } break
|
|
280
|
+
case ("pending") { var $msg { value = "Pending" } } break
|
|
281
|
+
default { var $msg { value = "Unknown" } }
|
|
282
|
+
}
|
|
283
|
+
// Note: break goes AFTER the closing } of each case
|
|
284
|
+
|
|
285
|
+
// precondition (guard clause — stops execution if false)
|
|
286
|
+
precondition ($input.id > 0) {
|
|
287
|
+
error_type = "inputerror"
|
|
288
|
+
error = "ID must be positive"
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// early return
|
|
292
|
+
conditional {
|
|
293
|
+
if ($input.skip) {
|
|
294
|
+
return { value = null }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Error Types
|
|
300
|
+
|
|
301
|
+
| Type | HTTP Status | Use Case |
|
|
302
|
+
|------|-------------|----------|
|
|
303
|
+
| `inputerror` | 400 | Invalid input data |
|
|
304
|
+
| `accessdenied` | 403 | Authorization failure |
|
|
305
|
+
| `notfound` | 404 | Resource doesn't exist |
|
|
306
|
+
| `standard` | 500 | General errors |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Loops
|
|
311
|
+
|
|
312
|
+
```xs
|
|
313
|
+
// foreach — iterate array
|
|
314
|
+
foreach ($input.items) {
|
|
315
|
+
each as $item {
|
|
316
|
+
debug.log { value = $item.name }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// for — iterate N times
|
|
321
|
+
for (10) {
|
|
322
|
+
each as $idx {
|
|
323
|
+
debug.log { value = $idx }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// while — MUST be inside stack block
|
|
328
|
+
stack {
|
|
329
|
+
var $counter { value = 0 }
|
|
330
|
+
while ($counter < 10) {
|
|
331
|
+
each {
|
|
332
|
+
var.update $counter { value = $counter + 1 }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// map/filter (inline, returns new array)
|
|
338
|
+
var $names { value = $items|map:$$.name }
|
|
339
|
+
var $active { value = $items|filter:$$.is_active }
|
|
340
|
+
var $found { value = $items|find:$$.id == $input.target_id }
|
|
341
|
+
|
|
342
|
+
// break/continue
|
|
343
|
+
foreach ($items) {
|
|
344
|
+
each as $item {
|
|
345
|
+
conditional {
|
|
346
|
+
if ($item.skip) { continue }
|
|
347
|
+
if ($item.done) { break }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Functions
|
|
356
|
+
|
|
357
|
+
```xs
|
|
358
|
+
// Define
|
|
359
|
+
function "calculate_total" {
|
|
360
|
+
input {
|
|
361
|
+
decimal[] prices
|
|
362
|
+
decimal tax_rate?=0.1
|
|
363
|
+
}
|
|
364
|
+
stack {
|
|
365
|
+
var $subtotal { value = $input.prices|sum }
|
|
366
|
+
var $tax { value = $subtotal * $input.tax_rate }
|
|
367
|
+
}
|
|
368
|
+
response = { subtotal: $subtotal, tax: $tax, total: $subtotal + $tax }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Call
|
|
372
|
+
function.run "calculate_total" {
|
|
373
|
+
input = { prices: [10, 20, 30], tax_rate: 0.08 }
|
|
374
|
+
} as $result
|
|
375
|
+
|
|
376
|
+
// Async (fire and forget)
|
|
377
|
+
function.run "send_notification" {
|
|
378
|
+
async = true
|
|
379
|
+
input = { user_id: $auth.id, message: "Done" }
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
> For async patterns, recursion, and advanced function features: `xanoscript_docs({ topic: "functions" })`
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Database Operations
|
|
388
|
+
|
|
389
|
+
### Choosing an Operation
|
|
390
|
+
```
|
|
391
|
+
Read: db.get (single by ID) | db.has (exists?) | db.query (filtered list)
|
|
392
|
+
Write: db.add (insert) | db.edit (update known fields) | db.patch (dynamic fields) | db.add_or_edit (upsert)
|
|
393
|
+
Delete: db.del (single) | db.truncate (all)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### db.get — Single record
|
|
397
|
+
```xs
|
|
398
|
+
db.get "user" { field_name = "id" field_value = $input.user_id } as $user
|
|
399
|
+
db.get "user" { field_name = "email" field_value = $input.email } as $user
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### db.has — Check existence
|
|
403
|
+
```xs
|
|
404
|
+
db.has "user" { field_name = "email" field_value = $input.email } as $exists
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### db.query — Filtered list
|
|
408
|
+
```xs
|
|
409
|
+
db.query "product" {
|
|
410
|
+
where = $db.product.is_active == true && $db.product.price > 0
|
|
411
|
+
sort = { created_at: "desc" }
|
|
412
|
+
return = { type: "list", paging: { page: $input.page, per_page: 25, totals: true } }
|
|
413
|
+
} as $products
|
|
414
|
+
|
|
415
|
+
// Return types: "list" (default), "single", "count", "exists"
|
|
416
|
+
// Null-safe where: $db.product.category ==? $input.category (ignores if null)
|
|
417
|
+
// String matching: $db.product.name includes "phone"
|
|
418
|
+
// Array contains: $db.product.tags contains "featured"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### db.query with join
|
|
422
|
+
```xs
|
|
423
|
+
db.query "comment" {
|
|
424
|
+
join = {
|
|
425
|
+
post: { table: "post", type: "inner", where: $db.comment.post_id == $db.post.id }
|
|
426
|
+
}
|
|
427
|
+
eval = { post_title: $db.post.title }
|
|
428
|
+
where = $db.post.author_id == $auth.id
|
|
429
|
+
} as $comments
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### db.add — Insert
|
|
433
|
+
```xs
|
|
434
|
+
db.add "product" {
|
|
435
|
+
data = { name: $input.name, price: $input.price, created_at: now }
|
|
436
|
+
} as $product
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### db.edit — Update (inline data)
|
|
440
|
+
```xs
|
|
441
|
+
db.edit "product" {
|
|
442
|
+
field_name = "id"
|
|
443
|
+
field_value = $input.product_id
|
|
444
|
+
data = { name: $input.name, price: $input.price, updated_at: now }
|
|
445
|
+
} as $product
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### db.patch — Update (dynamic fields)
|
|
449
|
+
```xs
|
|
450
|
+
var $updates { value = { updated_at: now } }
|
|
451
|
+
conditional {
|
|
452
|
+
if ($input.name != null) {
|
|
453
|
+
var.update $updates { value = $updates|set:"name":$input.name }
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
db.patch "product" { field_name = "id" field_value = $input.id data = $updates } as $product
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### db.del — Delete
|
|
460
|
+
```xs
|
|
461
|
+
db.del "product" { field_name = "id" field_value = $input.product_id }
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### db.transaction — Atomic operations
|
|
465
|
+
```xs
|
|
466
|
+
db.transaction {
|
|
467
|
+
stack {
|
|
468
|
+
db.add "order" { data = { user_id: $auth.id, total: $total } } as $order
|
|
469
|
+
foreach ($input.items) {
|
|
470
|
+
each as $item {
|
|
471
|
+
db.add "order_item" { data = { order_id: $order.id, product_id: $item.product_id, quantity: $item.quantity } }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
> For bulk operations, direct SQL, vector search, and advanced queries: `xanoscript_docs({ topic: "database" })`
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## API Endpoints
|
|
483
|
+
|
|
484
|
+
```xs
|
|
485
|
+
// GET endpoint
|
|
486
|
+
query "users" verb=GET {
|
|
487
|
+
api_group = "users"
|
|
488
|
+
auth = "none" // "none", "user", "session", "api_key"
|
|
489
|
+
input { int page?=1 int per_page?=25 }
|
|
490
|
+
stack {
|
|
491
|
+
db.query "user" {
|
|
492
|
+
where = $db.user.is_active == true
|
|
493
|
+
sort = { created_at: "desc" }
|
|
494
|
+
return = { type: "list", paging: { page: $input.page, per_page: $input.per_page } }
|
|
495
|
+
} as $users
|
|
496
|
+
}
|
|
497
|
+
response = $users
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// GET single
|
|
501
|
+
query "users/{user_id}" verb=GET {
|
|
502
|
+
api_group = "users"
|
|
503
|
+
auth = "user"
|
|
504
|
+
input { int user_id }
|
|
505
|
+
stack {
|
|
506
|
+
db.get "user" { field_name = "id" field_value = $input.user_id } as $user
|
|
507
|
+
precondition ($user != null) { error_type = "notfound" error = "User not found" }
|
|
508
|
+
}
|
|
509
|
+
response = $user
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// POST create
|
|
513
|
+
query "users" verb=POST {
|
|
514
|
+
api_group = "users"
|
|
515
|
+
auth = "user"
|
|
516
|
+
input { text name email email }
|
|
517
|
+
stack {
|
|
518
|
+
db.has "user" { field_name = "email" field_value = $input.email } as $exists
|
|
519
|
+
precondition (!$exists) { error_type = "inputerror" error = "Email already taken" }
|
|
520
|
+
db.add "user" { data = { name: $input.name, email: $input.email, created_at: now } } as $user
|
|
521
|
+
}
|
|
522
|
+
response = $user
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// PATCH update
|
|
526
|
+
query "users/{user_id}" verb=PATCH {
|
|
527
|
+
api_group = "users"
|
|
528
|
+
auth = "user"
|
|
529
|
+
input { int user_id text? name? email? email? }
|
|
530
|
+
stack {
|
|
531
|
+
db.get "user" { field_name = "id" field_value = $input.user_id } as $user
|
|
532
|
+
precondition ($user != null) { error_type = "notfound" error = "Not found" }
|
|
533
|
+
precondition ($user.id == $auth.id) { error_type = "accessdenied" error = "Not your record" }
|
|
534
|
+
var $updates { value = { updated_at: now } }
|
|
535
|
+
conditional {
|
|
536
|
+
if ($input.name != null) { var.update $updates { value = $updates|set:"name":$input.name } }
|
|
537
|
+
}
|
|
538
|
+
db.patch "user" { field_name = "id" field_value = $input.user_id data = $updates } as $updated
|
|
539
|
+
}
|
|
540
|
+
response = $updated
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// DELETE
|
|
544
|
+
query "users/{user_id}" verb=DELETE {
|
|
545
|
+
api_group = "users"
|
|
546
|
+
auth = "user"
|
|
547
|
+
input { int user_id }
|
|
548
|
+
stack {
|
|
549
|
+
db.get "user" { field_name = "id" field_value = $input.user_id } as $user
|
|
550
|
+
precondition ($user != null) { error_type = "notfound" error = "Not found" }
|
|
551
|
+
precondition ($user.id == $auth.id) { error_type = "accessdenied" error = "Not yours" }
|
|
552
|
+
db.del "user" { field_name = "id" field_value = $input.user_id }
|
|
553
|
+
}
|
|
554
|
+
response = { success: true }
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
> For API groups, rate limiting, file uploads, and custom headers: `xanoscript_docs({ topic: "apis" })`
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## Error Handling
|
|
563
|
+
|
|
564
|
+
```xs
|
|
565
|
+
// Precondition (guard — stops execution if false)
|
|
566
|
+
precondition ($input.email|contains:"@") {
|
|
567
|
+
error_type = "inputerror"
|
|
568
|
+
error = "Invalid email"
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Try/catch
|
|
572
|
+
try_catch {
|
|
573
|
+
try {
|
|
574
|
+
api.request { url = "https://api.example.com/data" method = "GET" } as $result
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
var $result { value = { response: { result: null } } }
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Throw custom error
|
|
582
|
+
throw {
|
|
583
|
+
name = "ValidationError"
|
|
584
|
+
value = "Custom error message"
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## External API Calls
|
|
591
|
+
|
|
592
|
+
```xs
|
|
593
|
+
api.request {
|
|
594
|
+
url = "https://api.example.com/endpoint"
|
|
595
|
+
method = "POST"
|
|
596
|
+
params = $payload // NOTE: "params" for body, NOT "body"
|
|
597
|
+
headers = [
|
|
598
|
+
"Content-Type: application/json",
|
|
599
|
+
"Authorization: Bearer " ~ $env.API_KEY
|
|
600
|
+
]
|
|
601
|
+
timeout = 30
|
|
602
|
+
} as $api_result
|
|
603
|
+
|
|
604
|
+
// Response structure:
|
|
605
|
+
// $api_result.response.status → HTTP status code
|
|
606
|
+
// $api_result.response.result → Parsed response body
|
|
607
|
+
// $api_result.response.headers → Response headers
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Common Patterns
|
|
613
|
+
|
|
614
|
+
### Authentication Check
|
|
615
|
+
```xs
|
|
616
|
+
precondition ($auth.id != null) {
|
|
617
|
+
error_type = "accessdenied"
|
|
618
|
+
error = "Authentication required"
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Optional Field Handling
|
|
623
|
+
```xs
|
|
624
|
+
var $data {
|
|
625
|
+
value = { required: $input.required }|set_ifnotnull:"optional":$input.optional
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Building Complex Objects
|
|
630
|
+
```xs
|
|
631
|
+
var $result { value = { user: $user, posts: $posts, count: ($posts|count) } }
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### String Concatenation with Filters
|
|
635
|
+
```xs
|
|
636
|
+
var $msg { value = ($status|to_text) ~ ": " ~ ($data|json_encode) }
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Topic Index
|
|
642
|
+
|
|
643
|
+
| Topic | Description | Size |
|
|
644
|
+
|-------|-------------|------|
|
|
645
|
+
| `essentials` | Common patterns and mistakes | ~15KB |
|
|
646
|
+
| `syntax` | Operators, all filters, system variables | ~17KB |
|
|
647
|
+
| `syntax/string-filters` | String filters, regex, encoding | ~6KB |
|
|
648
|
+
| `syntax/array-filters` | Array filters, functional operations | ~7KB |
|
|
649
|
+
| `types` | Type system, input validation | ~11KB |
|
|
650
|
+
| `database` | All db.* operations, joins, transactions | ~13KB |
|
|
651
|
+
| `functions` | Function stacks, async, recursion | ~9KB |
|
|
652
|
+
| `apis` | HTTP endpoints, authentication | ~12KB |
|
|
653
|
+
| `tables` | Database schema definitions | ~7KB |
|
|
654
|
+
| `tasks` | Scheduled/cron jobs | ~6KB |
|
|
655
|
+
| `triggers` | Event handlers (table, realtime, agent, MCP) | ~17KB |
|
|
656
|
+
| `agents` | AI agent configuration | ~9KB |
|
|
657
|
+
| `tools` | AI tools for agents | ~7KB |
|
|
658
|
+
| `mcp-servers` | MCP server definitions | ~4KB |
|
|
659
|
+
| `security` | Auth patterns, encryption | ~12KB |
|
|
660
|
+
| `integrations` | External APIs, cloud storage, Redis | ~varies |
|
|
661
|
+
| `unit-testing` | Unit tests, mocks, assertions | ~10KB |
|
|
662
|
+
| `workflow-tests` | E2E workflow tests | ~11KB |
|
|
663
|
+
| `debugging` | debug.log, debug.stop | ~6KB |
|
|
664
|
+
| `performance` | Caching, query optimization | ~8KB |
|
|
665
|
+
| `middleware` | Request/response interceptors | ~6KB |
|
|
666
|
+
| `realtime` | WebSocket channels and events | ~9KB |
|
|
667
|
+
| `streaming` | Large file/data streaming | ~8KB |
|
|
@@ -207,5 +207,13 @@ These variables are automatically available without configuration:
|
|
|
207
207
|
1. **Never commit secrets** - Use environment variables for API keys and credentials
|
|
208
208
|
2. **Use descriptive names** - Environment variable names should be self-documenting
|
|
209
209
|
3. **Enable performance tracking** - Helps identify bottlenecks in production
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Related Topics
|
|
214
|
+
|
|
215
|
+
| Topic | Description |
|
|
216
|
+
|-------|-------------|
|
|
217
|
+
| `branch` | Branch-level configuration |
|
|
218
|
+
| `realtime` | Realtime channel configuration |
|
|
219
|
+
| `security` | Environment variable security practices |
|
package/package.json
CHANGED