@cleocode/cleo 2026.3.4 → 2026.3.6
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/dist/cli/index.js +2277 -609
- package/dist/cli/index.js.map +4 -4
- package/dist/mcp/index.js +1838 -443
- package/dist/mcp/index.js.map +4 -4
- package/package.json +1 -1
- package/packages/ct-skills/index.js +1 -1
- package/packages/ct-skills/package.json +0 -2
- package/packages/ct-skills/profiles/core.json +1 -1
- package/packages/ct-skills/profiles/full.json +4 -5
- package/packages/ct-skills/profiles/minimal.json +3 -3
- package/packages/ct-skills/profiles/recommended.json +2 -2
- package/packages/ct-skills/provider-skills-map.json +97 -0
- package/packages/ct-skills/skills/_shared/skill-chaining-patterns.md +23 -26
- package/packages/ct-skills/skills/_shared/testing-framework-config.md +9 -9
- package/packages/ct-skills/skills/ct-cleo/SKILL.md +21 -1
- package/packages/ct-skills/skills/ct-dev-workflow/SKILL.md +1 -1
- package/packages/ct-skills/skills/ct-documentor/SKILL.md +1 -1
- package/packages/ct-skills/skills/ct-epic-architect/SKILL.md +1 -1
- package/packages/ct-skills/skills/ct-orchestrator/SKILL.md +119 -43
- package/packages/ct-skills/skills/ct-orchestrator/orchestrator-prompt.txt +17 -0
- package/packages/ct-skills/skills/ct-orchestrator/references/orchestrator-patterns.md +1 -1
- package/packages/ct-skills/skills/ct-research-agent/SKILL.md +1 -1
- package/packages/ct-skills/skills/ct-spec-writer/SKILL.md +1 -1
- package/packages/ct-skills/skills/ct-task-executor/SKILL.md +1 -1
- package/packages/ct-skills/skills/ct-validator/SKILL.md +1 -1
- package/packages/ct-skills/skills/manifest.json +217 -947
- package/packages/ct-skills/skills.json +244 -3
- package/templates/CLEO-INJECTION.md +24 -0
- package/packages/ct-skills/protocols/agent-protocol.md +0 -260
- package/packages/ct-skills/protocols/artifact-publish.md +0 -587
- package/packages/ct-skills/protocols/consensus.md +0 -309
- package/packages/ct-skills/protocols/contribution.md +0 -375
- package/packages/ct-skills/protocols/decomposition.md +0 -352
- package/packages/ct-skills/protocols/implementation.md +0 -344
- package/packages/ct-skills/protocols/provenance.md +0 -600
- package/packages/ct-skills/protocols/release.md +0 -635
- package/packages/ct-skills/protocols/research.md +0 -248
- package/packages/ct-skills/protocols/specification.md +0 -287
- package/packages/ct-skills/protocols/testing.md +0 -346
- package/packages/ct-skills/protocols/validation.md +0 -229
- package/packages/ct-skills/skills/ct-gitbook/SKILL.md +0 -516
- package/packages/ct-skills/skills/ct-gitbook/assets/SUMMARY.md +0 -28
- package/packages/ct-skills/skills/ct-gitbook/assets/gitbook.yaml +0 -14
- package/packages/ct-skills/skills/ct-gitbook/references/api-sdk.md +0 -318
- package/packages/ct-skills/skills/ct-gitbook/references/auth-sso.md +0 -208
- package/packages/ct-skills/skills/ct-gitbook/references/change-requests.md +0 -169
- package/packages/ct-skills/skills/ct-gitbook/references/content-blocks.md +0 -230
- package/packages/ct-skills/skills/ct-gitbook/references/docs-sites.md +0 -202
- package/packages/ct-skills/skills/ct-gitbook/references/git-sync.md +0 -175
- package/packages/ct-skills/skills/ct-gitbook/references/llm-ready.md +0 -178
- package/packages/ct-skills/skills/ct-gitbook/references/migration.md +0 -263
- package/packages/ct-skills/skills/ct-library-implementer-bash/SKILL.md +0 -316
- package/packages/ct-skills/skills/ct-skill-lookup/SKILL.md +0 -179
- package/packages/ct-skills/skills/ct-test-writer-bats/SKILL.md +0 -347
- package/packages/ct-skills/skills/railway-platform/SKILL.md +0 -506
- package/packages/ct-skills/skills/railway-platform/_shared/scripts/railway-api.sh +0 -180
- package/packages/ct-skills/skills/railway-platform/_shared/scripts/railway-common.sh +0 -262
- package/packages/ct-skills/skills/railway-platform/references/01-getting-started.md +0 -149
- package/packages/ct-skills/skills/railway-platform/references/02-projects.md +0 -116
- package/packages/ct-skills/skills/railway-platform/references/03-services.md +0 -147
- package/packages/ct-skills/skills/railway-platform/references/04-deployments.md +0 -210
- package/packages/ct-skills/skills/railway-platform/references/05-databases.md +0 -142
- package/packages/ct-skills/skills/railway-platform/references/06-environments.md +0 -261
- package/packages/ct-skills/skills/railway-platform/references/07-domains.md +0 -139
- package/packages/ct-skills/skills/railway-platform/references/08-volumes.md +0 -533
- package/packages/ct-skills/skills/railway-platform/references/09-networking.md +0 -592
- package/packages/ct-skills/skills/railway-platform/references/10-cron.md +0 -488
- package/packages/ct-skills/skills/railway-platform/references/11-functions.md +0 -170
- package/packages/ct-skills/skills/railway-platform/references/12-monorepo.md +0 -294
- package/packages/ct-skills/skills/railway-platform/references/13-troubleshooting.md +0 -335
- package/packages/ct-skills/skills/railway-platform/references/14-railway-metal.md +0 -197
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
# Scheduled Jobs (Cron)
|
|
2
|
-
|
|
3
|
-
Railway cron jobs run services on a schedule using standard cron expressions.
|
|
4
|
-
|
|
5
|
-
## When to Use Cron
|
|
6
|
-
|
|
7
|
-
**Good use cases:**
|
|
8
|
-
- Periodic data processing
|
|
9
|
-
- Scheduled reports
|
|
10
|
-
- Cleanup tasks
|
|
11
|
-
- Backup operations
|
|
12
|
-
- Monitoring checks
|
|
13
|
-
- ETL jobs
|
|
14
|
-
|
|
15
|
-
**Not suitable for:**
|
|
16
|
-
- Real-time processing
|
|
17
|
-
- User-triggered actions
|
|
18
|
-
- Long-running continuous tasks
|
|
19
|
-
- Jobs requiring sub-minute precision
|
|
20
|
-
|
|
21
|
-
## Cron Expression Format
|
|
22
|
-
|
|
23
|
-
Standard 5-field cron format:
|
|
24
|
-
```
|
|
25
|
-
* * * * *
|
|
26
|
-
│ │ │ │ │
|
|
27
|
-
│ │ │ │ └─── Day of week (0-7, 0/7 = Sunday)
|
|
28
|
-
│ │ │ └───── Month (1-12)
|
|
29
|
-
│ │ └─────── Day of month (1-31)
|
|
30
|
-
│ └───────── Hour (0-23)
|
|
31
|
-
└─────────── Minute (0-59)
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Common Patterns
|
|
35
|
-
|
|
36
|
-
| Expression | Meaning |
|
|
37
|
-
|------------|---------|
|
|
38
|
-
| `0 * * * *` | Every hour at minute 0 |
|
|
39
|
-
| `0 0 * * *` | Daily at midnight |
|
|
40
|
-
| `0 0 * * 0` | Weekly on Sunday at midnight |
|
|
41
|
-
| `0 0 1 * *` | Monthly on 1st at midnight |
|
|
42
|
-
| `*/5 * * * *` | Every 5 minutes |
|
|
43
|
-
| `0 9 * * 1-5` | Weekdays at 9 AM |
|
|
44
|
-
| `0 */6 * * *` | Every 6 hours |
|
|
45
|
-
|
|
46
|
-
### Special Characters
|
|
47
|
-
|
|
48
|
-
| Char | Meaning | Example |
|
|
49
|
-
|------|---------|---------|
|
|
50
|
-
| `*` | Any value | `* * * * *` = every minute |
|
|
51
|
-
| `,` | List | `0 0,12 * * *` = midnight and noon |
|
|
52
|
-
| `-` | Range | `0 9-17 * * 1-5` = 9-5 weekdays |
|
|
53
|
-
| `/` | Step | `*/15 * * * *` = every 15 minutes |
|
|
54
|
-
|
|
55
|
-
## Creating Cron Services
|
|
56
|
-
|
|
57
|
-
### Via Environment Config
|
|
58
|
-
|
|
59
|
-
```json
|
|
60
|
-
{
|
|
61
|
-
"services": {
|
|
62
|
-
"nightly-cleanup": {
|
|
63
|
-
"deploy": {
|
|
64
|
-
"cronSchedule": "0 2 * * *"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Via CLI
|
|
72
|
-
|
|
73
|
-
Currently, cron schedules must be set via environment configuration or dashboard.
|
|
74
|
-
|
|
75
|
-
## Cron Service Structure
|
|
76
|
-
|
|
77
|
-
### One-Shot Jobs
|
|
78
|
-
|
|
79
|
-
Job exits after completing work:
|
|
80
|
-
|
|
81
|
-
```javascript
|
|
82
|
-
// cleanup-job.js
|
|
83
|
-
const { PrismaClient } = require('@prisma/client');
|
|
84
|
-
const prisma = new PrismaClient();
|
|
85
|
-
|
|
86
|
-
async function cleanup() {
|
|
87
|
-
const thirtyDaysAgo = new Date();
|
|
88
|
-
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
89
|
-
|
|
90
|
-
const deleted = await prisma.session.deleteMany({
|
|
91
|
-
where: {
|
|
92
|
-
createdAt: {
|
|
93
|
-
lt: thirtyDaysAgo
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
console.log(`Deleted ${deleted.count} old sessions`);
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
cleanup().catch(err => {
|
|
103
|
-
console.error('Cleanup failed:', err);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
});
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Continuous with Cron Trigger
|
|
109
|
-
|
|
110
|
-
Service runs continuously, cron triggers specific action:
|
|
111
|
-
|
|
112
|
-
```python
|
|
113
|
-
# monitor.py
|
|
114
|
-
import os
|
|
115
|
-
import time
|
|
116
|
-
from datetime import datetime
|
|
117
|
-
|
|
118
|
-
def check_system_health():
|
|
119
|
-
# Run health checks
|
|
120
|
-
print(f"[{datetime.now()}] Running health checks...")
|
|
121
|
-
# ... health check logic
|
|
122
|
-
return True
|
|
123
|
-
|
|
124
|
-
if __name__ == "__main__":
|
|
125
|
-
# For cron-triggered runs
|
|
126
|
-
if os.environ.get('CRON_TRIGGER') == 'true':
|
|
127
|
-
check_system_health()
|
|
128
|
-
exit(0)
|
|
129
|
-
|
|
130
|
-
# For continuous monitoring
|
|
131
|
-
while True:
|
|
132
|
-
check_system_health()
|
|
133
|
-
time.sleep(60) # Check every minute
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
**Cron config:**
|
|
137
|
-
```json
|
|
138
|
-
{
|
|
139
|
-
"services": {
|
|
140
|
-
"monitor": {
|
|
141
|
-
"variables": {
|
|
142
|
-
"CRON_TRIGGER": {"value": "true"}
|
|
143
|
-
},
|
|
144
|
-
"deploy": {
|
|
145
|
-
"cronSchedule": "0 */6 * * *"
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Use Cases
|
|
153
|
-
|
|
154
|
-
### 1. Database Cleanup
|
|
155
|
-
|
|
156
|
-
**Schedule:** Daily at 2 AM
|
|
157
|
-
|
|
158
|
-
```json
|
|
159
|
-
{
|
|
160
|
-
"services": {
|
|
161
|
-
"cleanup": {
|
|
162
|
-
"source": {
|
|
163
|
-
"repo": "myorg/backend",
|
|
164
|
-
"branch": "main"
|
|
165
|
-
},
|
|
166
|
-
"build": {
|
|
167
|
-
"buildCommand": "npm install"
|
|
168
|
-
},
|
|
169
|
-
"deploy": {
|
|
170
|
-
"startCommand": "node jobs/cleanup.js",
|
|
171
|
-
"cronSchedule": "0 2 * * *"
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### 2. Daily Report Generation
|
|
179
|
-
|
|
180
|
-
**Schedule:** Weekdays at 9 AM
|
|
181
|
-
|
|
182
|
-
```json
|
|
183
|
-
{
|
|
184
|
-
"services": {
|
|
185
|
-
"reports": {
|
|
186
|
-
"source": {
|
|
187
|
-
"repo": "myorg/backend"
|
|
188
|
-
},
|
|
189
|
-
"variables": {
|
|
190
|
-
"DATABASE_URL": {"value": "${{Postgres.DATABASE_URL}}"}
|
|
191
|
-
},
|
|
192
|
-
"deploy": {
|
|
193
|
-
"startCommand": "python jobs/generate_reports.py",
|
|
194
|
-
"cronSchedule": "0 9 * * 1-5"
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### 3. Hourly Data Sync
|
|
202
|
-
|
|
203
|
-
**Schedule:** Every hour
|
|
204
|
-
|
|
205
|
-
```json
|
|
206
|
-
{
|
|
207
|
-
"services": {
|
|
208
|
-
"sync": {
|
|
209
|
-
"source": {
|
|
210
|
-
"repo": "myorg/integrations"
|
|
211
|
-
},
|
|
212
|
-
"variables": {
|
|
213
|
-
"API_KEY": {"value": "secret", "encrypted": true}
|
|
214
|
-
},
|
|
215
|
-
"deploy": {
|
|
216
|
-
"startCommand": "node jobs/sync-external-api.js",
|
|
217
|
-
"cronSchedule": "0 * * * *"
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### 4. Weekly Backup
|
|
225
|
-
|
|
226
|
-
**Schedule:** Sundays at midnight
|
|
227
|
-
|
|
228
|
-
```json
|
|
229
|
-
{
|
|
230
|
-
"services": {
|
|
231
|
-
"backup": {
|
|
232
|
-
"volumes": {
|
|
233
|
-
"backups": {
|
|
234
|
-
"mountPath": "/backups"
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
"variables": {
|
|
238
|
-
"DATABASE_URL": {"value": "${{Postgres.DATABASE_URL}}"},
|
|
239
|
-
"BACKUP_DIR": {"value": "/backups"}
|
|
240
|
-
},
|
|
241
|
-
"deploy": {
|
|
242
|
-
"startCommand": "bash scripts/backup-db.sh",
|
|
243
|
-
"cronSchedule": "0 0 * * 0"
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**backup-db.sh:**
|
|
251
|
-
```bash
|
|
252
|
-
#!/bin/bash
|
|
253
|
-
DATE=$(date +%Y%m%d_%H%M%S)
|
|
254
|
-
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.sql"
|
|
255
|
-
|
|
256
|
-
pg_dump "$DATABASE_URL" > "$BACKUP_FILE"
|
|
257
|
-
gzip "$BACKUP_FILE"
|
|
258
|
-
|
|
259
|
-
echo "Backup complete: $BACKUP_FILE.gz"
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
## Monitoring Cron Jobs
|
|
263
|
-
|
|
264
|
-
### View Execution History
|
|
265
|
-
|
|
266
|
-
```bash
|
|
267
|
-
# List deployments
|
|
268
|
-
railway deployment list --service my-cron-job
|
|
269
|
-
|
|
270
|
-
# View logs
|
|
271
|
-
railway logs --service my-cron-job --lines 100
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### Success/Failure Handling
|
|
275
|
-
|
|
276
|
-
**Exit codes matter:**
|
|
277
|
-
- `0` = Success
|
|
278
|
-
- Non-zero = Failure
|
|
279
|
-
|
|
280
|
-
**Notification on failure:**
|
|
281
|
-
```javascript
|
|
282
|
-
// In your job
|
|
283
|
-
async function main() {
|
|
284
|
-
try {
|
|
285
|
-
await runJob();
|
|
286
|
-
console.log('Job completed successfully');
|
|
287
|
-
process.exit(0);
|
|
288
|
-
} catch (error) {
|
|
289
|
-
console.error('Job failed:', error);
|
|
290
|
-
await sendAlert(error); // Send to Slack/email
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## Best Practices
|
|
297
|
-
|
|
298
|
-
### 1. Idempotency
|
|
299
|
-
|
|
300
|
-
Cron jobs should be safe to run multiple times:
|
|
301
|
-
|
|
302
|
-
```python
|
|
303
|
-
# Good - checks before acting
|
|
304
|
-
def process_orders():
|
|
305
|
-
orders = get_pending_orders()
|
|
306
|
-
for order in orders:
|
|
307
|
-
if not order.processed:
|
|
308
|
-
process_order(order)
|
|
309
|
-
mark_processed(order)
|
|
310
|
-
|
|
311
|
-
# Bad - might process twice
|
|
312
|
-
def process_orders():
|
|
313
|
-
orders = get_orders()
|
|
314
|
-
for order in orders:
|
|
315
|
-
process_order(order) # No check!
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### 2. Timeouts
|
|
319
|
-
|
|
320
|
-
Prevent hanging jobs:
|
|
321
|
-
|
|
322
|
-
```json
|
|
323
|
-
{
|
|
324
|
-
"services": {
|
|
325
|
-
"job": {
|
|
326
|
-
"deploy": {
|
|
327
|
-
"healthcheck": {
|
|
328
|
-
"enabled": false # Don't use for one-shot jobs
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
**In code:**
|
|
337
|
-
```javascript
|
|
338
|
-
// Set timeout
|
|
339
|
-
const TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
340
|
-
setTimeout(() => {
|
|
341
|
-
console.error('Job timeout');
|
|
342
|
-
process.exit(1);
|
|
343
|
-
}, TIMEOUT);
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### 3. Logging
|
|
347
|
-
|
|
348
|
-
Always log start, end, and key events:
|
|
349
|
-
|
|
350
|
-
```javascript
|
|
351
|
-
console.log(`[${new Date().toISOString()}] Job starting`);
|
|
352
|
-
console.log(`[${new Date().toISOString()}] Processing ${items.length} items`);
|
|
353
|
-
console.log(`[${new Date().toISOString()}] Job completed: ${results.success} success, ${results.failed} failed`);
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### 4. Resource Limits
|
|
357
|
-
|
|
358
|
-
Consider resource usage:
|
|
359
|
-
- Don't schedule too many concurrent jobs
|
|
360
|
-
- Memory-intensive jobs may need limits
|
|
361
|
-
- CPU usage affects billing
|
|
362
|
-
|
|
363
|
-
### 5. Error Recovery
|
|
364
|
-
|
|
365
|
-
```python
|
|
366
|
-
import time
|
|
367
|
-
from datetime import datetime
|
|
368
|
-
|
|
369
|
-
def run_with_retry(func, max_retries=3):
|
|
370
|
-
for attempt in range(max_retries):
|
|
371
|
-
try:
|
|
372
|
-
return func()
|
|
373
|
-
except Exception as e:
|
|
374
|
-
print(f"Attempt {attempt + 1} failed: {e}")
|
|
375
|
-
if attempt < max_retries - 1:
|
|
376
|
-
time.sleep(5 * (attempt + 1)) # Exponential backoff
|
|
377
|
-
else:
|
|
378
|
-
raise
|
|
379
|
-
|
|
380
|
-
# Use it
|
|
381
|
-
try:
|
|
382
|
-
result = run_with_retry(process_data)
|
|
383
|
-
print(f"Success: {result}")
|
|
384
|
-
except Exception as e:
|
|
385
|
-
print(f"Failed after retries: {e}")
|
|
386
|
-
exit(1)
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
## Troubleshooting
|
|
390
|
-
|
|
391
|
-
### Job Not Running
|
|
392
|
-
|
|
393
|
-
**Check:**
|
|
394
|
-
1. Cron expression is valid
|
|
395
|
-
2. Service has cronSchedule configured
|
|
396
|
-
3. No overlapping executions blocking
|
|
397
|
-
4. Check deployment list for last run
|
|
398
|
-
|
|
399
|
-
**Debug:**
|
|
400
|
-
```bash
|
|
401
|
-
# Check last deployment
|
|
402
|
-
railway deployment list --service my-job --limit 5
|
|
403
|
-
|
|
404
|
-
# View logs
|
|
405
|
-
railway logs --service my-job --lines 200
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
### Job Running Too Often
|
|
409
|
-
|
|
410
|
-
**Common mistakes:**
|
|
411
|
-
- `* * * * *` = every minute (probably too frequent)
|
|
412
|
-
- `*/1 * * * *` = every minute
|
|
413
|
-
- `0 0 * * *` = daily at midnight
|
|
414
|
-
|
|
415
|
-
**Verify expression:**
|
|
416
|
-
```bash
|
|
417
|
-
# Use online cron parser
|
|
418
|
-
echo "0 2 * * *" | python3 -c "
|
|
419
|
-
import sys
|
|
420
|
-
import croniter
|
|
421
|
-
from datetime import datetime
|
|
422
|
-
|
|
423
|
-
cron = sys.stdin.read().strip()
|
|
424
|
-
itr = croniter.croniter(cron, datetime.now())
|
|
425
|
-
print('Next 5 runs:')
|
|
426
|
-
for _ in range(5):
|
|
427
|
-
print(itr.get_next(datetime))
|
|
428
|
-
"
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### Job Failing Silently
|
|
432
|
-
|
|
433
|
-
**Ensure proper logging:**
|
|
434
|
-
```javascript
|
|
435
|
-
// Always log to stdout/stderr
|
|
436
|
-
console.log = (...args) => {
|
|
437
|
-
process.stdout.write(`[${new Date().toISOString()}] ${args.join(' ')}\n`);
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
console.error = (...args) => {
|
|
441
|
-
process.stderr.write(`[${new Date().toISOString()}] ERROR: ${args.join(' ')}\n`);
|
|
442
|
-
};
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### Long-Running Jobs
|
|
446
|
-
|
|
447
|
-
**Issue:** Job exceeds cron interval
|
|
448
|
-
|
|
449
|
-
**Solution:**
|
|
450
|
-
1. Extend interval
|
|
451
|
-
2. Implement locking to prevent overlap
|
|
452
|
-
3. Break into smaller jobs
|
|
453
|
-
|
|
454
|
-
```python
|
|
455
|
-
# Simple lock implementation
|
|
456
|
-
import redis
|
|
457
|
-
import os
|
|
458
|
-
|
|
459
|
-
redis_client = redis.from_url(os.environ['REDIS_URL'])
|
|
460
|
-
|
|
461
|
-
def with_lock(job_name, func):
|
|
462
|
-
lock_key = f"cron_lock:{job_name}"
|
|
463
|
-
|
|
464
|
-
# Try to acquire lock (expires in 1 hour)
|
|
465
|
-
acquired = redis_client.set(lock_key, "1", nx=True, ex=3600)
|
|
466
|
-
|
|
467
|
-
if not acquired:
|
|
468
|
-
print(f"Job {job_name} already running, skipping")
|
|
469
|
-
return
|
|
470
|
-
|
|
471
|
-
try:
|
|
472
|
-
func()
|
|
473
|
-
finally:
|
|
474
|
-
redis_client.delete(lock_key)
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
## Summary
|
|
478
|
-
|
|
479
|
-
| Aspect | Details |
|
|
480
|
-
|--------|---------|
|
|
481
|
-
| **Format** | Standard 5-field cron |
|
|
482
|
-
| **Precision** | Minute-level |
|
|
483
|
-
| **Concurrency** | No overlapping by default |
|
|
484
|
-
| **Timeout** | Service-level limits apply |
|
|
485
|
-
| **Monitoring** | Via deployment logs |
|
|
486
|
-
| **Cost** | Billed for execution time |
|
|
487
|
-
|
|
488
|
-
**Golden rule:** Cron jobs should be simple, idempotent, and well-logged. For complex workflows, consider using a job queue instead.
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# Railway Functions
|
|
2
|
-
|
|
3
|
-
Serverless functions for event-driven workloads (Beta feature).
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Railway Functions provide serverless execution for:
|
|
8
|
-
- HTTP endpoints
|
|
9
|
-
- Event triggers
|
|
10
|
-
- Scheduled tasks
|
|
11
|
-
- Background processing
|
|
12
|
-
|
|
13
|
-
## Creating Functions
|
|
14
|
-
|
|
15
|
-
### Via CLI
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
railway functions new my-function
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Function Structure
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
my-function/
|
|
25
|
-
├── function.toml # Function configuration
|
|
26
|
-
├── handler.js # Function code
|
|
27
|
-
└── package.json # Dependencies
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Handler Code
|
|
31
|
-
|
|
32
|
-
```javascript
|
|
33
|
-
// handler.js
|
|
34
|
-
module.exports = async (event, context) => {
|
|
35
|
-
const { method, path, body, query } = event;
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
statusCode: 200,
|
|
39
|
-
body: JSON.stringify({
|
|
40
|
-
message: "Hello from Railway Functions",
|
|
41
|
-
path,
|
|
42
|
-
method
|
|
43
|
-
})
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Configuration
|
|
49
|
-
|
|
50
|
-
### function.toml
|
|
51
|
-
|
|
52
|
-
```toml
|
|
53
|
-
name = "my-function"
|
|
54
|
-
runtime = "nodejs18"
|
|
55
|
-
|
|
56
|
-
[trigger]
|
|
57
|
-
type = "http"
|
|
58
|
-
path = "/api/hello"
|
|
59
|
-
methods = ["GET", "POST"]
|
|
60
|
-
|
|
61
|
-
[resources]
|
|
62
|
-
memory = "512MB"
|
|
63
|
-
timeout = "30s"
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Environment Variables
|
|
67
|
-
|
|
68
|
-
```toml
|
|
69
|
-
[env]
|
|
70
|
-
DATABASE_URL = "${{Postgres.DATABASE_URL}}"
|
|
71
|
-
API_KEY = "${{shared.API_KEY}}"
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Deployment
|
|
75
|
-
|
|
76
|
-
### Deploy Function
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
railway functions push
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### List Functions
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
railway functions list
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Triggers
|
|
89
|
-
|
|
90
|
-
### HTTP Trigger
|
|
91
|
-
|
|
92
|
-
```toml
|
|
93
|
-
[trigger]
|
|
94
|
-
type = "http"
|
|
95
|
-
path = "/webhook"
|
|
96
|
-
methods = ["POST"]
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Event Trigger
|
|
100
|
-
|
|
101
|
-
```toml
|
|
102
|
-
[trigger]
|
|
103
|
-
type = "event"
|
|
104
|
-
event = "deployment.success"
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Schedule Trigger
|
|
108
|
-
|
|
109
|
-
```toml
|
|
110
|
-
[trigger]
|
|
111
|
-
type = "schedule"
|
|
112
|
-
cron = "0 9 * * *"
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Use Cases
|
|
116
|
-
|
|
117
|
-
### 1. Webhook Handler
|
|
118
|
-
|
|
119
|
-
```javascript
|
|
120
|
-
module.exports = async (event) => {
|
|
121
|
-
const { body } = event;
|
|
122
|
-
|
|
123
|
-
// Process webhook payload
|
|
124
|
-
await processWebhook(body);
|
|
125
|
-
|
|
126
|
-
return { statusCode: 200 };
|
|
127
|
-
};
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### 2. Image Processing
|
|
131
|
-
|
|
132
|
-
```javascript
|
|
133
|
-
const sharp = require('sharp');
|
|
134
|
-
|
|
135
|
-
module.exports = async (event) => {
|
|
136
|
-
const { imageUrl } = event.body;
|
|
137
|
-
|
|
138
|
-
const processed = await sharp(imageUrl)
|
|
139
|
-
.resize(800, 600)
|
|
140
|
-
.toBuffer();
|
|
141
|
-
|
|
142
|
-
await uploadToS3(processed);
|
|
143
|
-
|
|
144
|
-
return { statusCode: 200 };
|
|
145
|
-
};
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 3. Notification Service
|
|
149
|
-
|
|
150
|
-
```javascript
|
|
151
|
-
module.exports = async (event) => {
|
|
152
|
-
const { userId, message } = event.body;
|
|
153
|
-
|
|
154
|
-
await sendNotification(userId, message);
|
|
155
|
-
|
|
156
|
-
return { statusCode: 200 };
|
|
157
|
-
};
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## Limitations
|
|
161
|
-
|
|
162
|
-
- **Beta feature** - API may change
|
|
163
|
-
- **Cold starts** - First invocation may have latency
|
|
164
|
-
- **Execution timeout** - Configurable, default 30s
|
|
165
|
-
- **Memory limits** - Configurable, default 512MB
|
|
166
|
-
|
|
167
|
-
## Next Steps
|
|
168
|
-
|
|
169
|
-
- [10-cron.md](10-cron.md) - Scheduled jobs
|
|
170
|
-
- [13-troubleshooting.md](13-troubleshooting.md) - Function debugging
|