@objectstack/service-job 4.0.3 → 4.0.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/README.md +371 -0
- package/package.json +31 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -169
- package/src/cron-job-adapter.ts +0 -51
- package/src/index.ts +0 -8
- package/src/interval-job-adapter.test.ts +0 -120
- package/src/interval-job-adapter.ts +0 -130
- package/src/job-service-plugin.ts +0 -65
- package/tsconfig.json +0 -10
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.5",
|
|
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,13 +14,38 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "4.0.
|
|
18
|
-
"@objectstack/spec": "4.0.
|
|
17
|
+
"@objectstack/core": "4.0.5",
|
|
18
|
+
"@objectstack/spec": "4.0.5"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^25.6.
|
|
22
|
-
"typescript": "^6.0.
|
|
23
|
-
"vitest": "^4.1.
|
|
21
|
+
"@types/node": "^25.6.2",
|
|
22
|
+
"typescript": "^6.0.3",
|
|
23
|
+
"vitest": "^4.1.5"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"objectstack",
|
|
27
|
+
"service",
|
|
28
|
+
"job",
|
|
29
|
+
"cron",
|
|
30
|
+
"scheduler"
|
|
31
|
+
],
|
|
32
|
+
"author": "ObjectStack",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/objectstack-ai/framework.git",
|
|
36
|
+
"directory": "packages/services/service-job"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://objectstack.ai/docs",
|
|
39
|
+
"bugs": "https://github.com/objectstack-ai/framework/issues",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
24
49
|
},
|
|
25
50
|
"scripts": {
|
|
26
51
|
"build": "tsup --config ../../../tsup.config.ts",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @objectstack/service-job@4.0.3 build /home/runner/work/framework/framework/packages/services/service-job
|
|
3
|
-
> tsup --config ../../../tsup.config.ts
|
|
4
|
-
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
|
-
[34mCLI[39m Target: es2020
|
|
10
|
-
[34mCLI[39m Cleaning output folder
|
|
11
|
-
[34mESM[39m Build start
|
|
12
|
-
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[32m3.99 KB[39m
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[32m10.88 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in 69ms
|
|
16
|
-
[32mCJS[39m [1mdist/index.cjs [22m[32m5.09 KB[39m
|
|
17
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[32m11.42 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 71ms
|
|
19
|
-
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in 10242ms
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.60 KB[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[32m3.60 KB[39m
|
package/CHANGELOG.md
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# @objectstack/service-job
|
|
2
|
-
|
|
3
|
-
## 4.0.3
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- @objectstack/spec@4.0.3
|
|
8
|
-
- @objectstack/core@4.0.3
|
|
9
|
-
|
|
10
|
-
## 4.0.2
|
|
11
|
-
|
|
12
|
-
### Patch Changes
|
|
13
|
-
|
|
14
|
-
- Updated dependencies [5f659e9]
|
|
15
|
-
- @objectstack/spec@4.0.2
|
|
16
|
-
- @objectstack/core@4.0.2
|
|
17
|
-
|
|
18
|
-
## 4.0.0
|
|
19
|
-
|
|
20
|
-
### Patch Changes
|
|
21
|
-
|
|
22
|
-
- Updated dependencies [f08ffc3]
|
|
23
|
-
- Updated dependencies [e0b0a78]
|
|
24
|
-
- @objectstack/spec@4.0.0
|
|
25
|
-
- @objectstack/core@4.0.0
|
|
26
|
-
|
|
27
|
-
## 3.3.1
|
|
28
|
-
|
|
29
|
-
### Patch Changes
|
|
30
|
-
|
|
31
|
-
- @objectstack/spec@3.3.1
|
|
32
|
-
- @objectstack/core@3.3.1
|
|
33
|
-
|
|
34
|
-
## 3.3.0
|
|
35
|
-
|
|
36
|
-
### Patch Changes
|
|
37
|
-
|
|
38
|
-
- @objectstack/spec@3.3.0
|
|
39
|
-
- @objectstack/core@3.3.0
|
|
40
|
-
|
|
41
|
-
## 3.2.9
|
|
42
|
-
|
|
43
|
-
### Patch Changes
|
|
44
|
-
|
|
45
|
-
- @objectstack/spec@3.2.9
|
|
46
|
-
- @objectstack/core@3.2.9
|
|
47
|
-
|
|
48
|
-
## 3.2.8
|
|
49
|
-
|
|
50
|
-
### Patch Changes
|
|
51
|
-
|
|
52
|
-
- @objectstack/spec@3.2.8
|
|
53
|
-
- @objectstack/core@3.2.8
|
|
54
|
-
|
|
55
|
-
## 3.2.7
|
|
56
|
-
|
|
57
|
-
### Patch Changes
|
|
58
|
-
|
|
59
|
-
- @objectstack/spec@3.2.7
|
|
60
|
-
- @objectstack/core@3.2.7
|
|
61
|
-
|
|
62
|
-
## 3.2.6
|
|
63
|
-
|
|
64
|
-
### Patch Changes
|
|
65
|
-
|
|
66
|
-
- @objectstack/spec@3.2.6
|
|
67
|
-
- @objectstack/core@3.2.6
|
|
68
|
-
|
|
69
|
-
## 3.2.5
|
|
70
|
-
|
|
71
|
-
### Patch Changes
|
|
72
|
-
|
|
73
|
-
- @objectstack/spec@3.2.5
|
|
74
|
-
- @objectstack/core@3.2.5
|
|
75
|
-
|
|
76
|
-
## 3.2.4
|
|
77
|
-
|
|
78
|
-
### Patch Changes
|
|
79
|
-
|
|
80
|
-
- @objectstack/spec@3.2.4
|
|
81
|
-
- @objectstack/core@3.2.4
|
|
82
|
-
|
|
83
|
-
## 3.2.3
|
|
84
|
-
|
|
85
|
-
### Patch Changes
|
|
86
|
-
|
|
87
|
-
- @objectstack/spec@3.2.3
|
|
88
|
-
- @objectstack/core@3.2.3
|
|
89
|
-
|
|
90
|
-
## 3.2.2
|
|
91
|
-
|
|
92
|
-
### Patch Changes
|
|
93
|
-
|
|
94
|
-
- Updated dependencies [46defbb]
|
|
95
|
-
- @objectstack/spec@3.2.2
|
|
96
|
-
- @objectstack/core@3.2.2
|
|
97
|
-
|
|
98
|
-
## 3.2.1
|
|
99
|
-
|
|
100
|
-
### Patch Changes
|
|
101
|
-
|
|
102
|
-
- Updated dependencies [850b546]
|
|
103
|
-
- @objectstack/spec@3.2.1
|
|
104
|
-
- @objectstack/core@3.2.1
|
|
105
|
-
|
|
106
|
-
## 3.2.0
|
|
107
|
-
|
|
108
|
-
### Patch Changes
|
|
109
|
-
|
|
110
|
-
- Updated dependencies [5901c29]
|
|
111
|
-
- @objectstack/spec@3.2.0
|
|
112
|
-
- @objectstack/core@3.2.0
|
|
113
|
-
|
|
114
|
-
## 3.1.1
|
|
115
|
-
|
|
116
|
-
### Patch Changes
|
|
117
|
-
|
|
118
|
-
- Updated dependencies [953d667]
|
|
119
|
-
- @objectstack/spec@3.1.1
|
|
120
|
-
- @objectstack/core@3.1.1
|
|
121
|
-
|
|
122
|
-
## 3.1.0
|
|
123
|
-
|
|
124
|
-
### Patch Changes
|
|
125
|
-
|
|
126
|
-
- Updated dependencies [0088830]
|
|
127
|
-
- @objectstack/spec@3.1.0
|
|
128
|
-
- @objectstack/core@3.1.0
|
|
129
|
-
|
|
130
|
-
## 3.0.11
|
|
131
|
-
|
|
132
|
-
### Patch Changes
|
|
133
|
-
|
|
134
|
-
- Updated dependencies [92d9d99]
|
|
135
|
-
- @objectstack/spec@3.0.11
|
|
136
|
-
- @objectstack/core@3.0.11
|
|
137
|
-
|
|
138
|
-
## 3.0.10
|
|
139
|
-
|
|
140
|
-
### Patch Changes
|
|
141
|
-
|
|
142
|
-
- Updated dependencies [d1e5d31]
|
|
143
|
-
- @objectstack/spec@3.0.10
|
|
144
|
-
- @objectstack/core@3.0.10
|
|
145
|
-
|
|
146
|
-
## 3.0.9
|
|
147
|
-
|
|
148
|
-
### Patch Changes
|
|
149
|
-
|
|
150
|
-
- Updated dependencies [15e0df6]
|
|
151
|
-
- @objectstack/spec@3.0.9
|
|
152
|
-
- @objectstack/core@3.0.9
|
|
153
|
-
|
|
154
|
-
## 3.0.8
|
|
155
|
-
|
|
156
|
-
### Patch Changes
|
|
157
|
-
|
|
158
|
-
- Updated dependencies [5a968a2]
|
|
159
|
-
- @objectstack/spec@3.0.8
|
|
160
|
-
- @objectstack/core@3.0.8
|
|
161
|
-
|
|
162
|
-
## 3.0.7
|
|
163
|
-
|
|
164
|
-
### Patch Changes
|
|
165
|
-
|
|
166
|
-
- Updated dependencies [0119bd7]
|
|
167
|
-
- Updated dependencies [5426bdf]
|
|
168
|
-
- @objectstack/spec@3.0.7
|
|
169
|
-
- @objectstack/core@3.0.7
|
package/src/cron-job-adapter.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { IJobService, JobSchedule, JobHandler, JobExecution } from '@objectstack/spec/contracts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Configuration for the cron-based job adapter.
|
|
7
|
-
*/
|
|
8
|
-
export interface CronJobAdapterOptions {
|
|
9
|
-
/** Timezone for cron expressions (default: 'UTC') */
|
|
10
|
-
timezone?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Cron-based job adapter skeleton implementing IJobService.
|
|
15
|
-
*
|
|
16
|
-
* This is a placeholder for future cron integration (e.g., `node-cron` or `croner`).
|
|
17
|
-
* Concrete implementation will parse cron expressions and schedule jobs accordingly.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* const scheduler = new CronJobAdapter({ timezone: 'America/New_York' });
|
|
22
|
-
* await scheduler.schedule('nightly-cleanup', { type: 'cron', expression: '0 0 * * *' }, handler);
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export class CronJobAdapter implements IJobService {
|
|
26
|
-
private readonly timezone: string;
|
|
27
|
-
|
|
28
|
-
constructor(options: CronJobAdapterOptions = {}) {
|
|
29
|
-
this.timezone = options.timezone ?? 'UTC';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async schedule(_name: string, _schedule: JobSchedule, _handler: JobHandler): Promise<void> {
|
|
33
|
-
throw new Error(`CronJobAdapter not yet implemented (timezone: ${this.timezone})`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async cancel(_name: string): Promise<void> {
|
|
37
|
-
throw new Error('CronJobAdapter not yet implemented');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async trigger(_name: string, _data?: unknown): Promise<void> {
|
|
41
|
-
throw new Error('CronJobAdapter not yet implemented');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async getExecutions(_name: string, _limit?: number): Promise<JobExecution[]> {
|
|
45
|
-
throw new Error('CronJobAdapter not yet implemented');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async listJobs(): Promise<string[]> {
|
|
49
|
-
throw new Error('CronJobAdapter not yet implemented');
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
export { JobServicePlugin } from './job-service-plugin.js';
|
|
4
|
-
export type { JobServicePluginOptions } from './job-service-plugin.js';
|
|
5
|
-
export { IntervalJobAdapter } from './interval-job-adapter.js';
|
|
6
|
-
export type { IntervalJobAdapterOptions } from './interval-job-adapter.js';
|
|
7
|
-
export { CronJobAdapter } from './cron-job-adapter.js';
|
|
8
|
-
export type { CronJobAdapterOptions } from './cron-job-adapter.js';
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
4
|
-
import { IntervalJobAdapter } from './interval-job-adapter';
|
|
5
|
-
import type { IJobService } from '@objectstack/spec/contracts';
|
|
6
|
-
|
|
7
|
-
describe('IntervalJobAdapter', () => {
|
|
8
|
-
let adapter: IntervalJobAdapter;
|
|
9
|
-
|
|
10
|
-
afterEach(async () => {
|
|
11
|
-
await adapter?.destroy();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should implement IJobService contract', () => {
|
|
15
|
-
adapter = new IntervalJobAdapter();
|
|
16
|
-
const job: IJobService = adapter;
|
|
17
|
-
expect(typeof job.schedule).toBe('function');
|
|
18
|
-
expect(typeof job.cancel).toBe('function');
|
|
19
|
-
expect(typeof job.trigger).toBe('function');
|
|
20
|
-
expect(typeof job.getExecutions).toBe('function');
|
|
21
|
-
expect(typeof job.listJobs).toBe('function');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should schedule and list jobs', async () => {
|
|
25
|
-
adapter = new IntervalJobAdapter();
|
|
26
|
-
await adapter.schedule('daily-report', { type: 'cron', expression: '0 0 * * *' }, async () => {});
|
|
27
|
-
expect(await adapter.listJobs()).toEqual(['daily-report']);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should cancel a job', async () => {
|
|
31
|
-
adapter = new IntervalJobAdapter();
|
|
32
|
-
await adapter.schedule('temp-job', { type: 'cron', expression: '* * * * *' }, async () => {});
|
|
33
|
-
await adapter.cancel('temp-job');
|
|
34
|
-
expect(await adapter.listJobs()).toEqual([]);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should trigger a job handler with data', async () => {
|
|
38
|
-
adapter = new IntervalJobAdapter();
|
|
39
|
-
let triggered = false;
|
|
40
|
-
let receivedCtx: any;
|
|
41
|
-
|
|
42
|
-
await adapter.schedule('my-job', { type: 'cron', expression: '* * * * *' }, async (ctx) => {
|
|
43
|
-
triggered = true;
|
|
44
|
-
receivedCtx = ctx;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await adapter.trigger('my-job', { key: 'val' });
|
|
48
|
-
expect(triggered).toBe(true);
|
|
49
|
-
expect(receivedCtx.jobId).toBe('my-job');
|
|
50
|
-
expect(receivedCtx.data).toEqual({ key: 'val' });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should throw when triggering non-existent job', async () => {
|
|
54
|
-
adapter = new IntervalJobAdapter();
|
|
55
|
-
await expect(adapter.trigger('missing')).rejects.toThrow('Job "missing" not found');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should record execution history', async () => {
|
|
59
|
-
adapter = new IntervalJobAdapter();
|
|
60
|
-
await adapter.schedule('tracked-job', { type: 'cron', expression: '* * * * *' }, async () => {});
|
|
61
|
-
await adapter.trigger('tracked-job');
|
|
62
|
-
|
|
63
|
-
const execs = await adapter.getExecutions('tracked-job');
|
|
64
|
-
expect(execs).toHaveLength(1);
|
|
65
|
-
expect(execs[0].status).toBe('success');
|
|
66
|
-
expect(execs[0].jobId).toBe('tracked-job');
|
|
67
|
-
expect(execs[0].startedAt).toBeTruthy();
|
|
68
|
-
expect(execs[0].completedAt).toBeTruthy();
|
|
69
|
-
expect(typeof execs[0].durationMs).toBe('number');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should record failed executions', async () => {
|
|
73
|
-
adapter = new IntervalJobAdapter();
|
|
74
|
-
await adapter.schedule('fail-job', { type: 'cron', expression: '* * * * *' }, async () => {
|
|
75
|
-
throw new Error('Job failed');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
await adapter.trigger('fail-job');
|
|
79
|
-
|
|
80
|
-
const execs = await adapter.getExecutions('fail-job');
|
|
81
|
-
expect(execs).toHaveLength(1);
|
|
82
|
-
expect(execs[0].status).toBe('failed');
|
|
83
|
-
expect(execs[0].error).toBe('Job failed');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should return empty executions for non-existent job', async () => {
|
|
87
|
-
adapter = new IntervalJobAdapter();
|
|
88
|
-
expect(await adapter.getExecutions('missing')).toEqual([]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should limit execution history with limit param', async () => {
|
|
92
|
-
adapter = new IntervalJobAdapter();
|
|
93
|
-
await adapter.schedule('multi-job', { type: 'cron', expression: '* * * * *' }, async () => {});
|
|
94
|
-
await adapter.trigger('multi-job');
|
|
95
|
-
await adapter.trigger('multi-job');
|
|
96
|
-
await adapter.trigger('multi-job');
|
|
97
|
-
|
|
98
|
-
const execs = await adapter.getExecutions('multi-job', 2);
|
|
99
|
-
expect(execs).toHaveLength(2);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should replace existing job with same name', async () => {
|
|
103
|
-
adapter = new IntervalJobAdapter();
|
|
104
|
-
let count = 0;
|
|
105
|
-
await adapter.schedule('dup', { type: 'cron', expression: '* * * * *' }, async () => { count = 1; });
|
|
106
|
-
await adapter.schedule('dup', { type: 'cron', expression: '* * * * *' }, async () => { count = 2; });
|
|
107
|
-
|
|
108
|
-
await adapter.trigger('dup');
|
|
109
|
-
expect(count).toBe(2);
|
|
110
|
-
expect(await adapter.listJobs()).toEqual(['dup']);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should clean up all timers on destroy', async () => {
|
|
114
|
-
adapter = new IntervalJobAdapter();
|
|
115
|
-
await adapter.schedule('j1', { type: 'interval', intervalMs: 100000 }, async () => {});
|
|
116
|
-
await adapter.schedule('j2', { type: 'interval', intervalMs: 100000 }, async () => {});
|
|
117
|
-
await adapter.destroy();
|
|
118
|
-
expect(await adapter.listJobs()).toEqual([]);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { IJobService, JobSchedule, JobHandler, JobExecution } from '@objectstack/spec/contracts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Internal record for a scheduled job.
|
|
7
|
-
*/
|
|
8
|
-
interface JobRecord {
|
|
9
|
-
name: string;
|
|
10
|
-
schedule: JobSchedule;
|
|
11
|
-
handler: JobHandler;
|
|
12
|
-
timerId?: ReturnType<typeof setInterval> | ReturnType<typeof setTimeout>;
|
|
13
|
-
executions: JobExecution[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Configuration options for IntervalJobAdapter.
|
|
18
|
-
*/
|
|
19
|
-
export interface IntervalJobAdapterOptions {
|
|
20
|
-
/** Maximum number of execution records to retain per job (default: 100) */
|
|
21
|
-
maxExecutions?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* setInterval-based job adapter implementing IJobService.
|
|
26
|
-
*
|
|
27
|
-
* Supports `interval` and `once` schedule types using Node.js timers.
|
|
28
|
-
* `cron` schedules are stored but not actively executed (requires a cron
|
|
29
|
-
* library — see CronJobAdapter skeleton).
|
|
30
|
-
*
|
|
31
|
-
* Suitable for single-process environments, development, and testing.
|
|
32
|
-
*/
|
|
33
|
-
export class IntervalJobAdapter implements IJobService {
|
|
34
|
-
private readonly jobs = new Map<string, JobRecord>();
|
|
35
|
-
private readonly maxExecutions: number;
|
|
36
|
-
|
|
37
|
-
constructor(options: IntervalJobAdapterOptions = {}) {
|
|
38
|
-
this.maxExecutions = options.maxExecutions ?? 100;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async schedule(name: string, schedule: JobSchedule, handler: JobHandler): Promise<void> {
|
|
42
|
-
// Cancel any existing job with the same name
|
|
43
|
-
await this.cancel(name);
|
|
44
|
-
|
|
45
|
-
const record: JobRecord = { name, schedule, handler, executions: [] };
|
|
46
|
-
|
|
47
|
-
if (schedule.type === 'interval' && schedule.intervalMs) {
|
|
48
|
-
record.timerId = setInterval(async () => {
|
|
49
|
-
await this.executeJob(record);
|
|
50
|
-
}, schedule.intervalMs);
|
|
51
|
-
} else if (schedule.type === 'once' && schedule.at) {
|
|
52
|
-
const delay = new Date(schedule.at).getTime() - Date.now();
|
|
53
|
-
if (delay > 0) {
|
|
54
|
-
record.timerId = setTimeout(async () => {
|
|
55
|
-
await this.executeJob(record);
|
|
56
|
-
}, delay);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// 'cron' type: stored but not actively scheduled (needs cron library)
|
|
60
|
-
|
|
61
|
-
this.jobs.set(name, record);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async cancel(name: string): Promise<void> {
|
|
65
|
-
const record = this.jobs.get(name);
|
|
66
|
-
if (record?.timerId) {
|
|
67
|
-
clearInterval(record.timerId as ReturnType<typeof setInterval>);
|
|
68
|
-
clearTimeout(record.timerId as ReturnType<typeof setTimeout>);
|
|
69
|
-
}
|
|
70
|
-
this.jobs.delete(name);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async trigger(name: string, data?: unknown): Promise<void> {
|
|
74
|
-
const record = this.jobs.get(name);
|
|
75
|
-
if (!record) {
|
|
76
|
-
throw new Error(`Job "${name}" not found`);
|
|
77
|
-
}
|
|
78
|
-
await this.executeJob(record, data);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async getExecutions(name: string, limit?: number): Promise<JobExecution[]> {
|
|
82
|
-
const record = this.jobs.get(name);
|
|
83
|
-
if (!record) return [];
|
|
84
|
-
const execs = record.executions;
|
|
85
|
-
return limit ? execs.slice(-limit) : execs;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async listJobs(): Promise<string[]> {
|
|
89
|
-
return [...this.jobs.keys()];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Stop all active timers. Call during plugin destroy phase.
|
|
94
|
-
*/
|
|
95
|
-
async destroy(): Promise<void> {
|
|
96
|
-
for (const record of this.jobs.values()) {
|
|
97
|
-
if (record.timerId) {
|
|
98
|
-
clearInterval(record.timerId as ReturnType<typeof setInterval>);
|
|
99
|
-
clearTimeout(record.timerId as ReturnType<typeof setTimeout>);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
this.jobs.clear();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private async executeJob(record: JobRecord, data?: unknown): Promise<void> {
|
|
106
|
-
const execution: JobExecution = {
|
|
107
|
-
jobId: record.name,
|
|
108
|
-
status: 'running',
|
|
109
|
-
startedAt: new Date().toISOString(),
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const startMs = Date.now();
|
|
113
|
-
try {
|
|
114
|
-
await record.handler({ jobId: record.name, data });
|
|
115
|
-
execution.status = 'success';
|
|
116
|
-
} catch (err) {
|
|
117
|
-
execution.status = 'failed';
|
|
118
|
-
execution.error = err instanceof Error ? err.message : String(err);
|
|
119
|
-
} finally {
|
|
120
|
-
execution.completedAt = new Date().toISOString();
|
|
121
|
-
execution.durationMs = Date.now() - startMs;
|
|
122
|
-
|
|
123
|
-
record.executions.push(execution);
|
|
124
|
-
// Trim old executions
|
|
125
|
-
if (record.executions.length > this.maxExecutions) {
|
|
126
|
-
record.executions.splice(0, record.executions.length - this.maxExecutions);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { Plugin, PluginContext } from '@objectstack/core';
|
|
4
|
-
import { IntervalJobAdapter } from './interval-job-adapter.js';
|
|
5
|
-
import type { IntervalJobAdapterOptions } from './interval-job-adapter.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Configuration options for the JobServicePlugin.
|
|
9
|
-
*/
|
|
10
|
-
export interface JobServicePluginOptions {
|
|
11
|
-
/** Job adapter type (default: 'interval') */
|
|
12
|
-
adapter?: 'interval' | 'cron';
|
|
13
|
-
/** Options for the interval job adapter */
|
|
14
|
-
interval?: IntervalJobAdapterOptions;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* JobServicePlugin — Production IJobService implementation.
|
|
19
|
-
*
|
|
20
|
-
* Registers a job scheduler with the kernel during the init phase.
|
|
21
|
-
* Supports setInterval-based and cron-based adapters.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* import { ObjectKernel } from '@objectstack/core';
|
|
26
|
-
* import { JobServicePlugin } from '@objectstack/service-job';
|
|
27
|
-
*
|
|
28
|
-
* const kernel = new ObjectKernel();
|
|
29
|
-
* kernel.use(new JobServicePlugin({ adapter: 'interval' }));
|
|
30
|
-
* await kernel.bootstrap();
|
|
31
|
-
*
|
|
32
|
-
* const job = kernel.getService('job');
|
|
33
|
-
* await job.schedule('cleanup', { type: 'interval', intervalMs: 60000 }, handler);
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export class JobServicePlugin implements Plugin {
|
|
37
|
-
name = 'com.objectstack.service.job';
|
|
38
|
-
version = '1.0.0';
|
|
39
|
-
type = 'standard';
|
|
40
|
-
|
|
41
|
-
private readonly options: JobServicePluginOptions;
|
|
42
|
-
private adapter?: IntervalJobAdapter;
|
|
43
|
-
|
|
44
|
-
constructor(options: JobServicePluginOptions = {}) {
|
|
45
|
-
this.options = { adapter: 'interval', ...options };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async init(ctx: PluginContext): Promise<void> {
|
|
49
|
-
const adapterType = this.options.adapter;
|
|
50
|
-
if (adapterType === 'cron') {
|
|
51
|
-
throw new Error(
|
|
52
|
-
'Cron job adapter is not yet implemented. ' +
|
|
53
|
-
'Use adapter: "interval" or provide a custom IJobService via ctx.registerService("job", impl).'
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
this.adapter = new IntervalJobAdapter(this.options.interval);
|
|
58
|
-
ctx.registerService('job', this.adapter);
|
|
59
|
-
ctx.logger.info('JobServicePlugin: registered interval job adapter');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async destroy(): Promise<void> {
|
|
63
|
-
await this.adapter?.destroy();
|
|
64
|
-
}
|
|
65
|
-
}
|