lua-cli 3.0.0-alpha.1 โ 3.0.0-alpha.5
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/api/job.api.service.d.ts +16 -7
- package/dist/api/job.api.service.js +21 -5
- package/dist/api/postprocessor.api.service.d.ts +61 -1
- package/dist/api/postprocessor.api.service.js +35 -0
- package/dist/api/preprocessor.api.service.d.ts +61 -1
- package/dist/api/preprocessor.api.service.js +35 -0
- package/dist/api-exports.d.ts +26 -6
- package/dist/api-exports.js +42 -29
- package/dist/cli/command-definitions.js +13 -6
- package/dist/commands/chat.js +32 -5
- package/dist/commands/compile.js +16 -2
- package/dist/commands/dev.js +23 -2
- package/dist/commands/push.d.ts +6 -2
- package/dist/commands/push.js +412 -6
- package/dist/commands/test.js +18 -2
- package/dist/common/job.instance.d.ts +3 -0
- package/dist/common/job.instance.js +8 -0
- package/dist/config/constants.d.ts +6 -5
- package/dist/config/constants.js +12 -10
- package/dist/interfaces/chat.d.ts +30 -1
- package/dist/interfaces/jobs.d.ts +21 -0
- package/dist/types/skill.d.ts +75 -56
- package/dist/types/skill.js +53 -59
- package/dist/utils/bundling.d.ts +13 -4
- package/dist/utils/bundling.js +83 -26
- package/dist/utils/compile.js +27 -6
- package/dist/utils/dev-api.d.ts +42 -2
- package/dist/utils/dev-api.js +177 -4
- package/dist/utils/dev-server.d.ts +1 -1
- package/dist/utils/dev-server.js +4 -4
- package/dist/utils/dynamic-job-bundler.d.ts +17 -0
- package/dist/utils/dynamic-job-bundler.js +143 -0
- package/dist/utils/pre-bundle-jobs.d.ts +26 -0
- package/dist/utils/pre-bundle-jobs.js +176 -0
- package/dist/utils/sandbox-storage.d.ts +48 -0
- package/dist/utils/sandbox-storage.js +114 -0
- package/dist/utils/sandbox.d.ts +2 -2
- package/dist/utils/sandbox.js +23 -7
- package/package.json +1 -1
- package/template/lua.skill.yaml +47 -0
- package/template/package-lock.json +10505 -0
- package/template/package.json +2 -1
- package/template/src/index.ts +65 -3
- package/template/src/tools/CreateInlineJob.ts +42 -0
- package/API_REFERENCE.md +0 -1408
- package/CHANGELOG.md +0 -236
- package/CLI_REFERENCE.md +0 -908
- package/GETTING_STARTED.md +0 -1040
- package/INSTANCE_TYPES.md +0 -1158
- package/README.md +0 -865
- package/TEMPLATE_GUIDE.md +0 -1398
- package/USER_DATA_INSTANCE.md +0 -621
- package/template/AGENT_CONFIGURATION.md +0 -251
- package/template/COMPLEX_JOB_EXAMPLES.md +0 -795
- package/template/DYNAMIC_JOB_CREATION.md +0 -371
- package/template/TOOL_EXAMPLES.md +0 -655
- package/template/WEBHOOKS_JOBS_QUICKSTART.md +0 -318
- package/template/WEBHOOK_JOB_EXAMPLES.md +0 -817
- package/template/src/index-agent-example.ts +0 -201
- package/template/src/postprocessors/ResponseFormatter.ts +0 -151
- package/template/src/preprocessors/MessageFilter.ts +0 -91
|
@@ -1,795 +0,0 @@
|
|
|
1
|
-
# Complex Job Example: Game Score Tracker
|
|
2
|
-
|
|
3
|
-
**Level**: Advanced
|
|
4
|
-
**Demonstrates**: Interval jobs, self-termination, dynamic job creation, real-time monitoring
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## ๐ฏ What It Does
|
|
9
|
-
|
|
10
|
-
A complete system for tracking live game scores that:
|
|
11
|
-
1. โ
Creates a game tracking record
|
|
12
|
-
2. โ
Creates an interval job that starts at game time
|
|
13
|
-
3. โ
Fetches score updates every X seconds
|
|
14
|
-
4. โ
Checks if game is over
|
|
15
|
-
5. โ
**Stops itself** when game ends
|
|
16
|
-
6. โ
Logs final results
|
|
17
|
-
|
|
18
|
-
**Perfect for**: Sports scores, esports matches, auctions, stock monitoring, server status
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## ๐ Files
|
|
23
|
-
|
|
24
|
-
**Location**: `src/tools/GameScoreTrackerTool.ts`
|
|
25
|
-
|
|
26
|
-
**Contains 3 tools:**
|
|
27
|
-
1. `TrackGameScoresTool` - Start tracking a game
|
|
28
|
-
2. `GetGameScoresTool` - Get current scores
|
|
29
|
-
3. `StopGameTrackingTool` - Manually stop tracking
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## ๐ฎ The Flow
|
|
34
|
-
|
|
35
|
-
### User Perspective
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
User: "Track the Lakers vs Warriors game starting at 7 PM"
|
|
39
|
-
โ
|
|
40
|
-
Agent uses: track_game_scores
|
|
41
|
-
โ
|
|
42
|
-
Tool creates:
|
|
43
|
-
- Game record in database
|
|
44
|
-
- Interval job (updates every 60 seconds)
|
|
45
|
-
โ
|
|
46
|
-
Job starts at 7 PM
|
|
47
|
-
โ
|
|
48
|
-
Every 60 seconds, job:
|
|
49
|
-
1. Fetches latest scores
|
|
50
|
-
2. Updates database
|
|
51
|
-
3. Checks if game finished
|
|
52
|
-
โ
|
|
53
|
-
When game ends:
|
|
54
|
-
- Job logs final score
|
|
55
|
-
- Job deactivates itself โญ
|
|
56
|
-
- Tracking stops automatically
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## ๐ป Code Walkthrough
|
|
62
|
-
|
|
63
|
-
### Part 1: Create Game Record
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
async execute(input: {
|
|
67
|
-
gameId: string;
|
|
68
|
-
gameName: string;
|
|
69
|
-
startTime: string;
|
|
70
|
-
updateIntervalSeconds: number;
|
|
71
|
-
}) {
|
|
72
|
-
// Create game record
|
|
73
|
-
const gameRecord = {
|
|
74
|
-
gameId: input.gameId,
|
|
75
|
-
gameName: input.gameName,
|
|
76
|
-
startTime: input.startTime,
|
|
77
|
-
status: 'scheduled',
|
|
78
|
-
currentScore: { home: 0, away: 0 },
|
|
79
|
-
createdAt: new Date().toISOString()
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const game = await Data.create('games', gameRecord);
|
|
83
|
-
console.log(`โ
Game record created: ${game.id}`);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Part 2: Calculate Start Time
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
// Calculate when job should start
|
|
90
|
-
const gameStartTime = new Date(input.startTime);
|
|
91
|
-
const now = new Date();
|
|
92
|
-
|
|
93
|
-
// If game hasn't started, wait until start time
|
|
94
|
-
// If already started, begin immediately
|
|
95
|
-
const jobStartTime = gameStartTime > now ? gameStartTime : now;
|
|
96
|
-
|
|
97
|
-
console.log(`โฐ Job will start at: ${jobStartTime.toLocaleString()}`);
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Key insight**: Job can be created now but scheduled to start later!
|
|
101
|
-
|
|
102
|
-
### Part 3: Capture Variables for Closure
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
// Capture for job closure
|
|
106
|
-
const gameId = input.gameId;
|
|
107
|
-
const gameName = input.gameName;
|
|
108
|
-
const intervalSeconds = input.updateIntervalSeconds;
|
|
109
|
-
const apiEndpoint = input.apiEndpoint || 'https://api.example.com/scores';
|
|
110
|
-
const gameRecordId = game.id;
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Part 4: Create Self-Terminating Interval Job
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
const job = await Jobs.create({
|
|
117
|
-
name: `track-game-${gameId}`,
|
|
118
|
-
description: `Live score tracking for ${gameName}`,
|
|
119
|
-
|
|
120
|
-
// Interval job - runs every X seconds
|
|
121
|
-
schedule: {
|
|
122
|
-
type: 'interval',
|
|
123
|
-
seconds: intervalSeconds
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
timeout: 30,
|
|
127
|
-
retry: { maxAttempts: 2, backoffSeconds: 10 },
|
|
128
|
-
|
|
129
|
-
execute: async () => {
|
|
130
|
-
console.log(`๐ฎ Fetching score update for ${gameId}...`);
|
|
131
|
-
|
|
132
|
-
// 1. Get current game data
|
|
133
|
-
const gameData = await Data.getEntry('games', gameRecordId);
|
|
134
|
-
|
|
135
|
-
// 2. Check if already finished
|
|
136
|
-
if (gameData.data.status === 'finished') {
|
|
137
|
-
console.log(`๐ Game ${gameId} finished. Stopping job.`);
|
|
138
|
-
|
|
139
|
-
// โญ KEY FEATURE: Job stops itself!
|
|
140
|
-
await Jobs.deactivate(job.jobId);
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
action: 'job-stopped',
|
|
144
|
-
reason: 'game-finished',
|
|
145
|
-
finalScore: gameData.data.currentScore
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 3. Fetch latest scores from API
|
|
150
|
-
const response = await fetch(`${apiEndpoint}/${gameId}`);
|
|
151
|
-
const scoreUpdate = await response.json();
|
|
152
|
-
|
|
153
|
-
// 4. Update database
|
|
154
|
-
await Data.update('games', gameRecordId, {
|
|
155
|
-
...gameData.data,
|
|
156
|
-
currentScore: {
|
|
157
|
-
home: scoreUpdate.home,
|
|
158
|
-
away: scoreUpdate.away
|
|
159
|
-
},
|
|
160
|
-
quarter: scoreUpdate.quarter,
|
|
161
|
-
status: scoreUpdate.isFinished ? 'finished' : 'in-progress',
|
|
162
|
-
lastUpdate: new Date().toISOString()
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
console.log(`๐ Score: ${scoreUpdate.home} - ${scoreUpdate.away}`);
|
|
166
|
-
|
|
167
|
-
// 5. If game just ended, stop the job
|
|
168
|
-
if (scoreUpdate.isFinished) {
|
|
169
|
-
console.log(`๐ Game ${gameId} ended!`);
|
|
170
|
-
|
|
171
|
-
// Store final result
|
|
172
|
-
await Data.create('game-results', {
|
|
173
|
-
gameId: gameId,
|
|
174
|
-
finalScore: scoreUpdate,
|
|
175
|
-
finishedAt: new Date().toISOString()
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// โญ Stop the job!
|
|
179
|
-
await Jobs.deactivate(job.jobId);
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
action: 'game-ended-job-stopped',
|
|
183
|
-
finalScore: scoreUpdate
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Game still in progress
|
|
188
|
-
return {
|
|
189
|
-
action: 'score-updated',
|
|
190
|
-
currentScore: scoreUpdate
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
success: true,
|
|
197
|
-
game,
|
|
198
|
-
scoreTracker: {
|
|
199
|
-
jobId: job.jobId,
|
|
200
|
-
updateInterval: intervalSeconds
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## ๐ Key Concepts
|
|
209
|
-
|
|
210
|
-
### 1. **Self-Terminating Job**
|
|
211
|
-
|
|
212
|
-
The job can deactivate itself:
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
execute: async () => {
|
|
216
|
-
// Check condition
|
|
217
|
-
if (gameIsOver) {
|
|
218
|
-
// Stop myself!
|
|
219
|
-
await Jobs.deactivate(job.jobId);
|
|
220
|
-
return { action: 'stopped' };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Continue running
|
|
224
|
-
return { action: 'continue' };
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**How it works:**
|
|
229
|
-
- Job has access to `Jobs` API in sandbox
|
|
230
|
-
- Job can call `Jobs.deactivate(job.jobId)`
|
|
231
|
-
- Closure captures `job.jobId` from parent scope
|
|
232
|
-
- Job stops scheduling future executions
|
|
233
|
-
|
|
234
|
-
### 2. **Interval Jobs with Start Time**
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
// Job created now
|
|
238
|
-
const job = await Jobs.create({
|
|
239
|
-
schedule: {
|
|
240
|
-
type: 'interval',
|
|
241
|
-
seconds: 60 // Every 60 seconds
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// But starts at specific time (gameStartTime)
|
|
246
|
-
// Backend scheduler waits until start time, then begins interval
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### 3. **Closure Capture**
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
const gameId = 'game_123';
|
|
253
|
-
const apiEndpoint = 'https://api.example.com';
|
|
254
|
-
|
|
255
|
-
execute: async () => {
|
|
256
|
-
// These variables are captured!
|
|
257
|
-
const response = await fetch(`${apiEndpoint}/${gameId}`);
|
|
258
|
-
// โ โ
|
|
259
|
-
// Both available!
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### 4. **Error Handling Without Stopping**
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
execute: async () => {
|
|
267
|
-
try {
|
|
268
|
-
// Try to fetch from external API
|
|
269
|
-
const response = await fetch(apiEndpoint);
|
|
270
|
-
const data = await response.json();
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('API failed:', error);
|
|
273
|
-
|
|
274
|
-
// Don't stop job - just log error and continue
|
|
275
|
-
return { action: 'error', error: error.message };
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
Job continues running on next interval even if this execution failed.
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## ๐งช Testing the Example
|
|
285
|
-
|
|
286
|
-
### Step 1: Enable in Template
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
// In src/index.ts, add:
|
|
290
|
-
import {
|
|
291
|
-
TrackGameScoresTool,
|
|
292
|
-
GetGameScoresTool,
|
|
293
|
-
StopGameTrackingTool
|
|
294
|
-
} from "./tools/GameScoreTrackerTool";
|
|
295
|
-
|
|
296
|
-
const gamesSkill = new LuaSkill({
|
|
297
|
-
name: "games-skill",
|
|
298
|
-
version: "1.0.0",
|
|
299
|
-
description: "Live game score tracking",
|
|
300
|
-
context: "Track live sports scores with automatic updates",
|
|
301
|
-
tools: [
|
|
302
|
-
new TrackGameScoresTool(),
|
|
303
|
-
new GetGameScoresTool(),
|
|
304
|
-
new StopGameTrackingTool()
|
|
305
|
-
]
|
|
306
|
-
});
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Step 2: Compile & Test
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
lua compile
|
|
313
|
-
lua test skill
|
|
314
|
-
|
|
315
|
-
# Select: track_game_scores
|
|
316
|
-
# Enter:
|
|
317
|
-
# - gameId: LAL-GSW-2025-10-12
|
|
318
|
-
# - gameName: Lakers vs Warriors
|
|
319
|
-
# - startTime: 2025-10-12T19:00:00Z
|
|
320
|
-
# - updateIntervalSeconds: 60
|
|
321
|
-
|
|
322
|
-
# Output:
|
|
323
|
-
# โ
Game record created
|
|
324
|
-
# โ
Score tracking job created: job_abc123
|
|
325
|
-
# ๐ Job will update scores every 60 seconds
|
|
326
|
-
# โน๏ธ Job will automatically stop when game ends
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Step 3: Monitor Job
|
|
330
|
-
|
|
331
|
-
```bash
|
|
332
|
-
# Check job status
|
|
333
|
-
lua jobs production
|
|
334
|
-
# Select: View deployed jobs
|
|
335
|
-
# You'll see: track-game-LAL-GSW-2025-10-12
|
|
336
|
-
|
|
337
|
-
# Trigger manually to test
|
|
338
|
-
lua jobs production
|
|
339
|
-
# Select: Trigger job now
|
|
340
|
-
# See: Score update happens immediately
|
|
341
|
-
|
|
342
|
-
# View execution history
|
|
343
|
-
lua jobs production
|
|
344
|
-
# Select: View execution history
|
|
345
|
-
# See: All score updates logged
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Step 4: Get Current Scores
|
|
349
|
-
|
|
350
|
-
```bash
|
|
351
|
-
lua test skill
|
|
352
|
-
|
|
353
|
-
# Select: get_game_scores
|
|
354
|
-
# Enter: gameId: LAL-GSW-2025-10-12
|
|
355
|
-
|
|
356
|
-
# Output:
|
|
357
|
-
# {
|
|
358
|
-
# "game": {
|
|
359
|
-
# "name": "Lakers vs Warriors",
|
|
360
|
-
# "status": "in-progress",
|
|
361
|
-
# "currentScore": { "home": 45, "away": 42 },
|
|
362
|
-
# "quarter": 2,
|
|
363
|
-
# "timeRemaining": "5:23"
|
|
364
|
-
# }
|
|
365
|
-
# }
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
### Step 5: Verify Auto-Stop
|
|
369
|
-
|
|
370
|
-
When the game ends:
|
|
371
|
-
```
|
|
372
|
-
Job executes
|
|
373
|
-
โ
|
|
374
|
-
Fetches score: { isFinished: true }
|
|
375
|
-
โ
|
|
376
|
-
Logs final score
|
|
377
|
-
โ
|
|
378
|
-
Calls Jobs.deactivate(job.jobId)
|
|
379
|
-
โ
|
|
380
|
-
Job stops - no more executions! โ
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## ๐ What This Example Teaches
|
|
386
|
-
|
|
387
|
-
### Advanced Patterns
|
|
388
|
-
|
|
389
|
-
1. **Self-terminating jobs** - Jobs can stop themselves
|
|
390
|
-
2. **Interval with start time** - Delay first execution
|
|
391
|
-
3. **External API integration** - Fetch from external services
|
|
392
|
-
4. **Error recovery** - Continue on errors, stop on success
|
|
393
|
-
5. **Closure serialization** - Complex variable capture
|
|
394
|
-
6. **Mock data fallback** - Demo mode when API unavailable
|
|
395
|
-
|
|
396
|
-
### Real-World Applications
|
|
397
|
-
|
|
398
|
-
**Sports Scores:**
|
|
399
|
-
- Track NBA, NFL, soccer games
|
|
400
|
-
- Update scores every minute
|
|
401
|
-
- Stop when game ends
|
|
402
|
-
|
|
403
|
-
**Auctions:**
|
|
404
|
-
- Monitor auction bids
|
|
405
|
-
- Update every 30 seconds
|
|
406
|
-
- Stop when auction closes
|
|
407
|
-
|
|
408
|
-
**Stock Monitoring:**
|
|
409
|
-
- Track stock prices
|
|
410
|
-
- Update every 5 minutes during market hours
|
|
411
|
-
- Stop when market closes
|
|
412
|
-
|
|
413
|
-
**Server Monitoring:**
|
|
414
|
-
- Check server health every 1 minute
|
|
415
|
-
- Stop when maintenance window ends
|
|
416
|
-
|
|
417
|
-
**Trial Periods:**
|
|
418
|
-
- Check trial status daily
|
|
419
|
-
- Stop when user converts or trial ends
|
|
420
|
-
|
|
421
|
-
---
|
|
422
|
-
|
|
423
|
-
## ๐ง Customization Examples
|
|
424
|
-
|
|
425
|
-
### Change Update Frequency
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
// More frequent updates (every 30 seconds)
|
|
429
|
-
updateIntervalSeconds: 30
|
|
430
|
-
|
|
431
|
-
// Less frequent (every 5 minutes)
|
|
432
|
-
updateIntervalSeconds: 300
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### Add Notifications
|
|
436
|
-
|
|
437
|
-
```typescript
|
|
438
|
-
execute: async () => {
|
|
439
|
-
const scoreUpdate = await fetchScores();
|
|
440
|
-
|
|
441
|
-
// Check for significant events
|
|
442
|
-
if (scoreUpdate.home - scoreUpdate.away >= 10) {
|
|
443
|
-
// Big lead! Send notification
|
|
444
|
-
await Data.create('notifications', {
|
|
445
|
-
type: 'big-lead',
|
|
446
|
-
gameId: gameId,
|
|
447
|
-
score: scoreUpdate,
|
|
448
|
-
timestamp: new Date().toISOString()
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return { scoreUpdate };
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### Multiple Stop Conditions
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
execute: async () => {
|
|
460
|
-
const gameData = await Data.getEntry('games', gameRecordId);
|
|
461
|
-
|
|
462
|
-
// Stop if game finished
|
|
463
|
-
if (gameData.data.status === 'finished') {
|
|
464
|
-
await Jobs.deactivate(job.jobId);
|
|
465
|
-
return { action: 'stopped', reason: 'game-finished' };
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Stop if timeout (3 hours)
|
|
469
|
-
const startTime = new Date(gameData.data.startTime);
|
|
470
|
-
const elapsed = Date.now() - startTime.getTime();
|
|
471
|
-
const threeHours = 3 * 60 * 60 * 1000;
|
|
472
|
-
|
|
473
|
-
if (elapsed > threeHours) {
|
|
474
|
-
await Jobs.deactivate(job.jobId);
|
|
475
|
-
return { action: 'stopped', reason: 'timeout' };
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Continue monitoring
|
|
479
|
-
return { action: 'continue' };
|
|
480
|
-
}
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
---
|
|
484
|
-
|
|
485
|
-
## ๐ Job Lifecycle
|
|
486
|
-
|
|
487
|
-
```
|
|
488
|
-
t=0: Tool creates job
|
|
489
|
-
|
|
|
490
|
-
| Schedule: { type: 'interval', seconds: 60 }
|
|
491
|
-
| Start time: 7:00 PM
|
|
492
|
-
โ
|
|
493
|
-
t=7:00 PM: First execution
|
|
494
|
-
|
|
|
495
|
-
โ
|
|
496
|
-
t=7:01 PM: Second execution (60s later)
|
|
497
|
-
|
|
|
498
|
-
โ
|
|
499
|
-
t=7:02 PM: Third execution
|
|
500
|
-
|
|
|
501
|
-
| ... continues every 60 seconds ...
|
|
502
|
-
โ
|
|
503
|
-
t=9:30 PM: Game ends detected
|
|
504
|
-
|
|
|
505
|
-
| Calls: await Jobs.deactivate(job.jobId)
|
|
506
|
-
โ
|
|
507
|
-
t=9:30 PM: Job stops โน๏ธ
|
|
508
|
-
|
|
|
509
|
-
| No more executions scheduled
|
|
510
|
-
โ
|
|
511
|
-
Done!
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
---
|
|
515
|
-
|
|
516
|
-
## ๐ฏ Use Cases Beyond Games
|
|
517
|
-
|
|
518
|
-
### 1. Auction Monitoring
|
|
519
|
-
|
|
520
|
-
```typescript
|
|
521
|
-
await Jobs.create({
|
|
522
|
-
name: `monitor-auction-${auctionId}`,
|
|
523
|
-
schedule: {
|
|
524
|
-
type: 'interval',
|
|
525
|
-
seconds: 30
|
|
526
|
-
},
|
|
527
|
-
execute: async () => {
|
|
528
|
-
const auction = await Data.getEntry('auctions', auctionId);
|
|
529
|
-
|
|
530
|
-
// Check if auction closed
|
|
531
|
-
if (new Date(auction.data.endTime) < new Date()) {
|
|
532
|
-
console.log('Auction closed!');
|
|
533
|
-
await Jobs.deactivate(job.jobId);
|
|
534
|
-
return { action: 'stopped', winner: auction.data.highestBidder };
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Update current bid
|
|
538
|
-
const currentBid = await fetchLatestBid(auctionId);
|
|
539
|
-
await Data.update('auctions', auctionId, { currentBid });
|
|
540
|
-
|
|
541
|
-
return { action: 'updated', currentBid };
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
### 2. Server Health Monitoring (with time limit)
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
await Jobs.create({
|
|
550
|
-
name: `health-check-deployment-${deploymentId}`,
|
|
551
|
-
schedule: {
|
|
552
|
-
type: 'interval',
|
|
553
|
-
seconds: 10 // Check every 10 seconds
|
|
554
|
-
},
|
|
555
|
-
execute: async () => {
|
|
556
|
-
const deployment = await Data.getEntry('deployments', deploymentId);
|
|
557
|
-
|
|
558
|
-
// Stop if deployment succeeded
|
|
559
|
-
if (deployment.data.status === 'success') {
|
|
560
|
-
await Jobs.deactivate(job.jobId);
|
|
561
|
-
return { action: 'stopped', reason: 'deployment-successful' };
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Stop if timeout (10 minutes)
|
|
565
|
-
const elapsed = Date.now() - new Date(deployment.data.startTime).getTime();
|
|
566
|
-
if (elapsed > 10 * 60 * 1000) {
|
|
567
|
-
await Jobs.deactivate(job.jobId);
|
|
568
|
-
return { action: 'stopped', reason: 'timeout' };
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Check health
|
|
572
|
-
const health = await checkServerHealth();
|
|
573
|
-
await Data.update('deployments', deploymentId, { health });
|
|
574
|
-
|
|
575
|
-
return { action: 'monitoring', health };
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
### 3. Limited-Time Promotion
|
|
581
|
-
|
|
582
|
-
```typescript
|
|
583
|
-
const promotionEnd = new Date('2025-12-25T23:59:59Z');
|
|
584
|
-
|
|
585
|
-
await Jobs.create({
|
|
586
|
-
name: `check-promotion-${promoId}`,
|
|
587
|
-
schedule: {
|
|
588
|
-
type: 'interval',
|
|
589
|
-
seconds: 3600 // Check every hour
|
|
590
|
-
},
|
|
591
|
-
execute: async () => {
|
|
592
|
-
// Check if promotion ended
|
|
593
|
-
if (new Date() > promotionEnd) {
|
|
594
|
-
console.log('Promotion ended');
|
|
595
|
-
|
|
596
|
-
// Deactivate promotion
|
|
597
|
-
await Data.update('promotions', promoId, { active: false });
|
|
598
|
-
|
|
599
|
-
// Stop this job
|
|
600
|
-
await Jobs.deactivate(job.jobId);
|
|
601
|
-
|
|
602
|
-
return { action: 'stopped', reason: 'promotion-ended' };
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Log promotion stats
|
|
606
|
-
const stats = await getPromotionStats(promoId);
|
|
607
|
-
await Data.create('promotion-stats', {
|
|
608
|
-
promoId,
|
|
609
|
-
stats,
|
|
610
|
-
timestamp: new Date().toISOString()
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
return { action: 'logged', stats };
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
---
|
|
619
|
-
|
|
620
|
-
## ๐ก Advanced Techniques
|
|
621
|
-
|
|
622
|
-
### Conditional Stop Logic
|
|
623
|
-
|
|
624
|
-
```typescript
|
|
625
|
-
execute: async () => {
|
|
626
|
-
const data = await Data.getEntry('collection', recordId);
|
|
627
|
-
|
|
628
|
-
// Multiple stop conditions
|
|
629
|
-
const stopReasons = [];
|
|
630
|
-
|
|
631
|
-
if (data.data.completed) stopReasons.push('completed');
|
|
632
|
-
if (data.data.cancelled) stopReasons.push('cancelled');
|
|
633
|
-
if (data.data.timeout) stopReasons.push('timeout');
|
|
634
|
-
|
|
635
|
-
if (stopReasons.length > 0) {
|
|
636
|
-
await Jobs.deactivate(job.jobId);
|
|
637
|
-
return {
|
|
638
|
-
action: 'stopped',
|
|
639
|
-
reasons: stopReasons
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Continue
|
|
644
|
-
return { action: 'continue' };
|
|
645
|
-
}
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
### Gradual Slowdown
|
|
649
|
-
|
|
650
|
-
```typescript
|
|
651
|
-
let executionCount = 0;
|
|
652
|
-
|
|
653
|
-
execute: async () => {
|
|
654
|
-
executionCount++;
|
|
655
|
-
|
|
656
|
-
// After 100 executions, slow down the interval
|
|
657
|
-
if (executionCount >= 100) {
|
|
658
|
-
// Create a new slower job
|
|
659
|
-
await Jobs.create({
|
|
660
|
-
name: `${originalName}-slow`,
|
|
661
|
-
schedule: { type: 'interval', seconds: 300 } // 5 min instead of 1 min
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
// Stop fast job
|
|
665
|
-
await Jobs.deactivate(job.jobId);
|
|
666
|
-
|
|
667
|
-
return { action: 'switched-to-slow-mode' };
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return { action: 'continue', count: executionCount };
|
|
671
|
-
}
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
### Pause and Resume Based on Time
|
|
675
|
-
|
|
676
|
-
```typescript
|
|
677
|
-
execute: async () => {
|
|
678
|
-
const hour = new Date().getHours();
|
|
679
|
-
|
|
680
|
-
// Only run during business hours (9 AM - 5 PM)
|
|
681
|
-
if (hour < 9 || hour >= 17) {
|
|
682
|
-
console.log('Outside business hours, skipping');
|
|
683
|
-
return { action: 'skipped', hour };
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Do work
|
|
687
|
-
const result = await doWork();
|
|
688
|
-
|
|
689
|
-
return { action: 'processed', result };
|
|
690
|
-
}
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
---
|
|
694
|
-
|
|
695
|
-
## ๐งช Testing Scenarios
|
|
696
|
-
|
|
697
|
-
### Test 1: Game In Progress
|
|
698
|
-
|
|
699
|
-
```bash
|
|
700
|
-
lua test skill
|
|
701
|
-
# Select: track_game_scores
|
|
702
|
-
# Enter:
|
|
703
|
-
# gameId: TEST-001
|
|
704
|
-
# gameName: Test Game
|
|
705
|
-
# startTime: 2025-10-12T20:00:00Z
|
|
706
|
-
# updateIntervalSeconds: 60
|
|
707
|
-
|
|
708
|
-
# Result: Job created and will run every 60 seconds
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
### Test 2: Get Scores
|
|
712
|
-
|
|
713
|
-
```bash
|
|
714
|
-
lua test skill
|
|
715
|
-
# Select: get_game_scores
|
|
716
|
-
# Enter: gameId: TEST-001
|
|
717
|
-
|
|
718
|
-
# Result: Current score and game status
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
### Test 3: Manual Stop
|
|
722
|
-
|
|
723
|
-
```bash
|
|
724
|
-
lua test skill
|
|
725
|
-
# Select: stop_game_tracking
|
|
726
|
-
# Enter: gameId: TEST-001
|
|
727
|
-
|
|
728
|
-
# Result: Job deactivated, game marked as stopped
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
### Test 4: Trigger Job Manually
|
|
732
|
-
|
|
733
|
-
```bash
|
|
734
|
-
lua jobs production
|
|
735
|
-
# Select: Trigger job now
|
|
736
|
-
# Select: track-game-TEST-001
|
|
737
|
-
|
|
738
|
-
# Job executes immediately and logs result
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
---
|
|
742
|
-
|
|
743
|
-
## ๐ Checklist for Your Own Implementation
|
|
744
|
-
|
|
745
|
-
When creating similar self-terminating interval jobs:
|
|
746
|
-
|
|
747
|
-
- [ ] Capture all needed variables before job creation
|
|
748
|
-
- [ ] Use interval schedule with appropriate frequency
|
|
749
|
-
- [ ] Include stop condition check at start of execute
|
|
750
|
-
- [ ] Call `Jobs.deactivate(job.jobId)` when stopping
|
|
751
|
-
- [ ] Log why the job stopped
|
|
752
|
-
- [ ] Store final results before stopping
|
|
753
|
-
- [ ] Handle API errors gracefully (don't stop on errors)
|
|
754
|
-
- [ ] Set appropriate timeout
|
|
755
|
-
- [ ] Configure retry for transient errors
|
|
756
|
-
- [ ] Test manual triggering
|
|
757
|
-
|
|
758
|
-
---
|
|
759
|
-
|
|
760
|
-
## ๐ Learn More
|
|
761
|
-
|
|
762
|
-
### Similar Examples
|
|
763
|
-
- **Stock Price Tracker** - Monitor stocks during market hours, stop at close
|
|
764
|
-
- **Build Monitor** - Check CI/CD status every 10s, stop when complete
|
|
765
|
-
- **Trial Countdown** - Check daily, stop when trial converts/expires
|
|
766
|
-
- **Flash Sale** - Update inventory every 5 seconds, stop when sold out
|
|
767
|
-
|
|
768
|
-
### Documentation
|
|
769
|
-
- **DYNAMIC_JOB_CREATION_API.md** - Jobs.create() reference
|
|
770
|
-
- **WEBHOOK_JOB_EXAMPLES.md** - More examples
|
|
771
|
-
- **JOB_API_SPEC.md** - Complete API reference
|
|
772
|
-
|
|
773
|
-
---
|
|
774
|
-
|
|
775
|
-
## โ
Summary
|
|
776
|
-
|
|
777
|
-
**This example demonstrates:**
|
|
778
|
-
- โ
Creating interval jobs dynamically
|
|
779
|
-
- โ
Jobs that stop themselves
|
|
780
|
-
- โ
Real-time monitoring patterns
|
|
781
|
-
- โ
External API integration
|
|
782
|
-
- โ
Error handling in jobs
|
|
783
|
-
- โ
Closure capture
|
|
784
|
-
- โ
Normal TypeScript code (not strings!)
|
|
785
|
-
|
|
786
|
-
**Perfect for:**
|
|
787
|
-
- Live data monitoring
|
|
788
|
-
- Time-bounded tracking
|
|
789
|
-
- Event-driven scheduling
|
|
790
|
-
- Self-managing background tasks
|
|
791
|
-
|
|
792
|
-
---
|
|
793
|
-
|
|
794
|
-
**Now you can build sophisticated real-time monitoring systems with clean, self-managing code!** ๐ฎโจ
|
|
795
|
-
|