@xano/developer-mcp 1.0.54 → 1.0.56

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.
@@ -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
- | `quickstart` | Common patterns and examples |
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
- var $key { value = "ratelimit:" ~ $input.user_id }
243
-
244
- redis.incr {
245
- key = $key
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 = { remaining: $input.max_requests - $count }
250
+ response = null
264
251
  }
265
252
  ```
266
253