@xano/developer-mcp 1.0.1 → 1.0.2
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 +96 -31
- package/dist/index.js +248 -180
- package/package.json +4 -2
- package/xanoscript_docs/README.md +107 -1
- package/xanoscript_docs/agents.md +329 -0
- package/xanoscript_docs/apis.md +343 -0
- package/xanoscript_docs/database.md +417 -0
- package/xanoscript_docs/ephemeral.md +333 -0
- package/xanoscript_docs/frontend.md +291 -0
- package/xanoscript_docs/functions.md +232 -2035
- package/xanoscript_docs/integrations.md +439 -0
- package/xanoscript_docs/mcp-servers.md +190 -0
- package/xanoscript_docs/plan.md +192 -0
- package/xanoscript_docs/syntax.md +314 -0
- package/xanoscript_docs/tables.md +270 -0
- package/xanoscript_docs/tasks.md +254 -0
- package/xanoscript_docs/testing.md +335 -0
- package/xanoscript_docs/tools.md +305 -0
- package/xanoscript_docs/types.md +297 -0
- package/xanoscript_docs/version.json +2 -1
- package/xanoscript_docs/api_query_examples.md +0 -1255
- package/xanoscript_docs/api_query_guideline.md +0 -129
- package/xanoscript_docs/build_from_lovable.md +0 -715
- package/xanoscript_docs/db_query_guideline.md +0 -427
- package/xanoscript_docs/ephemeral_environment_guideline.md +0 -529
- package/xanoscript_docs/expression_guideline.md +0 -1086
- package/xanoscript_docs/frontend_guideline.md +0 -67
- package/xanoscript_docs/function_examples.md +0 -1406
- package/xanoscript_docs/function_guideline.md +0 -130
- package/xanoscript_docs/input_guideline.md +0 -227
- package/xanoscript_docs/mcp_server_examples.md +0 -36
- package/xanoscript_docs/mcp_server_guideline.md +0 -69
- package/xanoscript_docs/query_filter.md +0 -489
- package/xanoscript_docs/table_examples.md +0 -586
- package/xanoscript_docs/table_guideline.md +0 -137
- package/xanoscript_docs/task_examples.md +0 -511
- package/xanoscript_docs/task_guideline.md +0 -103
- package/xanoscript_docs/tips_and_tricks.md +0 -144
- package/xanoscript_docs/tool_examples.md +0 -69
- package/xanoscript_docs/tool_guideline.md +0 -139
- package/xanoscript_docs/unit_testing_guideline.md +0 -328
- package/xanoscript_docs/workspace.md +0 -17
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "tables/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Tables
|
|
6
|
+
|
|
7
|
+
Database table definitions in XanoScript.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
table "<name>" {
|
|
13
|
+
auth = false # true if used for authentication
|
|
14
|
+
schema {
|
|
15
|
+
int id # Primary key (required)
|
|
16
|
+
text name filters=trim
|
|
17
|
+
timestamp created_at?=now
|
|
18
|
+
}
|
|
19
|
+
index = [
|
|
20
|
+
{type: "primary", field: [{name: "id"}]}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Field Types
|
|
26
|
+
`int`, `text`, `email`, `password`, `decimal`, `bool`, `timestamp`, `date`, `uuid`, `vector`, `json`, `image`, `video`, `audio`, `attachment`, `enum`
|
|
27
|
+
|
|
28
|
+
### Index Types
|
|
29
|
+
`primary`, `btree`, `btree|unique`, `gin`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Basic Structure
|
|
34
|
+
|
|
35
|
+
Every table requires an `id` field as the primary key:
|
|
36
|
+
|
|
37
|
+
```xs
|
|
38
|
+
table "user" {
|
|
39
|
+
auth = true
|
|
40
|
+
schema {
|
|
41
|
+
int id
|
|
42
|
+
text name filters=trim
|
|
43
|
+
email email filters=trim|lower {
|
|
44
|
+
sensitive = true
|
|
45
|
+
}
|
|
46
|
+
password password
|
|
47
|
+
timestamp created_at?=now
|
|
48
|
+
}
|
|
49
|
+
index = [
|
|
50
|
+
{type: "primary", field: [{name: "id"}]}
|
|
51
|
+
{type: "btree|unique", field: [{name: "email"}]}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Schema Fields
|
|
59
|
+
|
|
60
|
+
### Basic Fields
|
|
61
|
+
```xs
|
|
62
|
+
schema {
|
|
63
|
+
int id
|
|
64
|
+
text name
|
|
65
|
+
decimal price
|
|
66
|
+
bool is_active
|
|
67
|
+
timestamp created_at
|
|
68
|
+
date birth_date
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Optional & Defaults
|
|
73
|
+
```xs
|
|
74
|
+
schema {
|
|
75
|
+
text nickname? # Optional, no default
|
|
76
|
+
timestamp created_at?=now # Optional, defaults to current time
|
|
77
|
+
bool is_active?=true # Optional, defaults to true
|
|
78
|
+
int status?=0 # Optional, defaults to 0
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### With Filters
|
|
83
|
+
```xs
|
|
84
|
+
schema {
|
|
85
|
+
text name filters=trim
|
|
86
|
+
email email filters=trim|lower
|
|
87
|
+
int quantity filters=min:0
|
|
88
|
+
text category filters=trim|lower
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With Metadata
|
|
93
|
+
```xs
|
|
94
|
+
schema {
|
|
95
|
+
text ssn {
|
|
96
|
+
description = "Social Security Number"
|
|
97
|
+
sensitive = true
|
|
98
|
+
}
|
|
99
|
+
email email filters=lower {
|
|
100
|
+
description = "Primary contact email"
|
|
101
|
+
sensitive = true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Foreign Keys
|
|
107
|
+
```xs
|
|
108
|
+
schema {
|
|
109
|
+
int user_id {
|
|
110
|
+
table = "user"
|
|
111
|
+
description = "Reference to user table"
|
|
112
|
+
}
|
|
113
|
+
uuid order_id {
|
|
114
|
+
table = "order"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Enum Fields
|
|
120
|
+
```xs
|
|
121
|
+
schema {
|
|
122
|
+
enum status {
|
|
123
|
+
values = ["pending", "active", "completed", "cancelled"]
|
|
124
|
+
}
|
|
125
|
+
enum priority?="medium" {
|
|
126
|
+
values = ["low", "medium", "high"]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### JSON Fields
|
|
132
|
+
```xs
|
|
133
|
+
schema {
|
|
134
|
+
json metadata
|
|
135
|
+
json settings?={}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Vector Fields
|
|
140
|
+
```xs
|
|
141
|
+
schema {
|
|
142
|
+
vector embedding # For AI/ML embeddings
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Indexes
|
|
149
|
+
|
|
150
|
+
### Primary Key
|
|
151
|
+
```xs
|
|
152
|
+
index = [
|
|
153
|
+
{type: "primary", field: [{name: "id"}]}
|
|
154
|
+
]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### B-tree Index
|
|
158
|
+
```xs
|
|
159
|
+
index = [
|
|
160
|
+
{type: "btree", field: [{name: "email", op: "asc"}]}
|
|
161
|
+
{type: "btree", field: [{name: "created_at", op: "desc"}]}
|
|
162
|
+
]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Unique Index
|
|
166
|
+
```xs
|
|
167
|
+
index = [
|
|
168
|
+
{type: "btree|unique", field: [{name: "email"}]}
|
|
169
|
+
{type: "btree|unique", field: [{name: "username"}]}
|
|
170
|
+
]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Composite Index
|
|
174
|
+
```xs
|
|
175
|
+
index = [
|
|
176
|
+
{type: "btree", field: [{name: "user_id"}, {name: "created_at", op: "desc"}]}
|
|
177
|
+
]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### GIN Index (for JSON/arrays)
|
|
181
|
+
```xs
|
|
182
|
+
index = [
|
|
183
|
+
{type: "gin", field: [{name: "tags", op: "jsonb_path_op"}]}
|
|
184
|
+
{type: "gin", field: [{name: "metadata", op: "jsonb_path_op"}]}
|
|
185
|
+
]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Complete Examples
|
|
191
|
+
|
|
192
|
+
### User Table (with auth)
|
|
193
|
+
```xs
|
|
194
|
+
table "user" {
|
|
195
|
+
auth = true
|
|
196
|
+
schema {
|
|
197
|
+
int id
|
|
198
|
+
text name filters=trim
|
|
199
|
+
email email filters=trim|lower { sensitive = true }
|
|
200
|
+
password password { sensitive = true }
|
|
201
|
+
enum role?="user" { values = ["user", "admin", "moderator"] }
|
|
202
|
+
bool is_active?=true
|
|
203
|
+
timestamp created_at?=now
|
|
204
|
+
timestamp updated_at?
|
|
205
|
+
}
|
|
206
|
+
index = [
|
|
207
|
+
{type: "primary", field: [{name: "id"}]}
|
|
208
|
+
{type: "btree|unique", field: [{name: "email"}]}
|
|
209
|
+
{type: "btree", field: [{name: "role"}]}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Product Table
|
|
215
|
+
```xs
|
|
216
|
+
table "product" {
|
|
217
|
+
auth = false
|
|
218
|
+
schema {
|
|
219
|
+
int id
|
|
220
|
+
text name filters=trim
|
|
221
|
+
text description?
|
|
222
|
+
decimal price filters=min:0
|
|
223
|
+
int stock_quantity?=0 filters=min:0
|
|
224
|
+
int category_id { table = "category" }
|
|
225
|
+
json metadata?
|
|
226
|
+
bool is_published?=false
|
|
227
|
+
timestamp created_at?=now
|
|
228
|
+
}
|
|
229
|
+
index = [
|
|
230
|
+
{type: "primary", field: [{name: "id"}]}
|
|
231
|
+
{type: "btree", field: [{name: "category_id"}]}
|
|
232
|
+
{type: "btree", field: [{name: "is_published"}]}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Order with Foreign Keys
|
|
238
|
+
```xs
|
|
239
|
+
table "order" {
|
|
240
|
+
auth = false
|
|
241
|
+
schema {
|
|
242
|
+
int id
|
|
243
|
+
int user_id { table = "user" }
|
|
244
|
+
decimal total filters=min:0
|
|
245
|
+
enum status?="pending" { values = ["pending", "processing", "shipped", "delivered", "cancelled"] }
|
|
246
|
+
json shipping_address
|
|
247
|
+
timestamp created_at?=now
|
|
248
|
+
timestamp updated_at?
|
|
249
|
+
}
|
|
250
|
+
index = [
|
|
251
|
+
{type: "primary", field: [{name: "id"}]}
|
|
252
|
+
{type: "btree", field: [{name: "user_id"}]}
|
|
253
|
+
{type: "btree", field: [{name: "status"}]}
|
|
254
|
+
{type: "btree", field: [{name: "created_at", op: "desc"}]}
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Best Practices
|
|
262
|
+
|
|
263
|
+
1. **Always define `id`** - Every table needs a primary key named `id`
|
|
264
|
+
2. **Use `auth = true`** only for authentication tables (typically just `user`)
|
|
265
|
+
3. **Add indexes** for fields used in WHERE clauses and JOINs
|
|
266
|
+
4. **Use appropriate types** - `email` for emails, `password` for credentials
|
|
267
|
+
5. **Mark sensitive fields** - Set `sensitive = true` for PII
|
|
268
|
+
6. **Use filters** - Apply `trim`, `lower` for consistency
|
|
269
|
+
7. **Default timestamps** - Use `?=now` for created_at fields
|
|
270
|
+
8. **Document with descriptions** - Add descriptions for non-obvious fields
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "tasks/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Tasks
|
|
6
|
+
|
|
7
|
+
Scheduled jobs in XanoScript.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
task "<name>" {
|
|
13
|
+
description = "What this task does"
|
|
14
|
+
stack { ... }
|
|
15
|
+
schedule = [{starts_on: YYYY-MM-DD HH:MM:SS+0000, freq: <seconds>}]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Common Frequencies
|
|
20
|
+
| Interval | Seconds |
|
|
21
|
+
|----------|---------|
|
|
22
|
+
| 1 minute | 60 |
|
|
23
|
+
| 5 minutes | 300 |
|
|
24
|
+
| 1 hour | 3600 |
|
|
25
|
+
| Daily | 86400 |
|
|
26
|
+
| Weekly | 604800 |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Basic Structure
|
|
31
|
+
|
|
32
|
+
```xs
|
|
33
|
+
task "daily_cleanup" {
|
|
34
|
+
description = "Clean up expired sessions daily at midnight UTC"
|
|
35
|
+
stack {
|
|
36
|
+
db.query "session" {
|
|
37
|
+
where = $db.session.expires_at < now
|
|
38
|
+
} as $expired
|
|
39
|
+
|
|
40
|
+
foreach ($expired) {
|
|
41
|
+
each as $session {
|
|
42
|
+
db.del "session" {
|
|
43
|
+
field_name = "id"
|
|
44
|
+
field_value = $session.id
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
debug.log { value = "Cleaned " ~ ($expired|count) ~ " sessions" }
|
|
50
|
+
}
|
|
51
|
+
schedule = [{starts_on: 2025-01-01 00:00:00+0000, freq: 86400}]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Schedule Configuration
|
|
58
|
+
|
|
59
|
+
### Single Event
|
|
60
|
+
```xs
|
|
61
|
+
schedule = [{starts_on: 2025-06-15 09:00:00+0000, freq: 86400}]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With End Date
|
|
65
|
+
```xs
|
|
66
|
+
schedule = [{
|
|
67
|
+
starts_on: 2025-01-01 08:00:00+0000,
|
|
68
|
+
freq: 3600,
|
|
69
|
+
ends_on: 2025-12-31 23:59:59+0000
|
|
70
|
+
}]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Multiple Schedules
|
|
74
|
+
```xs
|
|
75
|
+
schedule = [
|
|
76
|
+
{starts_on: 2025-01-01 09:00:00+0000, freq: 86400},
|
|
77
|
+
{starts_on: 2025-01-01 21:00:00+0000, freq: 86400}
|
|
78
|
+
]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Common Patterns
|
|
84
|
+
|
|
85
|
+
### Data Aggregation
|
|
86
|
+
```xs
|
|
87
|
+
task "hourly_stats" {
|
|
88
|
+
description = "Aggregate hourly statistics"
|
|
89
|
+
stack {
|
|
90
|
+
var $hour_ago { value = now|transform_timestamp:"-1 hour" }
|
|
91
|
+
|
|
92
|
+
db.query "order" {
|
|
93
|
+
where = $db.order.created_at >= $hour_ago
|
|
94
|
+
} as $orders
|
|
95
|
+
|
|
96
|
+
db.add "hourly_stats" {
|
|
97
|
+
data = {
|
|
98
|
+
hour: $hour_ago,
|
|
99
|
+
order_count: $orders|count,
|
|
100
|
+
total_revenue: ($orders|map:$$.total)|sum
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
schedule = [{starts_on: 2025-01-01 00:00:00+0000, freq: 3600}]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Cleanup Job
|
|
109
|
+
```xs
|
|
110
|
+
task "cleanup_temp_files" {
|
|
111
|
+
description = "Delete temporary files older than 24 hours"
|
|
112
|
+
stack {
|
|
113
|
+
var $cutoff { value = now|transform_timestamp:"-24 hours" }
|
|
114
|
+
|
|
115
|
+
db.query "temp_file" {
|
|
116
|
+
where = $db.temp_file.created_at < $cutoff
|
|
117
|
+
} as $files
|
|
118
|
+
|
|
119
|
+
foreach ($files) {
|
|
120
|
+
each as $file {
|
|
121
|
+
storage.delete_file { pathname = $file.path }
|
|
122
|
+
db.del "temp_file" {
|
|
123
|
+
field_name = "id"
|
|
124
|
+
field_value = $file.id
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
schedule = [{starts_on: 2025-01-01 03:00:00+0000, freq: 86400}]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Notification Job
|
|
134
|
+
```xs
|
|
135
|
+
task "daily_digest" {
|
|
136
|
+
description = "Send daily digest emails"
|
|
137
|
+
stack {
|
|
138
|
+
db.query "user" {
|
|
139
|
+
where = $db.user.digest_enabled == true
|
|
140
|
+
} as $users
|
|
141
|
+
|
|
142
|
+
foreach ($users) {
|
|
143
|
+
each as $user {
|
|
144
|
+
db.query "notification" {
|
|
145
|
+
where = $db.notification.user_id == $user.id
|
|
146
|
+
&& $db.notification.sent == false
|
|
147
|
+
} as $notifications
|
|
148
|
+
|
|
149
|
+
conditional {
|
|
150
|
+
if (($notifications|count) > 0) {
|
|
151
|
+
util.send_email {
|
|
152
|
+
service_provider = "resend"
|
|
153
|
+
api_key = $env.RESEND_API_KEY
|
|
154
|
+
to = $user.email
|
|
155
|
+
from = "noreply@example.com"
|
|
156
|
+
subject = "Your Daily Digest"
|
|
157
|
+
message = "You have " ~ ($notifications|count) ~ " new notifications"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
schedule = [{starts_on: 2025-01-01 08:00:00+0000, freq: 86400}]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### External API Sync
|
|
169
|
+
```xs
|
|
170
|
+
task "sync_exchange_rates" {
|
|
171
|
+
description = "Sync currency exchange rates every hour"
|
|
172
|
+
stack {
|
|
173
|
+
api.request {
|
|
174
|
+
url = "https://api.exchangerate.com/latest"
|
|
175
|
+
method = "GET"
|
|
176
|
+
headers = []|push:("Authorization: Bearer " ~ $env.EXCHANGE_API_KEY)
|
|
177
|
+
} as $response
|
|
178
|
+
|
|
179
|
+
foreach ($response.rates|entries) {
|
|
180
|
+
each as $rate {
|
|
181
|
+
db.add_or_edit "exchange_rate" {
|
|
182
|
+
field_name = "currency"
|
|
183
|
+
field_value = $rate.key
|
|
184
|
+
data = {
|
|
185
|
+
currency: $rate.key,
|
|
186
|
+
rate: $rate.value,
|
|
187
|
+
updated_at: now
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
schedule = [{starts_on: 2025-01-01 00:00:00+0000, freq: 3600}]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Stack Operations
|
|
200
|
+
|
|
201
|
+
Tasks use the same stack operations as functions:
|
|
202
|
+
|
|
203
|
+
- `db.query`, `db.add`, `db.edit`, `db.del` - Database operations
|
|
204
|
+
- `api.request` - External API calls
|
|
205
|
+
- `function.run` - Call functions
|
|
206
|
+
- `foreach`, `for`, `while` - Loops
|
|
207
|
+
- `conditional` - Branching
|
|
208
|
+
- `debug.log` - Logging
|
|
209
|
+
- `util.send_email` - Send emails
|
|
210
|
+
- `try_catch` - Error handling
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Error Handling
|
|
215
|
+
|
|
216
|
+
```xs
|
|
217
|
+
task "risky_sync" {
|
|
218
|
+
stack {
|
|
219
|
+
try_catch {
|
|
220
|
+
try {
|
|
221
|
+
api.request {
|
|
222
|
+
url = "https://external-api.com/data"
|
|
223
|
+
method = "GET"
|
|
224
|
+
} as $data
|
|
225
|
+
# Process data...
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
util.send_email {
|
|
229
|
+
service_provider = "resend"
|
|
230
|
+
api_key = $env.RESEND_API_KEY
|
|
231
|
+
to = "alerts@example.com"
|
|
232
|
+
from = "system@example.com"
|
|
233
|
+
subject = "Sync Task Failed"
|
|
234
|
+
message = "The risky_sync task failed"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
schedule = [{starts_on: 2025-01-01 00:00:00+0000, freq: 3600}]
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Best Practices
|
|
246
|
+
|
|
247
|
+
1. **Use descriptive names** - Indicate what and when: `daily_cleanup`, `hourly_sync`
|
|
248
|
+
2. **Add descriptions** - Explain the purpose and schedule
|
|
249
|
+
3. **Handle errors** - Use try_catch for external dependencies
|
|
250
|
+
4. **Log important events** - Use debug.log for tracking
|
|
251
|
+
5. **Use appropriate frequency** - Don't run more often than needed
|
|
252
|
+
6. **Consider timezone** - Schedule uses UTC (+0000)
|
|
253
|
+
7. **Batch operations** - Process in chunks for large datasets
|
|
254
|
+
8. **Set end dates** - Use ends_on for temporary schedules
|