@xano/developer-mcp 1.0.7 → 1.0.9
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 +28 -0
- package/dist/index.js +42 -1
- package/dist/templates/xanoscript-index.js +7 -0
- package/package.json +1 -1
- package/xanoscript_docs/README.md +38 -5
- package/xanoscript_docs/addons.md +285 -0
- package/xanoscript_docs/agents.md +84 -0
- package/xanoscript_docs/database.md +160 -0
- package/xanoscript_docs/debugging.md +342 -0
- package/xanoscript_docs/functions.md +172 -0
- package/xanoscript_docs/integrations.md +376 -7
- package/xanoscript_docs/performance.md +407 -0
- package/xanoscript_docs/realtime.md +382 -0
- package/xanoscript_docs/schema.md +292 -0
- package/xanoscript_docs/security.md +550 -0
- package/xanoscript_docs/streaming.md +318 -0
- package/xanoscript_docs/syntax.md +267 -0
- package/xanoscript_docs/triggers.md +354 -52
- package/xanoscript_docs/types.md +66 -21
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "functions/**/*.xs, apis/**/*.xs, triggers/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Realtime
|
|
6
|
+
|
|
7
|
+
Push real-time updates to connected clients using channels and events.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
| Operation | Purpose |
|
|
12
|
+
|-----------|---------|
|
|
13
|
+
| `api.realtime_event` | Send event to channel |
|
|
14
|
+
| Channel patterns | Organize subscriptions |
|
|
15
|
+
| Realtime triggers | Handle client events |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## api.realtime_event
|
|
20
|
+
|
|
21
|
+
Send a real-time event to subscribed clients.
|
|
22
|
+
|
|
23
|
+
```xs
|
|
24
|
+
api.realtime_event {
|
|
25
|
+
channel = "orders"
|
|
26
|
+
event = "new_order"
|
|
27
|
+
data = {
|
|
28
|
+
order_id: $order.id,
|
|
29
|
+
total: $order.total,
|
|
30
|
+
customer: $order.customer_name
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Parameters
|
|
36
|
+
|
|
37
|
+
| Parameter | Type | Description |
|
|
38
|
+
|-----------|------|-------------|
|
|
39
|
+
| `channel` | text | Channel name or pattern |
|
|
40
|
+
| `event` | text | Event type name |
|
|
41
|
+
| `data` | any | Payload to send |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Channel Patterns
|
|
46
|
+
|
|
47
|
+
### Public Channels
|
|
48
|
+
|
|
49
|
+
Available to all authenticated clients.
|
|
50
|
+
|
|
51
|
+
```xs
|
|
52
|
+
// Global announcements
|
|
53
|
+
api.realtime_event {
|
|
54
|
+
channel = "announcements"
|
|
55
|
+
event = "system_update"
|
|
56
|
+
data = { message: "Maintenance in 10 minutes" }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### User-Specific Channels
|
|
61
|
+
|
|
62
|
+
Target individual users.
|
|
63
|
+
|
|
64
|
+
```xs
|
|
65
|
+
// Notify specific user
|
|
66
|
+
api.realtime_event {
|
|
67
|
+
channel = "user:" ~ $user.id
|
|
68
|
+
event = "notification"
|
|
69
|
+
data = {
|
|
70
|
+
title: "New message",
|
|
71
|
+
body: "You have a new message from " ~ $sender.name
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Resource Channels
|
|
77
|
+
|
|
78
|
+
Updates for specific resources.
|
|
79
|
+
|
|
80
|
+
```xs
|
|
81
|
+
// Update watchers of a document
|
|
82
|
+
api.realtime_event {
|
|
83
|
+
channel = "document:" ~ $document.id
|
|
84
|
+
event = "content_updated"
|
|
85
|
+
data = {
|
|
86
|
+
updated_by: $auth.id,
|
|
87
|
+
updated_at: now,
|
|
88
|
+
changes: $changes
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Tenant Channels
|
|
94
|
+
|
|
95
|
+
Multi-tenant isolation.
|
|
96
|
+
|
|
97
|
+
```xs
|
|
98
|
+
api.realtime_event {
|
|
99
|
+
channel = "tenant:" ~ $env.$tenant ~ ":orders"
|
|
100
|
+
event = "order_created"
|
|
101
|
+
data = $order
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Broadcast Patterns
|
|
108
|
+
|
|
109
|
+
### To All Connected Users
|
|
110
|
+
|
|
111
|
+
```xs
|
|
112
|
+
api.realtime_event {
|
|
113
|
+
channel = "global"
|
|
114
|
+
event = "broadcast"
|
|
115
|
+
data = { message: $input.message }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### To Role-Based Groups
|
|
120
|
+
|
|
121
|
+
```xs
|
|
122
|
+
// Notify all admins
|
|
123
|
+
foreach ($admin_users) {
|
|
124
|
+
each as $admin {
|
|
125
|
+
api.realtime_event {
|
|
126
|
+
channel = "user:" ~ $admin.id
|
|
127
|
+
event = "admin_alert"
|
|
128
|
+
data = $alert_data
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### To Room/Group
|
|
135
|
+
|
|
136
|
+
```xs
|
|
137
|
+
api.realtime_event {
|
|
138
|
+
channel = "room:" ~ $input.room_id
|
|
139
|
+
event = "message"
|
|
140
|
+
data = {
|
|
141
|
+
sender_id: $auth.id,
|
|
142
|
+
content: $input.message,
|
|
143
|
+
sent_at: now
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Realtime Triggers
|
|
151
|
+
|
|
152
|
+
Handle events from connected clients.
|
|
153
|
+
|
|
154
|
+
### Basic Realtime Trigger
|
|
155
|
+
|
|
156
|
+
```xs
|
|
157
|
+
realtime_trigger "on_presence" {
|
|
158
|
+
channel = "room:*"
|
|
159
|
+
event = "join"
|
|
160
|
+
stack {
|
|
161
|
+
// $input contains event data
|
|
162
|
+
// $channel contains matched channel
|
|
163
|
+
db.add "presence" {
|
|
164
|
+
data = {
|
|
165
|
+
user_id: $auth.id,
|
|
166
|
+
room_id: $input.room_id,
|
|
167
|
+
joined_at: now
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Notify room members
|
|
172
|
+
api.realtime_event {
|
|
173
|
+
channel = $channel
|
|
174
|
+
event = "user_joined"
|
|
175
|
+
data = { user_id: $auth.id }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Channel Pattern Matching
|
|
182
|
+
|
|
183
|
+
```xs
|
|
184
|
+
realtime_trigger "document_cursor" {
|
|
185
|
+
channel = "document:*" // Wildcard match
|
|
186
|
+
event = "cursor_move"
|
|
187
|
+
stack {
|
|
188
|
+
// Broadcast cursor position to other viewers
|
|
189
|
+
api.realtime_event {
|
|
190
|
+
channel = $channel
|
|
191
|
+
event = "cursor_update"
|
|
192
|
+
data = {
|
|
193
|
+
user_id: $auth.id,
|
|
194
|
+
position: $input.position
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Common Patterns
|
|
204
|
+
|
|
205
|
+
### Chat Application
|
|
206
|
+
|
|
207
|
+
```xs
|
|
208
|
+
function "send_chat_message" {
|
|
209
|
+
input {
|
|
210
|
+
int room_id
|
|
211
|
+
text content filters=trim|max:1000
|
|
212
|
+
}
|
|
213
|
+
stack {
|
|
214
|
+
// Save message
|
|
215
|
+
db.add "message" {
|
|
216
|
+
data = {
|
|
217
|
+
room_id: $input.room_id,
|
|
218
|
+
sender_id: $auth.id,
|
|
219
|
+
content: $input.content,
|
|
220
|
+
created_at: now
|
|
221
|
+
}
|
|
222
|
+
} as $message
|
|
223
|
+
|
|
224
|
+
// Broadcast to room
|
|
225
|
+
api.realtime_event {
|
|
226
|
+
channel = "room:" ~ $input.room_id
|
|
227
|
+
event = "new_message"
|
|
228
|
+
data = {
|
|
229
|
+
id: $message.id,
|
|
230
|
+
sender_id: $auth.id,
|
|
231
|
+
content: $input.content,
|
|
232
|
+
created_at: $message.created_at
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
response = $message
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Live Dashboard Updates
|
|
241
|
+
|
|
242
|
+
```xs
|
|
243
|
+
function "update_metrics" {
|
|
244
|
+
stack {
|
|
245
|
+
// Calculate metrics
|
|
246
|
+
db.query "order" {
|
|
247
|
+
where = $db.order.created_at >= now|transform_timestamp:"-1 hour"
|
|
248
|
+
} as $recent_orders
|
|
249
|
+
|
|
250
|
+
var $metrics {
|
|
251
|
+
value = {
|
|
252
|
+
orders_last_hour: $recent_orders|count,
|
|
253
|
+
revenue_last_hour: $recent_orders|map:$$.total|sum,
|
|
254
|
+
updated_at: now
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Push to dashboard subscribers
|
|
259
|
+
api.realtime_event {
|
|
260
|
+
channel = "dashboard:metrics"
|
|
261
|
+
event = "update"
|
|
262
|
+
data = $metrics
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
response = $metrics
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Collaborative Editing
|
|
270
|
+
|
|
271
|
+
```xs
|
|
272
|
+
realtime_trigger "document_edit" {
|
|
273
|
+
channel = "document:*"
|
|
274
|
+
event = "operation"
|
|
275
|
+
stack {
|
|
276
|
+
// Apply operation to document
|
|
277
|
+
function.run "apply_document_op" {
|
|
278
|
+
input = {
|
|
279
|
+
document_id: $input.document_id,
|
|
280
|
+
operation: $input.operation,
|
|
281
|
+
user_id: $auth.id
|
|
282
|
+
}
|
|
283
|
+
} as $result
|
|
284
|
+
|
|
285
|
+
// Broadcast to other editors
|
|
286
|
+
api.realtime_event {
|
|
287
|
+
channel = $channel
|
|
288
|
+
event = "remote_operation"
|
|
289
|
+
data = {
|
|
290
|
+
operation: $input.operation,
|
|
291
|
+
user_id: $auth.id,
|
|
292
|
+
version: $result.version
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Notification System
|
|
300
|
+
|
|
301
|
+
```xs
|
|
302
|
+
function "notify_user" {
|
|
303
|
+
input {
|
|
304
|
+
int user_id
|
|
305
|
+
text type
|
|
306
|
+
text title
|
|
307
|
+
text body
|
|
308
|
+
json? data
|
|
309
|
+
}
|
|
310
|
+
stack {
|
|
311
|
+
// Save notification
|
|
312
|
+
db.add "notification" {
|
|
313
|
+
data = {
|
|
314
|
+
user_id: $input.user_id,
|
|
315
|
+
type: $input.type,
|
|
316
|
+
title: $input.title,
|
|
317
|
+
body: $input.body,
|
|
318
|
+
data: $input.data,
|
|
319
|
+
read: false,
|
|
320
|
+
created_at: now
|
|
321
|
+
}
|
|
322
|
+
} as $notification
|
|
323
|
+
|
|
324
|
+
// Push to user
|
|
325
|
+
api.realtime_event {
|
|
326
|
+
channel = "user:" ~ $input.user_id
|
|
327
|
+
event = "notification"
|
|
328
|
+
data = $notification
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
response = $notification
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Subscription Management
|
|
338
|
+
|
|
339
|
+
Clients subscribe to channels client-side. Server controls what events to send.
|
|
340
|
+
|
|
341
|
+
### Authorization Pattern
|
|
342
|
+
|
|
343
|
+
```xs
|
|
344
|
+
// Validate user can access channel before sending
|
|
345
|
+
function "send_to_room" {
|
|
346
|
+
input {
|
|
347
|
+
int room_id
|
|
348
|
+
text event
|
|
349
|
+
json data
|
|
350
|
+
}
|
|
351
|
+
stack {
|
|
352
|
+
// Check membership
|
|
353
|
+
db.query "room_member" {
|
|
354
|
+
where = $db.room_member.room_id == $input.room_id
|
|
355
|
+
&& $db.room_member.user_id == $auth.id
|
|
356
|
+
return = { type: "exists" }
|
|
357
|
+
} as $is_member
|
|
358
|
+
|
|
359
|
+
precondition ($is_member) {
|
|
360
|
+
error_type = "accessdenied"
|
|
361
|
+
error = "Not a member of this room"
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
api.realtime_event {
|
|
365
|
+
channel = "room:" ~ $input.room_id
|
|
366
|
+
event = $input.event
|
|
367
|
+
data = $input.data
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Best Practices
|
|
376
|
+
|
|
377
|
+
1. **Use channel namespacing** - `type:id` format for clarity
|
|
378
|
+
2. **Keep payloads small** - Send IDs, let client fetch details
|
|
379
|
+
3. **Validate before broadcast** - Don't trust client event data
|
|
380
|
+
4. **Use triggers for client events** - Handle incoming realtime events
|
|
381
|
+
5. **Consider fan-out carefully** - Large broadcasts can be expensive
|
|
382
|
+
6. **Implement presence** - Track who's connected to channels
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "functions/**/*.xs, apis/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Schema Operations
|
|
6
|
+
|
|
7
|
+
Runtime schema parsing and validation for dynamic data structures.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
| Function | Purpose |
|
|
12
|
+
|----------|---------|
|
|
13
|
+
| `schema.parse` | Parse and validate any schema |
|
|
14
|
+
| `schema.parse.object` | Parse object schema |
|
|
15
|
+
| `schema.parse.array` | Parse array schema |
|
|
16
|
+
| `schema.parse.attribute` | Parse single attribute |
|
|
17
|
+
| `schema.parse.constant` | Parse constant value |
|
|
18
|
+
| `schema.parse.enum` | Parse enum value |
|
|
19
|
+
| `schema.parse.immutable` | Parse immutable structure |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## schema.parse
|
|
24
|
+
|
|
25
|
+
Parse and validate data against a dynamic schema.
|
|
26
|
+
|
|
27
|
+
```xs
|
|
28
|
+
schema.parse {
|
|
29
|
+
data = $input.payload
|
|
30
|
+
schema = {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
name: { type: "text", required: true },
|
|
34
|
+
email: { type: "email", filters: ["trim", "lower"] },
|
|
35
|
+
age: { type: "int", filters: ["min:0", "max:150"] }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} as $validated
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Schema Definition
|
|
42
|
+
|
|
43
|
+
| Property | Description |
|
|
44
|
+
|----------|-------------|
|
|
45
|
+
| `type` | Data type (text, int, decimal, bool, object, array, etc.) |
|
|
46
|
+
| `required` | Whether field is required (default: false) |
|
|
47
|
+
| `nullable` | Whether null is allowed (default: false) |
|
|
48
|
+
| `default` | Default value if not provided |
|
|
49
|
+
| `filters` | Array of validation filters |
|
|
50
|
+
| `properties` | Nested properties (for object type) |
|
|
51
|
+
| `items` | Item schema (for array type) |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## schema.parse.object
|
|
56
|
+
|
|
57
|
+
Parse and validate an object structure.
|
|
58
|
+
|
|
59
|
+
```xs
|
|
60
|
+
schema.parse.object {
|
|
61
|
+
data = $input.user
|
|
62
|
+
schema = {
|
|
63
|
+
id: { type: "int", required: true },
|
|
64
|
+
profile: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
name: { type: "text", required: true },
|
|
68
|
+
bio: { type: "text", nullable: true, filters: ["max:500"] }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
roles: { type: "array", items: { type: "text" } }
|
|
72
|
+
}
|
|
73
|
+
} as $user
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### With Defaults
|
|
77
|
+
|
|
78
|
+
```xs
|
|
79
|
+
schema.parse.object {
|
|
80
|
+
data = $input.settings
|
|
81
|
+
schema = {
|
|
82
|
+
theme: { type: "text", default: "light" },
|
|
83
|
+
notifications: { type: "bool", default: true },
|
|
84
|
+
language: { type: "text", default: "en" }
|
|
85
|
+
}
|
|
86
|
+
} as $settings
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## schema.parse.array
|
|
92
|
+
|
|
93
|
+
Parse and validate an array structure.
|
|
94
|
+
|
|
95
|
+
```xs
|
|
96
|
+
schema.parse.array {
|
|
97
|
+
data = $input.items
|
|
98
|
+
schema = {
|
|
99
|
+
min_items: 1,
|
|
100
|
+
max_items: 100,
|
|
101
|
+
items: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
product_id: { type: "int", required: true },
|
|
105
|
+
quantity: { type: "int", required: true, filters: ["min:1"] }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} as $items
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Flat Array
|
|
113
|
+
|
|
114
|
+
```xs
|
|
115
|
+
schema.parse.array {
|
|
116
|
+
data = $input.tags
|
|
117
|
+
schema = {
|
|
118
|
+
items: { type: "text", filters: ["trim", "lower"] },
|
|
119
|
+
unique: true
|
|
120
|
+
}
|
|
121
|
+
} as $tags
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## schema.parse.attribute
|
|
127
|
+
|
|
128
|
+
Parse a single attribute value.
|
|
129
|
+
|
|
130
|
+
```xs
|
|
131
|
+
schema.parse.attribute {
|
|
132
|
+
data = $input.email
|
|
133
|
+
schema = {
|
|
134
|
+
type: "email",
|
|
135
|
+
required: true,
|
|
136
|
+
filters: ["trim", "lower"]
|
|
137
|
+
}
|
|
138
|
+
} as $email
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### With Custom Validation
|
|
142
|
+
|
|
143
|
+
```xs
|
|
144
|
+
schema.parse.attribute {
|
|
145
|
+
data = $input.phone
|
|
146
|
+
schema = {
|
|
147
|
+
type: "text",
|
|
148
|
+
pattern: "^\\+?[1-9]\\d{1,14}$",
|
|
149
|
+
error_message: "Invalid phone number format"
|
|
150
|
+
}
|
|
151
|
+
} as $phone
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## schema.parse.constant
|
|
157
|
+
|
|
158
|
+
Validate that a value matches an expected constant.
|
|
159
|
+
|
|
160
|
+
```xs
|
|
161
|
+
schema.parse.constant {
|
|
162
|
+
data = $input.version
|
|
163
|
+
schema = {
|
|
164
|
+
value: "2.0",
|
|
165
|
+
error_message: "Only API version 2.0 is supported"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## schema.parse.enum
|
|
173
|
+
|
|
174
|
+
Validate that a value is one of allowed options.
|
|
175
|
+
|
|
176
|
+
```xs
|
|
177
|
+
schema.parse.enum {
|
|
178
|
+
data = $input.status
|
|
179
|
+
schema = {
|
|
180
|
+
values: ["pending", "active", "suspended", "closed"],
|
|
181
|
+
error_message: "Invalid status value"
|
|
182
|
+
}
|
|
183
|
+
} as $status
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### With Default
|
|
187
|
+
|
|
188
|
+
```xs
|
|
189
|
+
schema.parse.enum {
|
|
190
|
+
data = $input.priority
|
|
191
|
+
schema = {
|
|
192
|
+
values: ["low", "medium", "high"],
|
|
193
|
+
default: "medium"
|
|
194
|
+
}
|
|
195
|
+
} as $priority
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## schema.parse.immutable
|
|
201
|
+
|
|
202
|
+
Parse a structure that cannot be modified after parsing.
|
|
203
|
+
|
|
204
|
+
```xs
|
|
205
|
+
schema.parse.immutable {
|
|
206
|
+
data = $input.config
|
|
207
|
+
schema = {
|
|
208
|
+
api_version: { type: "text", required: true },
|
|
209
|
+
features: { type: "array", items: { type: "text" } }
|
|
210
|
+
}
|
|
211
|
+
} as $config
|
|
212
|
+
|
|
213
|
+
// Attempting to modify $config will throw an error
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Dynamic Schema Generation
|
|
219
|
+
|
|
220
|
+
Build schemas programmatically.
|
|
221
|
+
|
|
222
|
+
```xs
|
|
223
|
+
function "validate_dynamic_form" {
|
|
224
|
+
input {
|
|
225
|
+
object form_config
|
|
226
|
+
object form_data
|
|
227
|
+
}
|
|
228
|
+
stack {
|
|
229
|
+
// Build schema from configuration
|
|
230
|
+
var $schema { value = { type: "object", properties: {} } }
|
|
231
|
+
|
|
232
|
+
foreach ($input.form_config.fields) {
|
|
233
|
+
each as $field {
|
|
234
|
+
var.update $schema.properties {
|
|
235
|
+
value = $schema.properties|set:$field.name:{
|
|
236
|
+
type: $field.type,
|
|
237
|
+
required: $field.required,
|
|
238
|
+
filters: $field.filters
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Validate form data against dynamic schema
|
|
245
|
+
schema.parse.object {
|
|
246
|
+
data = $input.form_data
|
|
247
|
+
schema = $schema.properties
|
|
248
|
+
} as $validated
|
|
249
|
+
}
|
|
250
|
+
response = $validated
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Error Handling
|
|
257
|
+
|
|
258
|
+
Schema validation errors can be caught and handled.
|
|
259
|
+
|
|
260
|
+
```xs
|
|
261
|
+
try_catch {
|
|
262
|
+
try {
|
|
263
|
+
schema.parse.object {
|
|
264
|
+
data = $input.payload
|
|
265
|
+
schema = $expected_schema
|
|
266
|
+
} as $validated
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// $error contains validation details
|
|
270
|
+
// $error.field - which field failed
|
|
271
|
+
// $error.message - validation error message
|
|
272
|
+
// $error.value - the invalid value
|
|
273
|
+
throw {
|
|
274
|
+
name = "ValidationError"
|
|
275
|
+
value = {
|
|
276
|
+
field: $error.field,
|
|
277
|
+
message: $error.message
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Best Practices
|
|
287
|
+
|
|
288
|
+
1. **Define schemas upfront** - Use input blocks when structure is known
|
|
289
|
+
2. **Use schema.parse for dynamic data** - External APIs, user-generated content
|
|
290
|
+
3. **Validate at boundaries** - Parse external input, trust internal data
|
|
291
|
+
4. **Provide clear error messages** - Use error_message for user-facing errors
|
|
292
|
+
5. **Use immutable for config** - Prevent accidental modification
|