@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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# XanoScript Survival Kit
|
|
6
|
+
|
|
7
|
+
> Minimal reference for writing valid XanoScript. For more: `xanoscript_docs({ topic: "<topic>" })`.
|
|
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
|
+
### Syntax Traps
|
|
44
|
+
|
|
45
|
+
**Trap 1: `elseif` not `else if`**
|
|
46
|
+
```xs
|
|
47
|
+
// WRONG: conditional { if (...) { } else if (...) { } }
|
|
48
|
+
// RIGHT:
|
|
49
|
+
conditional {
|
|
50
|
+
if ($x > 0) { var $s { value = "positive" } }
|
|
51
|
+
elseif ($x == 0) { var $s { value = "zero" } }
|
|
52
|
+
else { var $s { value = "negative" } }
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Trap 2: Parentheses around filters in expressions**
|
|
57
|
+
```xs
|
|
58
|
+
// WRONG: if ($arr|count > 0) { }
|
|
59
|
+
// RIGHT:
|
|
60
|
+
if (($arr|count) > 0) { }
|
|
61
|
+
|
|
62
|
+
// WRONG: var $msg { value = $val|to_text ~ " items" }
|
|
63
|
+
// RIGHT:
|
|
64
|
+
var $msg { value = ($val|to_text) ~ " items" }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Trap 3: Object literals use `:` — block properties use `=`**
|
|
68
|
+
```xs
|
|
69
|
+
// Object literal (data) — uses : with commas
|
|
70
|
+
var $data { value = { name: "Alice", age: 30 } }
|
|
71
|
+
|
|
72
|
+
// Block property (config) — uses = with NO commas, separate lines
|
|
73
|
+
precondition ($data != null) {
|
|
74
|
+
error_type = "notfound"
|
|
75
|
+
error = "Not found"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Trap 4: `params` not `body` for api.request**
|
|
80
|
+
```xs
|
|
81
|
+
// WRONG: api.request { url = "..." method = "POST" body = $payload }
|
|
82
|
+
// RIGHT:
|
|
83
|
+
api.request { url = "..." method = "POST" params = $payload } as $result
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Trap 5: Wrong type names**
|
|
87
|
+
```xs
|
|
88
|
+
// WRONG: input { boolean active integer count string name }
|
|
89
|
+
// RIGHT:
|
|
90
|
+
input { bool active int count text name }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Canonical Function Example
|
|
94
|
+
|
|
95
|
+
```xs
|
|
96
|
+
function "get_user_greeting" {
|
|
97
|
+
input {
|
|
98
|
+
int user_id
|
|
99
|
+
text? greeting?="Hello"
|
|
100
|
+
}
|
|
101
|
+
stack {
|
|
102
|
+
db.get "user" {
|
|
103
|
+
field_name = "id"
|
|
104
|
+
field_value = $input.user_id
|
|
105
|
+
} as $user
|
|
106
|
+
|
|
107
|
+
precondition ($user != null) {
|
|
108
|
+
error_type = "notfound"
|
|
109
|
+
error = "User not found"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
var $message { value = ($input.greeting ?? "Hello") ~ ", " ~ $user.name ~ "!" }
|
|
113
|
+
}
|
|
114
|
+
response = $message
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Canonical API Endpoint Example
|
|
119
|
+
|
|
120
|
+
```xs
|
|
121
|
+
query "users/{user_id}" verb=GET {
|
|
122
|
+
api_group = "users"
|
|
123
|
+
auth = "user"
|
|
124
|
+
input { int user_id }
|
|
125
|
+
stack {
|
|
126
|
+
db.get "user" { field_name = "id" field_value = $input.user_id } as $user
|
|
127
|
+
precondition ($user != null) { error_type = "notfound" error = "User not found" }
|
|
128
|
+
}
|
|
129
|
+
response = $user
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Common Operations
|
|
134
|
+
|
|
135
|
+
```xs
|
|
136
|
+
db.get "table" { field_name = "id" field_value = $id } as $record
|
|
137
|
+
db.query "table" { where = $db.table.active == true } as $records
|
|
138
|
+
db.add "table" { data = { field: value } } as $new
|
|
139
|
+
db.edit "table" { field_name = "id" field_value = $id data = { field: val } }
|
|
140
|
+
db.del "table" { field_name = "id" field_value = $id }
|
|
141
|
+
api.request { url = $url method = "POST" params = $data headers = ["Authorization: Bearer " ~ $env.KEY] } as $res
|
|
142
|
+
function.run "my_func" { input = { param: value } } as $result
|
|
143
|
+
foreach ($items) { each as $item { debug.log { value = $item } } }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Input Modifiers
|
|
147
|
+
|
|
148
|
+
```xs
|
|
149
|
+
input {
|
|
150
|
+
text name // required, not nullable
|
|
151
|
+
text? bio // required, nullable (can send null)
|
|
152
|
+
text role?="user" // optional with default
|
|
153
|
+
text? note? // optional and nullable
|
|
154
|
+
email contact filters=trim // with validation filter
|
|
155
|
+
text[] tags // array type
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Available Topics
|
|
160
|
+
|
|
161
|
+
essentials, syntax, types, database, functions, apis, tables, tasks, triggers, agents, tools, mcp-servers, security, performance, debugging, unit-testing, workflow-tests, middleware, addons, realtime, streaming, schema, integrations, workspace, branch, run, frontend
|
|
@@ -148,12 +148,6 @@ if (($arr|count) == 0) { ... }
|
|
|
148
148
|
var $message {
|
|
149
149
|
value = ($status|to_text) ~ ": " ~ ($data|json_encode)
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
// ✅ Correct — filter result used in arithmetic
|
|
153
|
-
var $total { value = ($prices|sum) + $tax }
|
|
154
|
-
|
|
155
|
-
// ✅ Correct — filter result compared
|
|
156
|
-
var $is_long { value = ($text|strlen) > 100 }
|
|
157
151
|
```
|
|
158
152
|
|
|
159
153
|
**Summary:** Any time you apply an operator (`>`, `<`, `==`, `!=`, `~`, `+`, `-`, etc.) to a filtered value, wrap the `$var|filter` portion in its own parentheses.
|
|
@@ -319,11 +319,9 @@ table "order" {
|
|
|
319
319
|
|
|
320
320
|
## Best Practices
|
|
321
321
|
|
|
322
|
-
1. **Always define `id`** - Every table
|
|
323
|
-
2. **Use `auth = true`** only for authentication
|
|
324
|
-
3. **Add indexes** for fields used in WHERE clauses and JOINs
|
|
325
|
-
4. **Use appropriate types** - `email` for emails, `password` for credentials
|
|
326
|
-
5. **Default timestamps** - Use `?=now` for created_at fields
|
|
322
|
+
1. **Always define `int id`** - Every table requires a primary key field named `id`
|
|
323
|
+
2. **Use `auth = true`** only for the authentication table (typically just `user`)
|
|
324
|
+
3. **Add indexes** for fields used in `db.query` WHERE clauses and JOINs to avoid full table scans
|
|
327
325
|
|
|
328
326
|
---
|
|
329
327
|
|
|
@@ -254,9 +254,7 @@ task "risky_sync" {
|
|
|
254
254
|
|
|
255
255
|
1. **Descriptive names** - Indicate what and when: `daily_cleanup`, `hourly_sync`
|
|
256
256
|
2. **Handle errors** - Use try_catch for external dependencies
|
|
257
|
-
3. **Consider timezone** - Schedule uses UTC (+0000)
|
|
258
|
-
4. **Batch operations** - Process in chunks for large datasets
|
|
259
|
-
5. **Set end dates** - Use ends_on for temporary schedules
|
|
257
|
+
3. **Consider timezone** - Schedule uses UTC (+0000); use `ends_on` for temporary schedules
|
|
260
258
|
|
|
261
259
|
---
|
|
262
260
|
|
|
@@ -298,9 +298,7 @@ tool "cancel_order" {
|
|
|
298
298
|
|
|
299
299
|
1. **Write clear instructions** - This is what the AI reads to understand the tool
|
|
300
300
|
2. **Describe all inputs** - Help AI construct valid requests
|
|
301
|
-
3. **Use enums for fixed options** - Reduces AI errors
|
|
302
|
-
4. **Keep tools focused** - One task per tool
|
|
303
|
-
5. **Limit response size** - Don't return huge datasets
|
|
301
|
+
3. **Use enums for fixed options** - Reduces AI errors; keep tools focused to one task
|
|
304
302
|
|
|
305
303
|
---
|
|
306
304
|
|
|
@@ -589,77 +589,11 @@ mcp_server_trigger "database_tool_handler" {
|
|
|
589
589
|
|
|
590
590
|
---
|
|
591
591
|
|
|
592
|
-
## Common Patterns
|
|
593
|
-
|
|
594
|
-
### Error Handling in Triggers
|
|
595
|
-
|
|
596
|
-
```xs
|
|
597
|
-
table_trigger "safe_audit" {
|
|
598
|
-
table = "sensitive_data"
|
|
599
|
-
actions = {insert: true, update: true, delete: true, truncate: false}
|
|
600
|
-
|
|
601
|
-
// Uses predefined Table Trigger Input - see Predefined Input Blocks section
|
|
602
|
-
input { ... }
|
|
603
|
-
|
|
604
|
-
stack {
|
|
605
|
-
try_catch {
|
|
606
|
-
try {
|
|
607
|
-
db.add "audit_log" {
|
|
608
|
-
data = {
|
|
609
|
-
action: $input.action,
|
|
610
|
-
new_data: $input.new,
|
|
611
|
-
old_data: $input.old,
|
|
612
|
-
timestamp: now
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
catch {
|
|
617
|
-
debug.log { value = "Audit logging failed" }
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### Conditional Trigger Logic
|
|
625
|
-
|
|
626
|
-
```xs
|
|
627
|
-
table_trigger "conditional_notification" {
|
|
628
|
-
table = "order"
|
|
629
|
-
actions = {insert: true, update: false, delete: false, truncate: false}
|
|
630
|
-
|
|
631
|
-
// Uses predefined Table Trigger Input - see Predefined Input Blocks section
|
|
632
|
-
input { ... }
|
|
633
|
-
|
|
634
|
-
stack {
|
|
635
|
-
conditional {
|
|
636
|
-
if ($input.new.total > 1000) {
|
|
637
|
-
util.send_email {
|
|
638
|
-
service_provider = "resend"
|
|
639
|
-
api_key = $env.RESEND_API_KEY
|
|
640
|
-
to = "sales@example.com"
|
|
641
|
-
from = "system@example.com"
|
|
642
|
-
subject = "High Value Order"
|
|
643
|
-
message = "Order #" ~ $input.new.id ~ " for $" ~ $input.new.total
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
---
|
|
652
|
-
|
|
653
592
|
## Best Practices
|
|
654
593
|
|
|
655
|
-
1. **Use predefined input blocks as-is** - Each trigger type has a read-only input block that cannot be modified
|
|
656
|
-
2. **
|
|
657
|
-
3. **
|
|
658
|
-
4. **Keep triggers lightweight** - Offload heavy processing to functions or tasks
|
|
659
|
-
5. **Set appropriate history** - Use `history = false` for high-frequency triggers to save storage
|
|
660
|
-
6. **Use tags** - Organize triggers with meaningful tags for easier management
|
|
661
|
-
7. **Document with description** - Always provide a description explaining the trigger's purpose
|
|
662
|
-
8. **Test thoroughly** - Triggers execute automatically, so ensure they handle edge cases
|
|
594
|
+
1. **Use predefined input blocks as-is** - Each trigger type has a read-only input block that cannot be modified
|
|
595
|
+
2. **Handle errors gracefully** - Use try_catch to prevent trigger failures from affecting the main operation
|
|
596
|
+
3. **Keep triggers lightweight** - Offload heavy processing to functions or tasks; use `history = false` for high-frequency triggers
|
|
663
597
|
|
|
664
598
|
---
|
|
665
599
|
|
|
@@ -390,10 +390,9 @@ precondition ($input.start_date < $input.end_date) {
|
|
|
390
390
|
|
|
391
391
|
## Best Practices
|
|
392
392
|
|
|
393
|
-
1. **
|
|
394
|
-
2. **
|
|
395
|
-
3. **
|
|
396
|
-
4. **Validate at boundaries** - Validate user input, trust internal calls
|
|
393
|
+
1. **Use filters first** - Prefer declarative `filters=` over stack-level preconditions for input validation
|
|
394
|
+
2. **Mark sensitive data** - Use `sensitive = true` for PII/credentials to mask them in logs
|
|
395
|
+
3. **Use `?` placement correctly** - `text?` = nullable, `name?` = optional; these are independent modifiers
|
|
397
396
|
|
|
398
397
|
---
|
|
399
398
|
|
|
@@ -370,65 +370,11 @@ For a full breakdown of what each required/optional/nullable combination accepts
|
|
|
370
370
|
|
|
371
371
|
---
|
|
372
372
|
|
|
373
|
-
## Complete Example
|
|
374
|
-
|
|
375
|
-
```xs
|
|
376
|
-
function "calculate_discount" {
|
|
377
|
-
input {
|
|
378
|
-
decimal subtotal filters=min:0
|
|
379
|
-
enum customer_type { values = ["regular", "premium", "vip"] }
|
|
380
|
-
text coupon?
|
|
381
|
-
}
|
|
382
|
-
stack {
|
|
383
|
-
var $discount_rate { value = 0 }
|
|
384
|
-
|
|
385
|
-
switch ($input.customer_type) {
|
|
386
|
-
case ("premium") { var.update $discount_rate { value = 0.1 } } break
|
|
387
|
-
case ("vip") { var.update $discount_rate { value = 0.2 } } break
|
|
388
|
-
default { var.update $discount_rate { value = 0 } }
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
conditional {
|
|
392
|
-
if ($input.coupon == "SAVE10") {
|
|
393
|
-
math.add $discount_rate { value = 0.1 }
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
var $discount { value = $input.subtotal * $discount_rate }
|
|
398
|
-
}
|
|
399
|
-
response = { discount: $discount, rate: $discount_rate }
|
|
400
|
-
|
|
401
|
-
test "no discount for regular customer" {
|
|
402
|
-
input = { subtotal: 100, customer_type: "regular" }
|
|
403
|
-
expect.to_equal ($response.discount) { value = 0 }
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
test "10% discount for premium" {
|
|
407
|
-
input = { subtotal: 100, customer_type: "premium" }
|
|
408
|
-
expect.to_equal ($response.discount) { value = 10 }
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
test "20% discount for VIP" {
|
|
412
|
-
input = { subtotal: 100, customer_type: "vip" }
|
|
413
|
-
expect.to_equal ($response.discount) { value = 20 }
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
test "coupon stacks with VIP discount" {
|
|
417
|
-
input = { subtotal: 100, customer_type: "vip", coupon: "SAVE10" }
|
|
418
|
-
expect.to_equal ($response.rate) { value = 0.3 }
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
373
|
## Best Practices
|
|
426
374
|
|
|
427
375
|
1. **Test happy paths, edge cases, and errors** - Cover expected, boundary, and failure scenarios
|
|
428
376
|
2. **Use mocks** - Isolate from external dependencies
|
|
429
|
-
3. **Descriptive test names** -
|
|
430
|
-
4. **One assertion focus** - Each test verifies one behavior
|
|
431
|
-
5. **Keep tests independent** - No shared state between tests
|
|
377
|
+
3. **Descriptive test names** - Each test verifies one behavior
|
|
432
378
|
|
|
433
379
|
---
|
|
434
380
|
|
|
@@ -181,44 +181,21 @@ expect.to_throw {
|
|
|
181
181
|
|
|
182
182
|
## Data Sources
|
|
183
183
|
|
|
184
|
-
The `datasource` property controls which data source the test runs against.
|
|
185
|
-
|
|
186
|
-
**Avoid using `datasource = "live"`.** When a workflow test runs, the entire datasource is cloned so that real data is not modified. This cloning step can be slow, especially for large datasources. Prefer running without a datasource or using a smaller custom datasource instead.
|
|
187
|
-
|
|
188
|
-
### Default (No Data Source) — Recommended
|
|
184
|
+
The `datasource` property controls which data source the test runs against. **Avoid `datasource = "live"`** — it clones the entire datasource before each run, which can be slow. Prefer omitting `datasource` or using a smaller custom datasource.
|
|
189
185
|
|
|
190
186
|
```xs
|
|
187
|
+
// Recommended: no datasource
|
|
191
188
|
workflow_test "basic_check" {
|
|
192
189
|
stack {
|
|
193
190
|
function.call "health_check" as $result
|
|
194
191
|
expect.to_be_defined ($result)
|
|
195
192
|
}
|
|
196
193
|
}
|
|
197
|
-
```
|
|
198
194
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
```xs
|
|
202
|
-
workflow_test "staging_data_check" {
|
|
195
|
+
// Custom datasource
|
|
196
|
+
workflow_test "staging_check" {
|
|
203
197
|
datasource = "staging"
|
|
204
|
-
stack {
|
|
205
|
-
function.call "get_products" as $products
|
|
206
|
-
expect.to_be_defined ($products)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Live Data Source (Not Recommended)
|
|
212
|
-
|
|
213
|
-
Using `datasource = "live"` clones the entire live datasource before running the test. Only use this when you specifically need to validate against production data.
|
|
214
|
-
|
|
215
|
-
```xs
|
|
216
|
-
workflow_test "live_data_check" {
|
|
217
|
-
datasource = "live"
|
|
218
|
-
stack {
|
|
219
|
-
function.call "get_active_users" as $users
|
|
220
|
-
expect.to_be_defined ($users)
|
|
221
|
-
}
|
|
198
|
+
stack { ... }
|
|
222
199
|
}
|
|
223
200
|
```
|
|
224
201
|
|
|
@@ -317,13 +294,9 @@ workflow_test "comprehensive_test" {
|
|
|
317
294
|
|
|
318
295
|
## Best Practices
|
|
319
296
|
|
|
320
|
-
1. **Use
|
|
321
|
-
2. **
|
|
322
|
-
3. **
|
|
323
|
-
4. **Keep tests independent** — Each workflow test should be self-contained and not depend on other tests
|
|
324
|
-
5. **Use `function.call`, not `function.run`** — `function.call` handles errors so that `expect` assertions work correctly. `function.run` is for calling functions outside of workflow tests
|
|
325
|
-
6. **Use assertions over preconditions** — Prefer `expect.*` assertions for clearer test intent
|
|
326
|
-
7. **Don't test required fields with empty strings** — Required inputs reject both `null` and `""` at the platform level before your stack runs. If you send `""` to a required `text` field, you'll always get a platform validation error — never your custom logic. To test the empty/blank case, the input must be declared optional (`text name?`). See `xanoscript_docs({ topic: "types" })` for the full breakdown.
|
|
297
|
+
1. **Use `function.call`, not `function.run`** — `function.call` handles errors so that `expect` assertions work correctly
|
|
298
|
+
2. **Keep tests independent** — Each workflow test should be self-contained
|
|
299
|
+
3. **Don't test required fields with empty strings** — Required inputs reject `""` at the platform level. Use optional fields (`text name?`) to test blank cases. See `xanoscript_docs({ topic: "types" })`
|
|
327
300
|
|
|
328
301
|
---
|
|
329
302
|
|