@hile/schedule 2.0.0
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 +83 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/scheduler.d.ts +27 -0
- package/dist/scheduler.js +88 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.js +1 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @hile/schedule
|
|
2
|
+
|
|
3
|
+
Declarative job scheduler based on [node-schedule](https://github.com/node-schedule/node-schedule).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Code-defined jobs
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Scheduler, defineJob } from '@hile/schedule'
|
|
11
|
+
import { second } from '@hile/schedule'
|
|
12
|
+
|
|
13
|
+
const scheduler = new Scheduler()
|
|
14
|
+
|
|
15
|
+
// Cron expression
|
|
16
|
+
scheduler.add('daily-report', '0 8 * * *', () => {
|
|
17
|
+
console.log('generating daily report...')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Delay (毫秒)
|
|
21
|
+
scheduler.add('delayed-task', { delay: 5000 }, () => {
|
|
22
|
+
console.log('ran after 5 seconds')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
scheduler.stop() // cancel all jobs
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Auto-load from directory
|
|
29
|
+
|
|
30
|
+
Create files with `{name}.schedule.ts`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// tasks/daily-report.schedule.ts
|
|
34
|
+
import { defineJob } from '@hile/schedule'
|
|
35
|
+
|
|
36
|
+
export default defineJob('0 8 * * *', () => {
|
|
37
|
+
console.log('daily report generated')
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Load them:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const scheduler = new Scheduler()
|
|
45
|
+
const off = await scheduler.load(join(__dirname, 'tasks'))
|
|
46
|
+
// off() to unregister all loaded jobs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Custom suffix via `suffix` option:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
await scheduler.load('./jobs', { suffix: 'job' })
|
|
53
|
+
// loads *.job.ts, *.job.js, etc.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### API
|
|
57
|
+
|
|
58
|
+
#### `defineJob(expression, handler)`
|
|
59
|
+
|
|
60
|
+
- `expression: string | { delay: number }` — cron 表达式或延迟毫秒数
|
|
61
|
+
- Returns `{ id: number, type: 'job', expression, handler }`
|
|
62
|
+
|
|
63
|
+
#### `scheduler.add(id, expression | { delay }, handler)`
|
|
64
|
+
|
|
65
|
+
- `id: string | number` — 任务唯一标识,重复添加抛异常
|
|
66
|
+
|
|
67
|
+
#### `scheduler.remove(id)`
|
|
68
|
+
|
|
69
|
+
#### `scheduler.stop()`
|
|
70
|
+
|
|
71
|
+
取消所有任务
|
|
72
|
+
|
|
73
|
+
#### `scheduler.getJobs(): JobInfo[]`
|
|
74
|
+
|
|
75
|
+
返回已注册任务列表
|
|
76
|
+
|
|
77
|
+
#### `scheduler.load(directory, options?)`
|
|
78
|
+
|
|
79
|
+
自动发现并注册任务文件,返回注销函数
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Scheduler } from './scheduler.js';
|
|
2
|
+
import type { JobDefinition, JobHandler } from './types.js';
|
|
3
|
+
export { Scheduler };
|
|
4
|
+
export type { JobInfo } from './scheduler.js';
|
|
5
|
+
export type { JobDefinition, JobHandler };
|
|
6
|
+
export declare function defineJob(expression: string | {
|
|
7
|
+
delay: number;
|
|
8
|
+
}, handler: () => Promise<void> | void): JobDefinition;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { JobHandler } from './types.js';
|
|
2
|
+
export type JobInfo = {
|
|
3
|
+
id: string;
|
|
4
|
+
type: 'cron' | 'delay';
|
|
5
|
+
expression: string;
|
|
6
|
+
};
|
|
7
|
+
export declare class Scheduler {
|
|
8
|
+
private jobs;
|
|
9
|
+
private meta;
|
|
10
|
+
add(id: string | number, expression: string, handler: JobHandler): void;
|
|
11
|
+
add(id: string | number, options: {
|
|
12
|
+
delay: number;
|
|
13
|
+
}, handler: JobHandler): void;
|
|
14
|
+
remove(id: string | number): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
getJobs(): JobInfo[];
|
|
17
|
+
/**
|
|
18
|
+
* 从目录自动加载 schedule 任务文件
|
|
19
|
+
* 文件后缀: {suffix}.ts/.js,default export 须为 defineJob 返回值
|
|
20
|
+
* @param directory 目录路径
|
|
21
|
+
* @param options.suffix 文件后缀标记,默认 'schedule'
|
|
22
|
+
* @returns 注销函数
|
|
23
|
+
*/
|
|
24
|
+
load(directory: string, options?: {
|
|
25
|
+
suffix?: string;
|
|
26
|
+
}): Promise<() => void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { scheduleJob } from 'node-schedule';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
export class Scheduler {
|
|
6
|
+
jobs = new Map();
|
|
7
|
+
meta = new Map();
|
|
8
|
+
add(id, exprOrOpts, handler) {
|
|
9
|
+
const key = String(id);
|
|
10
|
+
if (this.jobs.has(key))
|
|
11
|
+
throw new Error(`Job "${key}" already exists`);
|
|
12
|
+
// 包装 handler,捕获异步错误防止 node-schedule 未捕获 rejection
|
|
13
|
+
const safeHandler = () => {
|
|
14
|
+
try {
|
|
15
|
+
const result = handler();
|
|
16
|
+
if (result && typeof result.catch === 'function') {
|
|
17
|
+
result.catch(() => { });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// handler 同步错误也不应影响调度器
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
if (typeof exprOrOpts === 'string') {
|
|
25
|
+
this.jobs.set(key, scheduleJob(exprOrOpts, safeHandler));
|
|
26
|
+
this.meta.set(key, { type: 'cron', expression: exprOrOpts });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const date = new Date(Date.now() + exprOrOpts.delay);
|
|
30
|
+
this.jobs.set(key, scheduleJob(date, safeHandler));
|
|
31
|
+
this.meta.set(key, { type: 'delay', expression: `delay:${exprOrOpts.delay}` });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
remove(id) {
|
|
35
|
+
const key = String(id);
|
|
36
|
+
const job = this.jobs.get(key);
|
|
37
|
+
if (job) {
|
|
38
|
+
job.cancel();
|
|
39
|
+
this.jobs.delete(key);
|
|
40
|
+
this.meta.delete(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
stop() {
|
|
44
|
+
for (const job of this.jobs.values()) {
|
|
45
|
+
job.cancel();
|
|
46
|
+
}
|
|
47
|
+
this.jobs.clear();
|
|
48
|
+
this.meta.clear();
|
|
49
|
+
}
|
|
50
|
+
getJobs() {
|
|
51
|
+
return Array.from(this.meta.entries()).map(([id, m]) => ({
|
|
52
|
+
id,
|
|
53
|
+
type: m.type,
|
|
54
|
+
expression: m.expression,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 从目录自动加载 schedule 任务文件
|
|
59
|
+
* 文件后缀: {suffix}.ts/.js,default export 须为 defineJob 返回值
|
|
60
|
+
* @param directory 目录路径
|
|
61
|
+
* @param options.suffix 文件后缀标记,默认 'schedule'
|
|
62
|
+
* @returns 注销函数
|
|
63
|
+
*/
|
|
64
|
+
async load(directory, options) {
|
|
65
|
+
const suffix = options?.suffix || 'schedule';
|
|
66
|
+
const files = await glob(`**/*.${suffix}.{ts,js,tsx,jsx,mjs}`, { cwd: directory });
|
|
67
|
+
const offFns = [];
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
const filePath = resolve(directory, file);
|
|
70
|
+
const mod = await import(pathToFileURL(filePath).href);
|
|
71
|
+
const jobDef = mod.default;
|
|
72
|
+
if (!jobDef || jobDef.type !== 'job')
|
|
73
|
+
continue;
|
|
74
|
+
const key = String(jobDef.id);
|
|
75
|
+
if (typeof jobDef.expression === 'string') {
|
|
76
|
+
this.add(key, jobDef.expression, jobDef.handler);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.add(key, jobDef.expression, jobDef.handler);
|
|
80
|
+
}
|
|
81
|
+
offFns.push(() => this.remove(key));
|
|
82
|
+
}
|
|
83
|
+
return () => {
|
|
84
|
+
for (const off of offFns)
|
|
85
|
+
off();
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hile/schedule",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Declarative job scheduler based on node-schedule",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -b && fix-esm-import-path --preserve-import-type ./dist",
|
|
9
|
+
"dev": "tsc -b --watch",
|
|
10
|
+
"test": "vitest run"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node-schedule": "^2.1.7",
|
|
22
|
+
"fix-esm-import-path": "^1.10.3",
|
|
23
|
+
"vitest": "^4.0.18"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"glob": "^13.0.6",
|
|
27
|
+
"node-schedule": "^2.1.1"
|
|
28
|
+
},
|
|
29
|
+
"gitHead": "8e0fd1f78b5a8abd21218d1f596ada2533a0c8e7"
|
|
30
|
+
}
|