@objectstack/service-job 4.0.3 → 4.0.4
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +8 -0
- package/README.md +371 -0
- package/package.json +3 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/service-job@4.0.
|
|
2
|
+
> @objectstack/service-job@4.0.4 build /home/runner/work/framework/framework/packages/services/service-job
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
13
|
[32mESM[39m [1mdist/index.js [22m[32m3.99 KB[39m
|
|
14
14
|
[32mESM[39m [1mdist/index.js.map [22m[32m10.88 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 82ms
|
|
16
16
|
[32mCJS[39m [1mdist/index.cjs [22m[32m5.09 KB[39m
|
|
17
17
|
[32mCJS[39m [1mdist/index.cjs.map [22m[32m11.42 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 84ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 15151ms
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.60 KB[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.cts [22m[32m3.60 KB[39m
|
package/CHANGELOG.md
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# @objectstack/service-job
|
|
2
|
+
|
|
3
|
+
Job Service for ObjectStack — implements `IJobService` with setInterval and cron scheduling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Cron Scheduling**: Schedule jobs with cron expressions
|
|
8
|
+
- **Interval Scheduling**: Run jobs at fixed intervals
|
|
9
|
+
- **Job Queue**: Manage job execution queue
|
|
10
|
+
- **Retry Logic**: Automatic retry on failure with exponential backoff
|
|
11
|
+
- **Job History**: Track execution history and status
|
|
12
|
+
- **Concurrency Control**: Limit concurrent job execution
|
|
13
|
+
- **Timezone Support**: Schedule jobs in specific timezones
|
|
14
|
+
- **Type-Safe**: Full TypeScript support
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @objectstack/service-job
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Basic Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { defineStack } from '@objectstack/spec';
|
|
26
|
+
import { ServiceJob } from '@objectstack/service-job';
|
|
27
|
+
|
|
28
|
+
const stack = defineStack({
|
|
29
|
+
services: [
|
|
30
|
+
ServiceJob.configure({
|
|
31
|
+
timezone: 'America/New_York',
|
|
32
|
+
maxConcurrent: 5,
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
interface JobServiceConfig {
|
|
42
|
+
/** Default timezone for cron jobs (default: 'UTC') */
|
|
43
|
+
timezone?: string;
|
|
44
|
+
|
|
45
|
+
/** Maximum concurrent job executions (default: 10) */
|
|
46
|
+
maxConcurrent?: number;
|
|
47
|
+
|
|
48
|
+
/** Enable job history tracking (default: true) */
|
|
49
|
+
enableHistory?: boolean;
|
|
50
|
+
|
|
51
|
+
/** Maximum history entries per job (default: 100) */
|
|
52
|
+
maxHistorySize?: number;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Service API
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Get job service
|
|
60
|
+
const jobs = kernel.getService<IJobService>('job');
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Cron Jobs
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Schedule a job with cron expression
|
|
67
|
+
const job = await jobs.schedule({
|
|
68
|
+
name: 'daily_report',
|
|
69
|
+
schedule: '0 9 * * *', // Every day at 9 AM
|
|
70
|
+
handler: async (context) => {
|
|
71
|
+
console.log('Generating daily report...');
|
|
72
|
+
// Your job logic here
|
|
73
|
+
},
|
|
74
|
+
timezone: 'America/New_York',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Common cron patterns:
|
|
78
|
+
// '*/5 * * * *' - Every 5 minutes
|
|
79
|
+
// '0 */2 * * *' - Every 2 hours
|
|
80
|
+
// '0 9 * * 1-5' - Weekdays at 9 AM
|
|
81
|
+
// '0 0 1 * *' - First day of every month at midnight
|
|
82
|
+
// '0 0 * * 0' - Every Sunday at midnight
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Interval Jobs
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Run every 30 seconds
|
|
89
|
+
const job = await jobs.scheduleInterval({
|
|
90
|
+
name: 'health_check',
|
|
91
|
+
interval: 30000, // milliseconds
|
|
92
|
+
handler: async (context) => {
|
|
93
|
+
console.log('Running health check...');
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Run every 5 minutes
|
|
98
|
+
const job = await jobs.scheduleInterval({
|
|
99
|
+
name: 'sync_data',
|
|
100
|
+
interval: 5 * 60 * 1000, // 5 minutes
|
|
101
|
+
handler: async (context) => {
|
|
102
|
+
// Sync data
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### One-Time Jobs
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Schedule a one-time job
|
|
111
|
+
const job = await jobs.scheduleOnce({
|
|
112
|
+
name: 'send_reminder',
|
|
113
|
+
runAt: new Date('2024-12-25T09:00:00Z'),
|
|
114
|
+
handler: async (context) => {
|
|
115
|
+
console.log('Sending holiday reminder...');
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Schedule to run after a delay
|
|
120
|
+
const job = await jobs.scheduleOnce({
|
|
121
|
+
name: 'delayed_task',
|
|
122
|
+
delay: 3600000, // 1 hour from now
|
|
123
|
+
handler: async (context) => {
|
|
124
|
+
console.log('Executing delayed task...');
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Job Management
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// List all jobs
|
|
133
|
+
const allJobs = await jobs.listJobs();
|
|
134
|
+
|
|
135
|
+
// Get job details
|
|
136
|
+
const job = await jobs.getJob('daily_report');
|
|
137
|
+
|
|
138
|
+
// Stop a job
|
|
139
|
+
await jobs.stopJob('daily_report');
|
|
140
|
+
|
|
141
|
+
// Resume a stopped job
|
|
142
|
+
await jobs.resumeJob('daily_report');
|
|
143
|
+
|
|
144
|
+
// Delete a job
|
|
145
|
+
await jobs.deleteJob('daily_report');
|
|
146
|
+
|
|
147
|
+
// Run a job immediately (ignoring schedule)
|
|
148
|
+
await jobs.runNow('daily_report');
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Advanced Features
|
|
152
|
+
|
|
153
|
+
### Job Context
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const job = await jobs.schedule({
|
|
157
|
+
name: 'process_orders',
|
|
158
|
+
schedule: '*/10 * * * *',
|
|
159
|
+
handler: async (context) => {
|
|
160
|
+
console.log('Job name:', context.jobName);
|
|
161
|
+
console.log('Execution ID:', context.executionId);
|
|
162
|
+
console.log('Scheduled time:', context.scheduledTime);
|
|
163
|
+
console.log('Execution count:', context.executionCount);
|
|
164
|
+
|
|
165
|
+
// Access services
|
|
166
|
+
const db = context.kernel.getService('database');
|
|
167
|
+
const orders = await db.find({ object: 'order', status: 'pending' });
|
|
168
|
+
|
|
169
|
+
// Process orders...
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Retry Configuration
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const job = await jobs.schedule({
|
|
178
|
+
name: 'api_sync',
|
|
179
|
+
schedule: '0 * * * *', // Every hour
|
|
180
|
+
retry: {
|
|
181
|
+
maxAttempts: 3,
|
|
182
|
+
backoff: 'exponential', // 'linear' or 'exponential'
|
|
183
|
+
initialDelay: 1000, // 1 second
|
|
184
|
+
maxDelay: 60000, // 1 minute
|
|
185
|
+
},
|
|
186
|
+
handler: async (context) => {
|
|
187
|
+
// May fail and retry
|
|
188
|
+
await syncWithExternalAPI();
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Concurrency Control
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const job = await jobs.schedule({
|
|
197
|
+
name: 'heavy_processing',
|
|
198
|
+
schedule: '*/5 * * * *',
|
|
199
|
+
concurrency: 1, // Only one instance can run at a time
|
|
200
|
+
handler: async (context) => {
|
|
201
|
+
// Long-running process
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Job History
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Get execution history for a job
|
|
210
|
+
const history = await jobs.getJobHistory('daily_report', {
|
|
211
|
+
limit: 50,
|
|
212
|
+
status: 'success', // 'success', 'failed', 'running'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Example history entry:
|
|
216
|
+
// {
|
|
217
|
+
// executionId: 'exec:abc123',
|
|
218
|
+
// jobName: 'daily_report',
|
|
219
|
+
// status: 'success',
|
|
220
|
+
// startedAt: '2024-01-15T09:00:00Z',
|
|
221
|
+
// completedAt: '2024-01-15T09:05:23Z',
|
|
222
|
+
// duration: 323000, // milliseconds
|
|
223
|
+
// error: null,
|
|
224
|
+
// result: { records: 1250 }
|
|
225
|
+
// }
|
|
226
|
+
|
|
227
|
+
// Clear history for a job
|
|
228
|
+
await jobs.clearHistory('daily_report');
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Job Data & Results
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const job = await jobs.schedule({
|
|
235
|
+
name: 'data_export',
|
|
236
|
+
schedule: '0 0 * * *',
|
|
237
|
+
handler: async (context) => {
|
|
238
|
+
const records = await exportData();
|
|
239
|
+
|
|
240
|
+
// Return result data
|
|
241
|
+
return {
|
|
242
|
+
recordCount: records.length,
|
|
243
|
+
fileSize: calculateSize(records),
|
|
244
|
+
exportedAt: new Date(),
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Get last execution result
|
|
250
|
+
const lastRun = await jobs.getLastExecution('data_export');
|
|
251
|
+
console.log('Last export:', lastRun.result);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Common Patterns
|
|
255
|
+
|
|
256
|
+
### Database Cleanup Job
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
jobs.schedule({
|
|
260
|
+
name: 'cleanup_old_records',
|
|
261
|
+
schedule: '0 2 * * *', // 2 AM daily
|
|
262
|
+
handler: async (context) => {
|
|
263
|
+
const db = context.kernel.getService('database');
|
|
264
|
+
|
|
265
|
+
// Delete records older than 90 days
|
|
266
|
+
const cutoff = new Date();
|
|
267
|
+
cutoff.setDate(cutoff.getDate() - 90);
|
|
268
|
+
|
|
269
|
+
await db.delete({
|
|
270
|
+
object: 'audit_log',
|
|
271
|
+
filters: [{ field: 'created_at', operator: 'lt', value: cutoff }],
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Report Generation Job
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
jobs.schedule({
|
|
281
|
+
name: 'weekly_sales_report',
|
|
282
|
+
schedule: '0 8 * * 1', // Mondays at 8 AM
|
|
283
|
+
handler: async (context) => {
|
|
284
|
+
const analytics = context.kernel.getService('analytics');
|
|
285
|
+
|
|
286
|
+
const data = await analytics.query({
|
|
287
|
+
object: 'order',
|
|
288
|
+
aggregations: [{ function: 'sum', field: 'amount' }],
|
|
289
|
+
groupBy: ['sales_rep'],
|
|
290
|
+
filters: [{ field: 'created_at', operator: 'last_week' }],
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Generate and email report
|
|
294
|
+
await sendReport(data);
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Cache Warming Job
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
jobs.scheduleInterval({
|
|
303
|
+
name: 'warm_cache',
|
|
304
|
+
interval: 15 * 60 * 1000, // Every 15 minutes
|
|
305
|
+
handler: async (context) => {
|
|
306
|
+
const cache = context.kernel.getService('cache');
|
|
307
|
+
|
|
308
|
+
// Pre-load frequently accessed data
|
|
309
|
+
const popularProducts = await getPopularProducts();
|
|
310
|
+
await cache.set('popular_products', popularProducts, { ttl: 900 });
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## REST API Endpoints
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
GET /api/v1/jobs # List all jobs
|
|
319
|
+
GET /api/v1/jobs/:name # Get job details
|
|
320
|
+
POST /api/v1/jobs/:name/run # Run job immediately
|
|
321
|
+
POST /api/v1/jobs/:name/stop # Stop job
|
|
322
|
+
POST /api/v1/jobs/:name/resume # Resume job
|
|
323
|
+
DELETE /api/v1/jobs/:name # Delete job
|
|
324
|
+
GET /api/v1/jobs/:name/history # Get execution history
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Best Practices
|
|
328
|
+
|
|
329
|
+
1. **Idempotent Handlers**: Job handlers should be idempotent (safe to run multiple times)
|
|
330
|
+
2. **Error Handling**: Always handle errors gracefully and log failures
|
|
331
|
+
3. **Timeout Limits**: Set reasonable timeout limits for long-running jobs
|
|
332
|
+
4. **Resource Limits**: Limit concurrent executions to avoid overloading the system
|
|
333
|
+
5. **Monitoring**: Monitor job execution times and failure rates
|
|
334
|
+
6. **Timezone Awareness**: Always specify timezone for cron jobs to avoid ambiguity
|
|
335
|
+
7. **Cleanup**: Periodically delete old job history to save storage
|
|
336
|
+
|
|
337
|
+
## Performance Considerations
|
|
338
|
+
|
|
339
|
+
- **Concurrency**: Limit concurrent jobs based on system resources
|
|
340
|
+
- **Job Duration**: Keep job execution time reasonable (< 5 minutes ideal)
|
|
341
|
+
- **History Size**: Limit history entries to prevent memory bloat
|
|
342
|
+
- **Batch Processing**: Process records in batches for large datasets
|
|
343
|
+
|
|
344
|
+
## Contract Implementation
|
|
345
|
+
|
|
346
|
+
Implements `IJobService` from `@objectstack/spec/contracts`:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
interface IJobService {
|
|
350
|
+
schedule(options: ScheduleOptions): Promise<Job>;
|
|
351
|
+
scheduleInterval(options: IntervalOptions): Promise<Job>;
|
|
352
|
+
scheduleOnce(options: OnceOptions): Promise<Job>;
|
|
353
|
+
getJob(name: string): Promise<Job>;
|
|
354
|
+
listJobs(filter?: JobFilter): Promise<Job[]>;
|
|
355
|
+
stopJob(name: string): Promise<void>;
|
|
356
|
+
resumeJob(name: string): Promise<void>;
|
|
357
|
+
deleteJob(name: string): Promise<void>;
|
|
358
|
+
runNow(name: string): Promise<JobExecution>;
|
|
359
|
+
getJobHistory(name: string, options?: HistoryOptions): Promise<JobExecution[]>;
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## License
|
|
364
|
+
|
|
365
|
+
Apache-2.0
|
|
366
|
+
|
|
367
|
+
## See Also
|
|
368
|
+
|
|
369
|
+
- [Cron Expression Generator](https://crontab.guru/)
|
|
370
|
+
- [@objectstack/spec/contracts](../../spec/src/contracts/)
|
|
371
|
+
- [Job Scheduling Guide](/content/docs/guides/jobs/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-job",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Job Service for ObjectStack — implements IJobService with setInterval and cron scheduling",
|
|
6
6
|
"type": "module",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "4.0.
|
|
18
|
-
"@objectstack/spec": "4.0.
|
|
17
|
+
"@objectstack/core": "4.0.4",
|
|
18
|
+
"@objectstack/spec": "4.0.4"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^25.6.0",
|