@xano/developer-mcp 1.0.19 → 1.0.21
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 +38 -2
- package/dist/index.js +15 -0
- package/dist/templates/init-workspace.js +4 -4
- package/dist/templates/xanoscript-index.d.ts +3 -1
- package/dist/templates/xanoscript-index.js +54 -51
- package/dist/xanoscript_docs/README.md +51 -37
- package/dist/xanoscript_docs/apis.md +6 -3
- package/dist/xanoscript_docs/branch.md +239 -0
- package/dist/xanoscript_docs/functions.md +2 -2
- package/dist/xanoscript_docs/middleware.md +321 -0
- package/dist/xanoscript_docs/realtime.md +113 -1
- package/dist/xanoscript_docs/tools.md +1 -1
- package/dist/xanoscript_docs/types.md +25 -8
- package/dist/xanoscript_docs/workspace.md +209 -0
- package/package.json +1 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "middleware/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Middleware
|
|
6
|
+
|
|
7
|
+
Middleware intercepts and processes requests before and after your functions, queries, tasks, and tools execute.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
middleware "<name>" {
|
|
13
|
+
description = "What this middleware does"
|
|
14
|
+
exception_policy = "silent" | "rethrow" | "critical"
|
|
15
|
+
response_strategy = "merge" | "replace"
|
|
16
|
+
input { ... }
|
|
17
|
+
stack { ... }
|
|
18
|
+
response = $result
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Required Blocks
|
|
23
|
+
| Block | Purpose |
|
|
24
|
+
|-------|---------|
|
|
25
|
+
| `input` | Define parameters passed to middleware |
|
|
26
|
+
| `stack` | Processing logic |
|
|
27
|
+
| `response` | Output returned to caller |
|
|
28
|
+
|
|
29
|
+
### Optional Attributes
|
|
30
|
+
| Attribute | Values | Default | Description |
|
|
31
|
+
|-----------|--------|---------|-------------|
|
|
32
|
+
| `description` | text | - | Documents middleware purpose |
|
|
33
|
+
| `exception_policy` | `silent`, `rethrow`, `critical` | `rethrow` | How to handle errors |
|
|
34
|
+
| `response_strategy` | `merge`, `replace` | `merge` | How response combines with original |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Basic Structure
|
|
39
|
+
|
|
40
|
+
```xs
|
|
41
|
+
middleware "log_request" {
|
|
42
|
+
description = "Logs all incoming requests"
|
|
43
|
+
|
|
44
|
+
input {
|
|
45
|
+
json request_data
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stack {
|
|
49
|
+
debug.log {
|
|
50
|
+
value = {
|
|
51
|
+
timestamp: now,
|
|
52
|
+
data: $input.request_data
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
response = null
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Exception Policies
|
|
64
|
+
|
|
65
|
+
Control how middleware handles errors:
|
|
66
|
+
|
|
67
|
+
### silent
|
|
68
|
+
Errors are caught and ignored. Execution continues normally.
|
|
69
|
+
|
|
70
|
+
```xs
|
|
71
|
+
middleware "optional_enrichment" {
|
|
72
|
+
exception_policy = "silent"
|
|
73
|
+
|
|
74
|
+
input {
|
|
75
|
+
int user_id
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
stack {
|
|
79
|
+
// If this fails, request continues without enrichment
|
|
80
|
+
api.call "external_service" {
|
|
81
|
+
url = "https://api.example.com/enrich/" ~ $input.user_id
|
|
82
|
+
} as $enriched
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
response = $enriched
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### rethrow (default)
|
|
90
|
+
Errors are passed through to the caller. Standard error handling applies.
|
|
91
|
+
|
|
92
|
+
```xs
|
|
93
|
+
middleware "validate_token" {
|
|
94
|
+
exception_policy = "rethrow"
|
|
95
|
+
|
|
96
|
+
input {
|
|
97
|
+
text token
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
stack {
|
|
101
|
+
precondition ($input.token|strlen > 0) {
|
|
102
|
+
error_type = "accessdenied"
|
|
103
|
+
error = "Token required"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
response = { valid: true }
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### critical
|
|
112
|
+
Errors are treated as critical failures. Request is immediately terminated.
|
|
113
|
+
|
|
114
|
+
```xs
|
|
115
|
+
middleware "security_check" {
|
|
116
|
+
exception_policy = "critical"
|
|
117
|
+
|
|
118
|
+
input {
|
|
119
|
+
text ip_address
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
stack {
|
|
123
|
+
db.query "blocked_ips" {
|
|
124
|
+
where = $db.blocked_ips.ip == $input.ip_address
|
|
125
|
+
return = { type: "exists" }
|
|
126
|
+
} as $is_blocked
|
|
127
|
+
|
|
128
|
+
precondition (!$is_blocked) {
|
|
129
|
+
error_type = "accessdenied"
|
|
130
|
+
error = "Access denied"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
response = { allowed: true }
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Response Strategies
|
|
141
|
+
|
|
142
|
+
Control how middleware response combines with the original response:
|
|
143
|
+
|
|
144
|
+
### merge (default)
|
|
145
|
+
Middleware response is merged with original response.
|
|
146
|
+
|
|
147
|
+
```xs
|
|
148
|
+
middleware "add_metadata" {
|
|
149
|
+
response_strategy = "merge"
|
|
150
|
+
|
|
151
|
+
input {
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
stack {
|
|
155
|
+
var $meta {
|
|
156
|
+
value = {
|
|
157
|
+
server_time: now,
|
|
158
|
+
version: "1.0.0"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
response = { _meta: $meta }
|
|
164
|
+
}
|
|
165
|
+
// Original: { data: [...] }
|
|
166
|
+
// Result: { data: [...], _meta: { server_time: ..., version: "1.0.0" } }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### replace
|
|
170
|
+
Middleware response completely replaces original response.
|
|
171
|
+
|
|
172
|
+
```xs
|
|
173
|
+
middleware "transform_response" {
|
|
174
|
+
response_strategy = "replace"
|
|
175
|
+
|
|
176
|
+
input {
|
|
177
|
+
json original_response
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stack {
|
|
181
|
+
var $transformed {
|
|
182
|
+
value = {
|
|
183
|
+
success: true,
|
|
184
|
+
payload: $input.original_response
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
response = $transformed
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Common Patterns
|
|
196
|
+
|
|
197
|
+
### Request Logging
|
|
198
|
+
|
|
199
|
+
```xs
|
|
200
|
+
middleware "audit_log" {
|
|
201
|
+
description = "Logs all API requests for audit trail"
|
|
202
|
+
exception_policy = "silent"
|
|
203
|
+
|
|
204
|
+
input {
|
|
205
|
+
text endpoint
|
|
206
|
+
text method
|
|
207
|
+
json request_body?
|
|
208
|
+
int user_id?
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
stack {
|
|
212
|
+
db.add "audit_log" {
|
|
213
|
+
data = {
|
|
214
|
+
endpoint: $input.endpoint,
|
|
215
|
+
method: $input.method,
|
|
216
|
+
request_body: $input.request_body,
|
|
217
|
+
user_id: $input.user_id,
|
|
218
|
+
ip_address: $env.$remote_ip,
|
|
219
|
+
created_at: now
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
response = null
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Rate Limiting
|
|
229
|
+
|
|
230
|
+
```xs
|
|
231
|
+
middleware "rate_limit" {
|
|
232
|
+
description = "Enforces rate limits per user"
|
|
233
|
+
exception_policy = "rethrow"
|
|
234
|
+
|
|
235
|
+
input {
|
|
236
|
+
int user_id
|
|
237
|
+
int max_requests?=100
|
|
238
|
+
int window_seconds?=60
|
|
239
|
+
}
|
|
240
|
+
|
|
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"
|
|
259
|
+
error = "Rate limit exceeded"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
response = { remaining: $input.max_requests - $count }
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Response Caching
|
|
268
|
+
|
|
269
|
+
```xs
|
|
270
|
+
middleware "cache_response" {
|
|
271
|
+
description = "Caches responses in Redis"
|
|
272
|
+
exception_policy = "silent"
|
|
273
|
+
|
|
274
|
+
input {
|
|
275
|
+
text cache_key
|
|
276
|
+
int ttl_seconds?=300
|
|
277
|
+
json response_data
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
stack {
|
|
281
|
+
redis.set {
|
|
282
|
+
key = $input.cache_key
|
|
283
|
+
value = $input.response_data|json_encode
|
|
284
|
+
expire = $input.ttl_seconds
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
response = null
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Applying Middleware
|
|
295
|
+
|
|
296
|
+
Middleware is configured at the branch level. See `xanoscript_docs({ topic: "branch" })` for configuration details.
|
|
297
|
+
|
|
298
|
+
```xs
|
|
299
|
+
branch "production" {
|
|
300
|
+
middleware = {
|
|
301
|
+
query: {
|
|
302
|
+
pre: ["validate_token", "rate_limit"],
|
|
303
|
+
post: ["audit_log", "cache_response"]
|
|
304
|
+
},
|
|
305
|
+
function: {
|
|
306
|
+
pre: ["validate_token"],
|
|
307
|
+
post: ["audit_log"]
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Best Practices
|
|
316
|
+
|
|
317
|
+
1. **Keep middleware focused** - Each middleware should do one thing well
|
|
318
|
+
2. **Use appropriate exception policies** - Critical for security, silent for optional enrichment
|
|
319
|
+
3. **Consider performance** - Middleware runs on every request
|
|
320
|
+
4. **Log failures** - Even silent failures should be logged for debugging
|
|
321
|
+
5. **Test independently** - Middleware should be testable in isolation
|
|
@@ -42,11 +42,123 @@ api.realtime_event {
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
+
## Realtime Channel Configuration
|
|
46
|
+
|
|
47
|
+
Define channel settings at the workspace level using `realtime_channel`.
|
|
48
|
+
|
|
49
|
+
```xs
|
|
50
|
+
realtime_channel "<channel_pattern>" {
|
|
51
|
+
description = "Channel description"
|
|
52
|
+
active = true
|
|
53
|
+
|
|
54
|
+
public_messaging = {
|
|
55
|
+
active: true,
|
|
56
|
+
auth: false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private_messaging = {
|
|
60
|
+
active: true,
|
|
61
|
+
auth: true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
settings = {
|
|
65
|
+
anonymous_clients: false,
|
|
66
|
+
nested_channels: true,
|
|
67
|
+
message_history: 100,
|
|
68
|
+
auth_channel: true,
|
|
69
|
+
presence: true
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Channel Configuration Options
|
|
75
|
+
|
|
76
|
+
| Attribute | Type | Description |
|
|
77
|
+
|-----------|------|-------------|
|
|
78
|
+
| `description` | text | Human-readable channel description |
|
|
79
|
+
| `active` | boolean | Enable/disable the channel |
|
|
80
|
+
|
|
81
|
+
### Messaging Options
|
|
82
|
+
|
|
83
|
+
| Setting | Type | Description |
|
|
84
|
+
|---------|------|-------------|
|
|
85
|
+
| `public_messaging.active` | boolean | Allow public messages |
|
|
86
|
+
| `public_messaging.auth` | boolean | Require authentication for public messages |
|
|
87
|
+
| `private_messaging.active` | boolean | Allow private messages |
|
|
88
|
+
| `private_messaging.auth` | boolean | Require authentication for private messages |
|
|
89
|
+
|
|
90
|
+
### Settings Options
|
|
91
|
+
|
|
92
|
+
| Setting | Type | Values | Description |
|
|
93
|
+
|---------|------|--------|-------------|
|
|
94
|
+
| `anonymous_clients` | boolean | true/false | Allow unauthenticated clients |
|
|
95
|
+
| `nested_channels` | boolean | true/false | Allow sub-channel patterns |
|
|
96
|
+
| `message_history` | number | 0, 25, 50, 100, 250, 1000 | Messages to retain |
|
|
97
|
+
| `auth_channel` | boolean | true/false | Require channel-level auth |
|
|
98
|
+
| `presence` | boolean | true/false | Track client presence |
|
|
99
|
+
|
|
100
|
+
### Example Configurations
|
|
101
|
+
|
|
102
|
+
```xs
|
|
103
|
+
// Chat room channel with presence
|
|
104
|
+
realtime_channel "room:*" {
|
|
105
|
+
description = "Chat room channels"
|
|
106
|
+
active = true
|
|
107
|
+
|
|
108
|
+
public_messaging = { active: true, auth: true }
|
|
109
|
+
private_messaging = { active: true, auth: true }
|
|
110
|
+
|
|
111
|
+
settings = {
|
|
112
|
+
anonymous_clients: false,
|
|
113
|
+
nested_channels: false,
|
|
114
|
+
message_history: 100,
|
|
115
|
+
auth_channel: true,
|
|
116
|
+
presence: true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Global announcements (read-only for clients)
|
|
121
|
+
realtime_channel "announcements" {
|
|
122
|
+
description = "System-wide announcements"
|
|
123
|
+
active = true
|
|
124
|
+
|
|
125
|
+
public_messaging = { active: true, auth: false }
|
|
126
|
+
private_messaging = { active: false, auth: false }
|
|
127
|
+
|
|
128
|
+
settings = {
|
|
129
|
+
anonymous_clients: true,
|
|
130
|
+
nested_channels: false,
|
|
131
|
+
message_history: 25,
|
|
132
|
+
auth_channel: false,
|
|
133
|
+
presence: false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// User-specific notifications
|
|
138
|
+
realtime_channel "user:*" {
|
|
139
|
+
description = "User notification channels"
|
|
140
|
+
active = true
|
|
141
|
+
|
|
142
|
+
public_messaging = { active: false, auth: false }
|
|
143
|
+
private_messaging = { active: true, auth: true }
|
|
144
|
+
|
|
145
|
+
settings = {
|
|
146
|
+
anonymous_clients: false,
|
|
147
|
+
nested_channels: false,
|
|
148
|
+
message_history: 50,
|
|
149
|
+
auth_channel: true,
|
|
150
|
+
presence: false
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
45
157
|
## Channel Patterns
|
|
46
158
|
|
|
47
159
|
### Public Channels
|
|
48
160
|
|
|
49
|
-
|
|
161
|
+
Accessible to all authenticated clients. When you send to a public channel, all clients currently subscribed to that channel receive the message.
|
|
50
162
|
|
|
51
163
|
```xs
|
|
52
164
|
// Global announcements
|
|
@@ -62,7 +62,7 @@ tool "get_user_by_email" {
|
|
|
62
62
|
|
|
63
63
|
## Input Block
|
|
64
64
|
|
|
65
|
-
For complete type reference, use `xanoscript_docs({
|
|
65
|
+
For complete type reference, use `xanoscript_docs({ topic: "types" })`. For tools, input `description` fields are sent to the AI, so write them clearly:
|
|
66
66
|
|
|
67
67
|
```xs
|
|
68
68
|
input {
|
|
@@ -62,7 +62,13 @@ input {
|
|
|
62
62
|
|
|
63
63
|
### Empty Input Blocks
|
|
64
64
|
|
|
65
|
-
**
|
|
65
|
+
**SYNTAX REQUIREMENT:** When an input block has no parameters, the braces MUST be on separate lines.
|
|
66
|
+
|
|
67
|
+
**Why:** The XanoScript parser requires whitespace between braces to distinguish empty input blocks from inline objects.
|
|
68
|
+
|
|
69
|
+
**Impact:** Using `input {}` on a single line will cause:
|
|
70
|
+
- Syntax error during compilation
|
|
71
|
+
- Function/API/tool will fail to deploy
|
|
66
72
|
|
|
67
73
|
```xs
|
|
68
74
|
// CORRECT - braces on separate lines
|
|
@@ -109,6 +115,21 @@ email contact filters=trim|lower {
|
|
|
109
115
|
password secret filters=min:8 // Minimum 8 characters
|
|
110
116
|
```
|
|
111
117
|
|
|
118
|
+
#### Password Complexity Filters
|
|
119
|
+
| Filter | Description | Example |
|
|
120
|
+
|--------|-------------|---------|
|
|
121
|
+
| `min:<n>` | Minimum total length | `password pwd filters=min:8` |
|
|
122
|
+
| `minAlpha:<n>` | Minimum letters (a-zA-Z) | `password pwd filters=minAlpha:2` |
|
|
123
|
+
| `minLowerAlpha:<n>` | Minimum lowercase letters | `password pwd filters=minLowerAlpha:1` |
|
|
124
|
+
| `minUpperAlpha:<n>` | Minimum uppercase letters | `password pwd filters=minUpperAlpha:1` |
|
|
125
|
+
| `minDigit:<n>` | Minimum digits (0-9) | `password pwd filters=minDigit:1` |
|
|
126
|
+
| `minSymbol:<n>` | Minimum special characters | `password pwd filters=minSymbol:1` |
|
|
127
|
+
|
|
128
|
+
```xs
|
|
129
|
+
// Strong password: 8+ chars, 1 upper, 1 lower, 1 digit, 1 symbol
|
|
130
|
+
password strong_pwd filters=min:8|minUpperAlpha:1|minLowerAlpha:1|minDigit:1|minSymbol:1
|
|
131
|
+
```
|
|
132
|
+
|
|
112
133
|
### timestamp / date
|
|
113
134
|
```xs
|
|
114
135
|
timestamp created_at?=now // Defaults to current time
|
|
@@ -199,10 +220,8 @@ Filters validate and transform input values. Chain with `|`.
|
|
|
199
220
|
| `ok:<chars>` | Allow only specified characters | `text hex filters=ok:0123456789abcdef` |
|
|
200
221
|
| `prevent:<str>` | Block specific substrings | `text name filters=prevent:admin` |
|
|
201
222
|
| `startsWith:<prefix>` | Require prefix | `text sku filters=startsWith:SKU-` |
|
|
202
|
-
| `endsWith:<suffix>` | Require suffix | `text file filters=endsWith:.pdf` |
|
|
203
223
|
| `alphaOk` | Allow only letters (a-zA-Z) | `text name filters=alphaOk` |
|
|
204
224
|
| `digitOk` | Allow only digits (0-9) | `text code filters=digitOk` |
|
|
205
|
-
| `alphaNumOk` | Allow letters and digits | `text username filters=alphaNumOk` |
|
|
206
225
|
| `pattern:<regex>` | Match regex pattern | `text phone filters=pattern:^\+?[0-9]+$` |
|
|
207
226
|
|
|
208
227
|
### Numeric Filters
|
|
@@ -210,14 +229,12 @@ Filters validate and transform input values. Chain with `|`.
|
|
|
210
229
|
|--------|-------------|---------|
|
|
211
230
|
| `min:<n>` | Minimum value | `int age filters=min:0` |
|
|
212
231
|
| `max:<n>` | Maximum value | `int age filters=max:150` |
|
|
213
|
-
| `between:<a>:<b>` | Value between a and b | `int score filters=between:0:100` |
|
|
214
232
|
|
|
215
233
|
### Array Filters
|
|
216
234
|
| Filter | Description | Example |
|
|
217
235
|
|--------|-------------|---------|
|
|
218
236
|
| `min:<n>` | Minimum array length | `text[] tags filters=min:1` |
|
|
219
237
|
| `max:<n>` | Maximum array length | `text[] tags filters=max:10` |
|
|
220
|
-
| `unique` | Remove duplicates | `int[] ids filters=unique` |
|
|
221
238
|
|
|
222
239
|
### Character Set Filters
|
|
223
240
|
|
|
@@ -255,11 +272,11 @@ input {
|
|
|
255
272
|
### Combined Examples
|
|
256
273
|
```xs
|
|
257
274
|
input {
|
|
258
|
-
text username filters=trim|lower|min:3|max:20|
|
|
275
|
+
text username filters=trim|lower|min:3|max:20|ok:abcdefghijklmnopqrstuvwxyz0123456789_
|
|
259
276
|
email contact filters=trim|lower
|
|
260
277
|
int age filters=min:0|max:150
|
|
261
278
|
text hex_code filters=ok:abcdef0123456789|min:6|max:6
|
|
262
|
-
text[] tags filters=trim|lower|max:50
|
|
279
|
+
text[] tags filters=trim|lower|max:50
|
|
263
280
|
text phone filters=trim|pattern:^\+?[0-9\s-]+$
|
|
264
281
|
int quantity filters=min:1|max:100
|
|
265
282
|
}
|
|
@@ -326,7 +343,7 @@ input {
|
|
|
326
343
|
|
|
327
344
|
## Validation with Preconditions
|
|
328
345
|
|
|
329
|
-
For complex validation beyond filters, use preconditions. For complete error handling reference, use `xanoscript_docs({
|
|
346
|
+
For complex validation beyond filters, use preconditions. For complete error handling reference, use `xanoscript_docs({ topic: "syntax" })`.
|
|
330
347
|
|
|
331
348
|
```xs
|
|
332
349
|
precondition ($input.start_date < $input.end_date) {
|