@xano/developer-mcp 1.0.54 → 1.0.57
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 +42 -10
- package/dist/cli_docs/index.d.ts +10 -0
- package/dist/cli_docs/index.js +10 -0
- package/dist/index.js +2 -18
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +1 -1
- package/dist/meta_api_docs/index.d.ts +10 -0
- package/dist/meta_api_docs/index.js +10 -0
- package/dist/tools/cli_docs.js +1 -0
- package/dist/tools/index.d.ts +138 -2
- package/dist/tools/index.js +60 -8
- package/dist/tools/index.test.d.ts +1 -0
- package/dist/tools/index.test.js +179 -0
- package/dist/tools/mcp_version.d.ts +10 -0
- package/dist/tools/mcp_version.js +13 -1
- package/dist/tools/meta_api_docs.js +1 -0
- package/dist/tools/types.d.ts +15 -6
- package/dist/tools/types.js +10 -4
- package/dist/tools/validate_xanoscript.d.ts +14 -0
- package/dist/tools/validate_xanoscript.js +22 -5
- package/dist/tools/xanoscript_docs.d.ts +32 -1
- package/dist/tools/xanoscript_docs.js +69 -5
- package/dist/tools/xanoscript_docs.test.d.ts +1 -0
- package/dist/tools/xanoscript_docs.test.js +197 -0
- package/dist/xanoscript.d.ts +17 -1
- package/dist/xanoscript.js +99 -179
- package/dist/xanoscript.test.js +84 -8
- package/dist/xanoscript_docs/README.md +7 -7
- package/dist/xanoscript_docs/cheatsheet.md +4 -1
- package/dist/xanoscript_docs/database.md +2 -2
- package/dist/xanoscript_docs/docs_index.json +186 -108
- package/dist/xanoscript_docs/essentials.md +665 -0
- package/dist/xanoscript_docs/functions.md +1 -1
- package/dist/xanoscript_docs/middleware.md +5 -18
- package/dist/xanoscript_docs/quickstart.md +15 -6
- package/dist/xanoscript_docs/security.md +18 -43
- package/dist/xanoscript_docs/syntax/array-filters.md +238 -0
- package/dist/xanoscript_docs/syntax/functions.md +136 -0
- package/dist/xanoscript_docs/syntax/string-filters.md +188 -0
- package/dist/xanoscript_docs/syntax.md +92 -900
- package/dist/xanoscript_docs/triggers.md +1 -1
- package/dist/xanoscript_docs/types.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# XanoScript Essentials
|
|
6
|
+
|
|
7
|
+
Essential patterns, quick reference, and common mistakes for XanoScript development.
|
|
8
|
+
|
|
9
|
+
> **TL;DR:** Use `text` not `string`, `elseif` not `else if`, `params` not `body` for api.request, parentheses around filters in expressions.
|
|
10
|
+
|
|
11
|
+
## Quick Reference
|
|
12
|
+
|
|
13
|
+
### Variable Access Rules
|
|
14
|
+
|
|
15
|
+
**Inputs must use `$input.fieldname` — no shorthand exists.**
|
|
16
|
+
|
|
17
|
+
```xs
|
|
18
|
+
input {
|
|
19
|
+
text name
|
|
20
|
+
int age
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Wrong — $name is not defined; inputs live on $input
|
|
24
|
+
// var $greeting { value = "Hello, " ~ $name }
|
|
25
|
+
|
|
26
|
+
// Correct
|
|
27
|
+
var $greeting { value = "Hello, " ~ $input.name }
|
|
28
|
+
var $is_adult { value = $input.age >= 18 }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Stack variables have an optional `$var.` prefix — both forms are identical.**
|
|
32
|
+
|
|
33
|
+
```xs
|
|
34
|
+
var $total { value = 100 }
|
|
35
|
+
$var.total // explicit prefix form
|
|
36
|
+
$total // shorthand form (no prefix)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Reserved Variable Names
|
|
40
|
+
|
|
41
|
+
These variable names are reserved and cannot be used: `$response`, `$output`, `$input`, `$auth`, `$env`, `$db`, `$this`, `$result`, `$index`.
|
|
42
|
+
|
|
43
|
+
```xs
|
|
44
|
+
// Wrong - $response is reserved
|
|
45
|
+
// var $response { value = "test" }
|
|
46
|
+
|
|
47
|
+
// Correct - use a different name
|
|
48
|
+
var $api_response { value = "test" }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Type Names
|
|
52
|
+
|
|
53
|
+
XanoScript uses specific type names. Common aliases from other languages won't work.
|
|
54
|
+
|
|
55
|
+
> **Full reference:** See `xanoscript_docs({ topic: "types" })` for complete type details and validation.
|
|
56
|
+
|
|
57
|
+
| Wrong | Correct | Description |
|
|
58
|
+
|-------|---------|-------------|
|
|
59
|
+
| `boolean` | `bool` | Boolean true/false |
|
|
60
|
+
| `integer` | `int` | 32-bit integer |
|
|
61
|
+
| `string` | `text` | UTF-8 string |
|
|
62
|
+
| `number` / `float` | `decimal` | Floating-point number |
|
|
63
|
+
| `array` / `list` | `type[]` | Array (e.g., `text[]`, `int[]`) |
|
|
64
|
+
|
|
65
|
+
### Variable Declaration
|
|
66
|
+
|
|
67
|
+
```xs
|
|
68
|
+
var $name { value = "initial" }
|
|
69
|
+
var $count { value = 0 }
|
|
70
|
+
var $items { value = [] }
|
|
71
|
+
var $data { value = { key: "value" } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Conditionals
|
|
75
|
+
|
|
76
|
+
```xs
|
|
77
|
+
conditional {
|
|
78
|
+
if ($input.age >= 18) {
|
|
79
|
+
var $status { value = "adult" }
|
|
80
|
+
}
|
|
81
|
+
elseif ($input.age >= 13) {
|
|
82
|
+
var $status { value = "teen" }
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
var $status { value = "child" }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **Note:** Use `elseif` (one word), not `else if`.
|
|
91
|
+
|
|
92
|
+
### Switch
|
|
93
|
+
|
|
94
|
+
```xs
|
|
95
|
+
switch ($input.status) {
|
|
96
|
+
case ("active") {
|
|
97
|
+
var $message { value = "User is active" }
|
|
98
|
+
} break
|
|
99
|
+
case ("pending") {
|
|
100
|
+
var $message { value = "User is pending" }
|
|
101
|
+
} break
|
|
102
|
+
default {
|
|
103
|
+
var $message { value = "Unknown status" }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> **Note:** `break` goes **after** the closing `}` of each `case` block. The `default` case does not need `break`.
|
|
109
|
+
|
|
110
|
+
### Loops
|
|
111
|
+
|
|
112
|
+
```xs
|
|
113
|
+
// For each loop
|
|
114
|
+
foreach ($input.items) {
|
|
115
|
+
each as $item {
|
|
116
|
+
debug.log { value = $item.name }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// For loop (iterate N times)
|
|
121
|
+
for (10) {
|
|
122
|
+
each as $idx {
|
|
123
|
+
debug.log { value = $idx }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// While loop (must be inside stack block)
|
|
128
|
+
stack {
|
|
129
|
+
var $counter { value = 0 }
|
|
130
|
+
while ($counter < 10) {
|
|
131
|
+
each {
|
|
132
|
+
var.update $counter { value = $counter + 1 }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Map/filter for transformations
|
|
138
|
+
var $names { value = $items|map:$$.name }
|
|
139
|
+
var $active { value = $items|filter:$$.is_active }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Database CRUD
|
|
143
|
+
|
|
144
|
+
```xs
|
|
145
|
+
// Get single record by field
|
|
146
|
+
db.get "user" {
|
|
147
|
+
field_name = "id"
|
|
148
|
+
field_value = $input.user_id
|
|
149
|
+
} as $user
|
|
150
|
+
|
|
151
|
+
// Query with filters
|
|
152
|
+
db.query "user" {
|
|
153
|
+
where = $db.user.is_active == true
|
|
154
|
+
sort = { created_at: "desc" }
|
|
155
|
+
return = { type: "list", paging: { page: 1, per_page: 25 } }
|
|
156
|
+
} as $users
|
|
157
|
+
|
|
158
|
+
// Insert record
|
|
159
|
+
db.add "user" {
|
|
160
|
+
data = { name: $input.name, email: $input.email, created_at: now }
|
|
161
|
+
} as $new_user
|
|
162
|
+
|
|
163
|
+
// Update record
|
|
164
|
+
db.edit "user" {
|
|
165
|
+
field_name = "id"
|
|
166
|
+
field_value = $input.user_id
|
|
167
|
+
data = { name: $input.name, updated_at: now }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Delete record
|
|
171
|
+
db.del "user" {
|
|
172
|
+
field_name = "id"
|
|
173
|
+
field_value = $input.user_id
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
> **Full reference:** See `xanoscript_docs({ topic: "database" })` for joins, bulk operations, transactions, and more.
|
|
178
|
+
|
|
179
|
+
### API Requests
|
|
180
|
+
|
|
181
|
+
```xs
|
|
182
|
+
api.request {
|
|
183
|
+
url = "https://api.example.com/endpoint"
|
|
184
|
+
method = "POST"
|
|
185
|
+
params = $payload // Note: "params" for body, NOT "body"
|
|
186
|
+
headers = [
|
|
187
|
+
"Content-Type: application/json",
|
|
188
|
+
"Authorization: Bearer " ~ $env.API_KEY
|
|
189
|
+
]
|
|
190
|
+
timeout = 30
|
|
191
|
+
} as $api_result
|
|
192
|
+
|
|
193
|
+
// Response structure:
|
|
194
|
+
// $api_result.response.status -> HTTP status code (200, 404, etc.)
|
|
195
|
+
// $api_result.response.result -> Parsed response body
|
|
196
|
+
// $api_result.response.headers -> Response headers
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Error Handling
|
|
200
|
+
|
|
201
|
+
```xs
|
|
202
|
+
// Precondition (stops execution if false)
|
|
203
|
+
precondition ($input.id > 0) {
|
|
204
|
+
error_type = "inputerror"
|
|
205
|
+
error = "ID must be positive"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Try-catch
|
|
209
|
+
try_catch {
|
|
210
|
+
try {
|
|
211
|
+
api.request {
|
|
212
|
+
url = "https://api.example.com/risky"
|
|
213
|
+
method = "GET"
|
|
214
|
+
} as $result
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
debug.log { value = "Request failed, using fallback" }
|
|
218
|
+
var $result { value = { response: { result: null } } }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Throw custom error
|
|
223
|
+
throw {
|
|
224
|
+
name = "ValidationError"
|
|
225
|
+
value = "Custom error message"
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Error Types
|
|
230
|
+
|
|
231
|
+
| Type | HTTP Status | Use Case |
|
|
232
|
+
|------|-------------|----------|
|
|
233
|
+
| `inputerror` | 400 Bad Request | Invalid input data |
|
|
234
|
+
| `accessdenied` | 403 Forbidden | Authorization failure |
|
|
235
|
+
| `notfound` | 404 Not Found | Resource doesn't exist |
|
|
236
|
+
| `standard` | 500 Internal Server Error | General errors |
|
|
237
|
+
|
|
238
|
+
### Input Block Syntax
|
|
239
|
+
|
|
240
|
+
`?` after the **type** = nullable, `?` after the **variable name** = optional (not required).
|
|
241
|
+
|
|
242
|
+
> **Full reference:** See `xanoscript_docs({ topic: "types" })` for complete input types and validation options.
|
|
243
|
+
|
|
244
|
+
```xs
|
|
245
|
+
input {
|
|
246
|
+
text name // Required, not nullable
|
|
247
|
+
text? name // Required, nullable (can send null)
|
|
248
|
+
text name? // Optional (can be omitted)
|
|
249
|
+
text? name? // Optional and nullable
|
|
250
|
+
text role?="user" // Optional with default
|
|
251
|
+
email contact filters=trim // With filters
|
|
252
|
+
text[] tags // Array type
|
|
253
|
+
object address { // Nested object
|
|
254
|
+
schema {
|
|
255
|
+
text street
|
|
256
|
+
text city
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Common Filters
|
|
263
|
+
|
|
264
|
+
```xs
|
|
265
|
+
// String
|
|
266
|
+
$text|trim // Remove whitespace
|
|
267
|
+
$text|to_lower // Lowercase
|
|
268
|
+
$text|to_upper // Uppercase
|
|
269
|
+
$text|substr:0:10 // Substring
|
|
270
|
+
$text|split:"," // Split to array
|
|
271
|
+
$text|contains:"x" // Check contains -> bool
|
|
272
|
+
$text|strlen // Length of a string
|
|
273
|
+
|
|
274
|
+
// Array
|
|
275
|
+
$arr|first // First element
|
|
276
|
+
$arr|last // Last element
|
|
277
|
+
$arr|count // Length
|
|
278
|
+
$arr|map:$$.field // Transform elements
|
|
279
|
+
$arr|filter:$$.active // Filter by condition
|
|
280
|
+
$arr|find:$$.id == 5 // Find first match
|
|
281
|
+
|
|
282
|
+
// Type conversion
|
|
283
|
+
$val|to_text // To string
|
|
284
|
+
$val|to_int // To integer
|
|
285
|
+
$val|to_bool // To boolean
|
|
286
|
+
$val|json_encode // To JSON string
|
|
287
|
+
$text|json_decode // From JSON string
|
|
288
|
+
|
|
289
|
+
// Object
|
|
290
|
+
$obj|get:"key" // Get property
|
|
291
|
+
$obj|get:"key":"default" // Get with default
|
|
292
|
+
$obj|set:"key":"value" // Set property
|
|
293
|
+
$obj|has:"key" // Check key exists
|
|
294
|
+
|
|
295
|
+
// Null handling
|
|
296
|
+
$val|first_notnull:"default" // Default if null
|
|
297
|
+
$val ?? "default" // Nullish coalescing
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
> **Full reference:** See `xanoscript_docs({ topic: "syntax" })` for all 100+ filters organized by category (string, array, object, type, date, encoding).
|
|
301
|
+
|
|
302
|
+
### String Concatenation
|
|
303
|
+
|
|
304
|
+
```xs
|
|
305
|
+
// Basic
|
|
306
|
+
var $msg { value = "Hello, " ~ $input.name ~ "!" }
|
|
307
|
+
|
|
308
|
+
// With filters - MUST use parentheses
|
|
309
|
+
var $msg { value = ($status|to_text) ~ ": " ~ ($data|json_encode) }
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Authentication Check
|
|
313
|
+
|
|
314
|
+
```xs
|
|
315
|
+
precondition ($auth.id != null) {
|
|
316
|
+
error_type = "accessdenied"
|
|
317
|
+
error = "Authentication required"
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Function Call
|
|
322
|
+
|
|
323
|
+
```xs
|
|
324
|
+
function.run "my_function" {
|
|
325
|
+
input = { param1: "value" }
|
|
326
|
+
} as $result
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Early Return
|
|
330
|
+
|
|
331
|
+
Stop execution and return a value immediately. Useful for guard clauses.
|
|
332
|
+
|
|
333
|
+
```xs
|
|
334
|
+
conditional {
|
|
335
|
+
if ($input.skip) {
|
|
336
|
+
return { value = null }
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Common Patterns
|
|
344
|
+
|
|
345
|
+
### 1. Input Validation
|
|
346
|
+
|
|
347
|
+
```xs
|
|
348
|
+
precondition ($input.email != null && $input.email != "") {
|
|
349
|
+
error_type = "inputerror"
|
|
350
|
+
error = "Email is required"
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
precondition ($input.email|contains:"@") {
|
|
354
|
+
error_type = "inputerror"
|
|
355
|
+
error = "Invalid email format"
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 2. Optional Field Handling
|
|
360
|
+
|
|
361
|
+
```xs
|
|
362
|
+
// Build object with optional fields
|
|
363
|
+
var $data { value = { required_field: $input.required } }
|
|
364
|
+
|
|
365
|
+
conditional {
|
|
366
|
+
if ($input.optional_field != null) {
|
|
367
|
+
var.update $data { value = $data|set:"optional_field":$input.optional_field }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Or use set_ifnotnull
|
|
372
|
+
var $data {
|
|
373
|
+
value = { required: $input.required }|set_ifnotnull:"optional":$input.optional
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 3. Pagination
|
|
378
|
+
|
|
379
|
+
```xs
|
|
380
|
+
var $page { value = $input.page ?? 1 }
|
|
381
|
+
var $limit { value = $input.limit ?? 20 }
|
|
382
|
+
var $offset { value = ($page - 1) * $limit }
|
|
383
|
+
|
|
384
|
+
db.query "item" {
|
|
385
|
+
where = $db.item.is_active == true
|
|
386
|
+
order = [{ field: created_at, direction: desc }]
|
|
387
|
+
paging = { limit: $limit, offset: $offset }
|
|
388
|
+
count = true
|
|
389
|
+
} as $result
|
|
390
|
+
|
|
391
|
+
response = {
|
|
392
|
+
items: $result.items,
|
|
393
|
+
total: $result.count,
|
|
394
|
+
page: $page,
|
|
395
|
+
pages: ($result.count / $limit)|ceil
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### 4. Building Complex Objects
|
|
400
|
+
|
|
401
|
+
```xs
|
|
402
|
+
// Step by step
|
|
403
|
+
var $response_data { value = {} }
|
|
404
|
+
var.update $response_data { value = $response_data|set:"user":$user }
|
|
405
|
+
var.update $response_data { value = $response_data|set:"posts":$posts }
|
|
406
|
+
var.update $response_data { value = $response_data|set:"stats":{ count: $posts|count } }
|
|
407
|
+
|
|
408
|
+
// Or all at once
|
|
409
|
+
var $response_data {
|
|
410
|
+
value = {
|
|
411
|
+
user: $user,
|
|
412
|
+
posts: $posts,
|
|
413
|
+
stats: { count: $posts|count }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 5. Date/Time Operations
|
|
419
|
+
|
|
420
|
+
> **Full reference:** See `xanoscript_docs({ topic: "syntax" })` for all date/time filters.
|
|
421
|
+
|
|
422
|
+
```xs
|
|
423
|
+
var $now { value = now }
|
|
424
|
+
var $formatted { value = now|format_timestamp:"Y-m-d H:i:s":"UTC" }
|
|
425
|
+
var $yesterday { value = now|transform_timestamp:"-1 day" }
|
|
426
|
+
var $next_week { value = now|transform_timestamp:"+7 days" }
|
|
427
|
+
|
|
428
|
+
db.query "event" {
|
|
429
|
+
where = $db.event.start_date >= now
|
|
430
|
+
} as $upcoming_events
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### 6. JSON API Response
|
|
434
|
+
|
|
435
|
+
```xs
|
|
436
|
+
api.request {
|
|
437
|
+
url = "https://api.openai.com/v1/chat/completions"
|
|
438
|
+
method = "POST"
|
|
439
|
+
params = {
|
|
440
|
+
model: "gpt-4",
|
|
441
|
+
messages: [{ role: "user", content: $input.prompt }]
|
|
442
|
+
}
|
|
443
|
+
headers = [
|
|
444
|
+
"Content-Type: application/json",
|
|
445
|
+
"Authorization: Bearer " ~ $env.OPENAI_API_KEY
|
|
446
|
+
]
|
|
447
|
+
} as $api_result
|
|
448
|
+
|
|
449
|
+
conditional {
|
|
450
|
+
if ($api_result.response.status == 200) {
|
|
451
|
+
var $answer { value = $api_result.response.result.choices|first|get:"message"|get:"content" }
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
throw {
|
|
455
|
+
name = "APIError"
|
|
456
|
+
value = "OpenAI API error: " ~ ($api_result.response.status|to_text)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 7. Resource Ownership Check
|
|
463
|
+
|
|
464
|
+
```xs
|
|
465
|
+
precondition ($auth.id != null) {
|
|
466
|
+
error_type = "accessdenied"
|
|
467
|
+
error = "Authentication required"
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
db.get "post" {
|
|
471
|
+
where = $db.post.id == $input.post_id
|
|
472
|
+
} as $post
|
|
473
|
+
|
|
474
|
+
precondition ($post.user_id == $auth.id) {
|
|
475
|
+
error_type = "accessdenied"
|
|
476
|
+
error = "You can only edit your own posts"
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Common Mistakes
|
|
483
|
+
|
|
484
|
+
### 1. Using `else if` instead of `elseif`
|
|
485
|
+
```xs
|
|
486
|
+
// Wrong
|
|
487
|
+
// conditional { if (...) { } else if (...) { } }
|
|
488
|
+
|
|
489
|
+
// Correct
|
|
490
|
+
conditional {
|
|
491
|
+
if (...) { }
|
|
492
|
+
elseif (...) { }
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### 2. Missing parentheses in filter concatenation
|
|
497
|
+
```xs
|
|
498
|
+
// Wrong - parse error
|
|
499
|
+
// var $msg { value = $status|to_text ~ " - " ~ $data|json_encode }
|
|
500
|
+
|
|
501
|
+
// Correct - wrap filtered expressions in parentheses
|
|
502
|
+
var $msg { value = ($status|to_text) ~ " - " ~ ($data|json_encode) }
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 3. Missing parentheses in filter comparisons
|
|
506
|
+
```xs
|
|
507
|
+
// Wrong - parse error
|
|
508
|
+
// if ($array|count > 0) { }
|
|
509
|
+
|
|
510
|
+
// Correct - wrap filter expression in parentheses
|
|
511
|
+
if (($array|count) > 0) { }
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### 4. Using `body` instead of `params` for api.request
|
|
515
|
+
```xs
|
|
516
|
+
// Wrong - "body" is not valid
|
|
517
|
+
// api.request { url = "..." method = "POST" body = $payload }
|
|
518
|
+
|
|
519
|
+
// Correct - use "params" for request body
|
|
520
|
+
api.request {
|
|
521
|
+
url = "..."
|
|
522
|
+
method = "POST"
|
|
523
|
+
params = $payload
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### 5. Using `default` filter (doesn't exist)
|
|
528
|
+
```xs
|
|
529
|
+
// Wrong - no "default" filter exists
|
|
530
|
+
// var $value { value = $input.optional|default:"fallback" }
|
|
531
|
+
|
|
532
|
+
// Correct - use first_notnull or ?? operator
|
|
533
|
+
var $value { value = $input.optional|first_notnull:"fallback" }
|
|
534
|
+
// or
|
|
535
|
+
var $value { value = $input.optional ?? "fallback" }
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### 6. Using reserved variable names
|
|
539
|
+
```xs
|
|
540
|
+
// Wrong - $response is reserved
|
|
541
|
+
// var $response { value = "test" }
|
|
542
|
+
|
|
543
|
+
// Correct - use a different name
|
|
544
|
+
var $api_response { value = "test" }
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### 7. Wrong type names
|
|
548
|
+
```xs
|
|
549
|
+
// Wrong
|
|
550
|
+
// input { boolean active integer count string name }
|
|
551
|
+
|
|
552
|
+
// Correct
|
|
553
|
+
input {
|
|
554
|
+
bool active
|
|
555
|
+
int count
|
|
556
|
+
text name
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### 8. Object literal syntax (using = instead of :)
|
|
561
|
+
```xs
|
|
562
|
+
// Wrong - object literals use : not =
|
|
563
|
+
// var $data { value = { customer = $id } }
|
|
564
|
+
|
|
565
|
+
// Correct - use : for object properties
|
|
566
|
+
var $data { value = { customer: $id } }
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### 9. Commas in block properties (`=`) vs object literals (`:`)
|
|
570
|
+
|
|
571
|
+
Block properties use `=` and go on **separate lines with no commas**. Object literals use `:` and **require commas** between entries.
|
|
572
|
+
|
|
573
|
+
```xs
|
|
574
|
+
// Wrong - block properties don't use commas
|
|
575
|
+
// throw { name = "Error", value = "message" }
|
|
576
|
+
|
|
577
|
+
// Correct - no commas between block properties (=)
|
|
578
|
+
throw {
|
|
579
|
+
name = "Error"
|
|
580
|
+
value = "message"
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Correct - object literals (:) DO use commas
|
|
584
|
+
db.query "dad_jokes" {
|
|
585
|
+
sort = {dad_jokes.id: "rand", dad_jokes.joke: "asc"}
|
|
586
|
+
return = {type: "single"}
|
|
587
|
+
} as $joke
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 10. Using $env in run.job input blocks
|
|
591
|
+
```xs
|
|
592
|
+
// Wrong - $env not allowed in input blocks
|
|
593
|
+
// run.job "my_job" { input { text api_key = $env.API_KEY } }
|
|
594
|
+
|
|
595
|
+
// Correct - access $env in the stack instead
|
|
596
|
+
run.job "my_job" {
|
|
597
|
+
stack {
|
|
598
|
+
var $api_key { value = $env.API_KEY }
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### 11. Using `object` type without schema
|
|
604
|
+
```xs
|
|
605
|
+
// Wrong - object requires a schema
|
|
606
|
+
// input { object data }
|
|
607
|
+
|
|
608
|
+
// Correct - use json for arbitrary data
|
|
609
|
+
input {
|
|
610
|
+
json data
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Or define a schema for object
|
|
614
|
+
input {
|
|
615
|
+
object data {
|
|
616
|
+
schema {
|
|
617
|
+
text name
|
|
618
|
+
int id
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### 12. While loop outside of stack block
|
|
625
|
+
```xs
|
|
626
|
+
// Wrong - while must be inside stack
|
|
627
|
+
// while (true) { each { ... } }
|
|
628
|
+
|
|
629
|
+
// Correct - wrap in stack block
|
|
630
|
+
stack {
|
|
631
|
+
while (true) {
|
|
632
|
+
each { ... }
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### 13. Multiple constructs in one file
|
|
638
|
+
|
|
639
|
+
Each `.xs` file must contain exactly **one** construct. Placing two constructs in the same file causes a parse error.
|
|
640
|
+
|
|
641
|
+
```
|
|
642
|
+
// Wrong - two constructs in one file
|
|
643
|
+
// function "helper_a" { ... }
|
|
644
|
+
// function "helper_b" { ... }
|
|
645
|
+
|
|
646
|
+
// Correct - one construct per file
|
|
647
|
+
// function/helper_a.xs -> contains only "helper_a"
|
|
648
|
+
// function/helper_b.xs -> contains only "helper_b"
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## Related Topics
|
|
654
|
+
|
|
655
|
+
Explore more with `xanoscript_docs({ topic: "<topic>" })`:
|
|
656
|
+
|
|
657
|
+
| Topic | Description |
|
|
658
|
+
|-------|-------------|
|
|
659
|
+
| `syntax` | Complete filter reference, operators, system variables |
|
|
660
|
+
| `types` | Data types, input validation, schema definitions |
|
|
661
|
+
| `database` | All db.* operations: query, get, add, edit, delete |
|
|
662
|
+
| `functions` | Reusable function stacks, async patterns, loops |
|
|
663
|
+
| `apis` | HTTP endpoints, authentication, CRUD patterns |
|
|
664
|
+
| `security` | Security best practices and authentication |
|
|
665
|
+
| `integrations` | External API patterns (OpenAI, Stripe, etc.) |
|
|
@@ -474,6 +474,6 @@ Explore more with `xanoscript_docs({ topic: "<topic>" })`:
|
|
|
474
474
|
|-------|-------------|
|
|
475
475
|
| `types` | Input types, filters, and validation |
|
|
476
476
|
| `syntax` | Expressions, operators, and control flow |
|
|
477
|
-
| `
|
|
477
|
+
| `essentials` | Common patterns and examples |
|
|
478
478
|
| `database` | Database operations in function stacks |
|
|
479
479
|
| `testing` | Unit testing functions |
|
|
@@ -239,28 +239,15 @@ middleware "rate_limit" {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
stack {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
} as $count
|
|
247
|
-
|
|
248
|
-
conditional {
|
|
249
|
-
if ($count == 1) {
|
|
250
|
-
redis.expire {
|
|
251
|
-
key = $key
|
|
252
|
-
seconds = $input.window_seconds
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
precondition ($count <= $input.max_requests) {
|
|
258
|
-
error_type = "standard"
|
|
242
|
+
redis.ratelimit {
|
|
243
|
+
key = "ratelimit:" ~ $input.user_id
|
|
244
|
+
max = $input.max_requests
|
|
245
|
+
ttl = $input.window_seconds
|
|
259
246
|
error = "Rate limit exceeded"
|
|
260
247
|
}
|
|
261
248
|
}
|
|
262
249
|
|
|
263
|
-
response =
|
|
250
|
+
response = null
|
|
264
251
|
}
|
|
265
252
|
```
|
|
266
253
|
|