@eggjs/schedule 5.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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/dist/commonjs/agent.d.ts +7 -0
  4. package/dist/commonjs/agent.js +32 -0
  5. package/dist/commonjs/app/extend/agent.d.ts +2 -0
  6. package/dist/commonjs/app/extend/agent.js +29 -0
  7. package/dist/commonjs/app/extend/application.d.ts +2 -0
  8. package/dist/commonjs/app/extend/application.js +16 -0
  9. package/dist/commonjs/app.d.ts +6 -0
  10. package/dist/commonjs/app.js +131 -0
  11. package/dist/commonjs/config/config.default.d.ts +2 -0
  12. package/dist/commonjs/config/config.default.js +17 -0
  13. package/dist/commonjs/index.d.ts +1 -0
  14. package/dist/commonjs/index.js +18 -0
  15. package/dist/commonjs/lib/load_schedule.d.ts +3 -0
  16. package/dist/commonjs/lib/load_schedule.js +71 -0
  17. package/dist/commonjs/lib/schedule.d.ts +31 -0
  18. package/dist/commonjs/lib/schedule.js +89 -0
  19. package/dist/commonjs/lib/schedule_worker.d.ts +10 -0
  20. package/dist/commonjs/lib/schedule_worker.js +22 -0
  21. package/dist/commonjs/lib/strategy/all.d.ts +4 -0
  22. package/dist/commonjs/lib/strategy/all.js +11 -0
  23. package/dist/commonjs/lib/strategy/base.d.ts +30 -0
  24. package/dist/commonjs/lib/strategy/base.js +79 -0
  25. package/dist/commonjs/lib/strategy/timer.d.ts +20 -0
  26. package/dist/commonjs/lib/strategy/timer.js +99 -0
  27. package/dist/commonjs/lib/strategy/worker.d.ts +4 -0
  28. package/dist/commonjs/lib/strategy/worker.js +11 -0
  29. package/dist/commonjs/lib/types.d.ts +53 -0
  30. package/dist/commonjs/lib/types.js +3 -0
  31. package/dist/commonjs/package.json +3 -0
  32. package/dist/esm/agent.d.ts +7 -0
  33. package/dist/esm/agent.js +29 -0
  34. package/dist/esm/app/extend/agent.d.ts +2 -0
  35. package/dist/esm/app/extend/agent.js +27 -0
  36. package/dist/esm/app/extend/application.d.ts +2 -0
  37. package/dist/esm/app/extend/application.js +14 -0
  38. package/dist/esm/app.d.ts +6 -0
  39. package/dist/esm/app.js +125 -0
  40. package/dist/esm/config/config.default.d.ts +2 -0
  41. package/dist/esm/config/config.default.js +15 -0
  42. package/dist/esm/index.d.ts +1 -0
  43. package/dist/esm/index.js +2 -0
  44. package/dist/esm/lib/load_schedule.d.ts +3 -0
  45. package/dist/esm/lib/load_schedule.js +65 -0
  46. package/dist/esm/lib/schedule.d.ts +31 -0
  47. package/dist/esm/lib/schedule.js +85 -0
  48. package/dist/esm/lib/schedule_worker.d.ts +10 -0
  49. package/dist/esm/lib/schedule_worker.js +18 -0
  50. package/dist/esm/lib/strategy/all.d.ts +4 -0
  51. package/dist/esm/lib/strategy/all.js +7 -0
  52. package/dist/esm/lib/strategy/base.d.ts +30 -0
  53. package/dist/esm/lib/strategy/base.js +75 -0
  54. package/dist/esm/lib/strategy/timer.d.ts +20 -0
  55. package/dist/esm/lib/strategy/timer.js +92 -0
  56. package/dist/esm/lib/strategy/worker.d.ts +4 -0
  57. package/dist/esm/lib/strategy/worker.js +7 -0
  58. package/dist/esm/lib/types.d.ts +53 -0
  59. package/dist/esm/lib/types.js +2 -0
  60. package/dist/esm/package.json +3 -0
  61. package/dist/package.json +4 -0
  62. package/package.json +91 -0
  63. package/src/agent.ts +36 -0
  64. package/src/app/extend/agent.ts +30 -0
  65. package/src/app/extend/application.ts +16 -0
  66. package/src/app.ts +142 -0
  67. package/src/config/config.default.ts +17 -0
  68. package/src/index.ts +1 -0
  69. package/src/lib/load_schedule.ts +74 -0
  70. package/src/lib/schedule.ts +100 -0
  71. package/src/lib/schedule_worker.ts +24 -0
  72. package/src/lib/strategy/all.ts +7 -0
  73. package/src/lib/strategy/base.ts +91 -0
  74. package/src/lib/strategy/timer.ts +106 -0
  75. package/src/lib/strategy/worker.ts +7 -0
  76. package/src/lib/types.ts +58 -0
@@ -0,0 +1,100 @@
1
+ import { debuglog } from 'node:util';
2
+ import type { Agent, EggLogger } from 'egg';
3
+ import { loadSchedule } from './load_schedule.js';
4
+ import type { EggScheduleItem, EggScheduleJobInfo } from './types.js';
5
+ import type { BaseStrategy } from './strategy/base.js';
6
+
7
+ const debug = debuglog('@eggjs/schedule/lib/schedule');
8
+
9
+ export class Schedule {
10
+ closed = false;
11
+
12
+ #agent: Agent;
13
+ #logger: EggLogger;
14
+ #strategyClassMap = new Map<string, typeof BaseStrategy>();
15
+ #strategyInstanceMap = new Map<string, BaseStrategy>();
16
+
17
+ constructor(agent: Agent) {
18
+ this.#agent = agent;
19
+ this.#logger = agent.getLogger('scheduleLogger');
20
+ }
21
+
22
+ /**
23
+ * register a custom Schedule Strategy
24
+ * @param {String} type - strategy type
25
+ * @param {Strategy} clz - Strategy class
26
+ */
27
+ use(type: string, clz: typeof BaseStrategy) {
28
+ this.#strategyClassMap.set(type, clz);
29
+ debug('use type: %o', type);
30
+ }
31
+
32
+ /**
33
+ * load all schedule jobs, then initialize and register speical strategy
34
+ */
35
+ async init() {
36
+ const scheduleItems = await loadSchedule(this.#agent);
37
+ for (const scheduleItem of Object.values(scheduleItems)) {
38
+ this.registerSchedule(scheduleItem);
39
+ }
40
+ }
41
+
42
+ registerSchedule(scheduleItem: EggScheduleItem) {
43
+ const { key, schedule } = scheduleItem;
44
+ const type = schedule.type;
45
+ if (schedule.disable) {
46
+ return;
47
+ }
48
+
49
+ // find Strategy by type
50
+ const Strategy = this.#strategyClassMap.get(type!);
51
+ if (!Strategy) {
52
+ const err = new Error(`schedule type [${type}] is not defined`);
53
+ err.name = 'EggScheduleError';
54
+ throw err;
55
+ }
56
+
57
+ // Initialize strategy and register
58
+ const instance = new Strategy(schedule, this.#agent, key);
59
+ this.#strategyInstanceMap.set(key, instance);
60
+ debug('registerSchedule type: %o, config: %o, key: %o', type, schedule, key);
61
+ }
62
+
63
+ unregisterSchedule(key: string) {
64
+ debug('unregisterSchedule key: %o', key);
65
+ return this.#strategyInstanceMap.delete(key);
66
+ }
67
+
68
+ /**
69
+ * job finish event handler
70
+ *
71
+ * @param {Object} info - { id, key, success, message, workerId }
72
+ */
73
+ onJobFinish(info: EggScheduleJobInfo) {
74
+ this.#logger.debug(`[Job#${info.id}] ${info.key} finish event received by agent from worker#${info.workerId}`);
75
+ const instance = this.#strategyInstanceMap.get(info.key);
76
+ /* istanbul ignore else */
77
+ if (instance) {
78
+ instance.onJobFinish(info);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * start schedule
84
+ */
85
+ start() {
86
+ debug('start');
87
+ this.closed = false;
88
+ for (const instance of this.#strategyInstanceMap.values()) {
89
+ instance.start();
90
+ }
91
+ }
92
+
93
+ close() {
94
+ this.closed = true;
95
+ for (const instance of this.#strategyInstanceMap.values()) {
96
+ instance.close();
97
+ }
98
+ debug('close');
99
+ }
100
+ }
@@ -0,0 +1,24 @@
1
+ import type { Application } from 'egg';
2
+ import { loadSchedule } from './load_schedule.js';
3
+ import type { EggScheduleItem } from './types.js';
4
+
5
+ export class ScheduleWorker {
6
+ #app: Application;
7
+ scheduleItems: Record<string, EggScheduleItem> = {};
8
+
9
+ constructor(app: Application) {
10
+ this.#app = app;
11
+ }
12
+
13
+ async init() {
14
+ this.scheduleItems = await loadSchedule(this.#app);
15
+ }
16
+
17
+ registerSchedule(scheduleItem: EggScheduleItem) {
18
+ this.scheduleItems[scheduleItem.key] = scheduleItem;
19
+ }
20
+
21
+ unregisterSchedule(key: string) {
22
+ delete this.scheduleItems[key];
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ import { TimerStrategy } from './timer.js';
2
+
3
+ export class AllStrategy extends TimerStrategy {
4
+ handler() {
5
+ this.sendAll();
6
+ }
7
+ }
@@ -0,0 +1,91 @@
1
+ import type { Agent, EggLogger } from 'egg';
2
+ import type { EggScheduleConfig, EggScheduleJobInfo } from '../types.js';
3
+
4
+ export class BaseStrategy {
5
+ protected agent: Agent;
6
+ protected scheduleConfig: EggScheduleConfig;
7
+ protected key: string;
8
+ protected logger: EggLogger;
9
+ protected closed = false;
10
+ count = 0;
11
+
12
+ constructor(scheduleConfig: EggScheduleConfig, agent: Agent, key: string) {
13
+ this.agent = agent;
14
+ this.key = key;
15
+ this.scheduleConfig = scheduleConfig;
16
+ this.logger = this.agent.getLogger('scheduleLogger');
17
+ }
18
+
19
+ /** keep compatibility */
20
+ get schedule(): EggScheduleConfig {
21
+ return this.scheduleConfig;
22
+ }
23
+
24
+ start() {
25
+ // empty loop by default
26
+ }
27
+
28
+ close() {
29
+ this.closed = true;
30
+ }
31
+
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ onJobStart(_info: EggScheduleJobInfo) {}
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
36
+ onJobFinish(_info: EggScheduleJobInfo) {}
37
+
38
+ /**
39
+ * trigger one worker
40
+ *
41
+ * @param {...any} args - pass to job task
42
+ */
43
+ sendOne(...args: any[]) {
44
+ /* istanbul ignore next */
45
+ if (this.agent.schedule.closed) {
46
+ this.logger.warn(`${this.key} skip due to schedule closed`);
47
+ return;
48
+ }
49
+
50
+ this.count++;
51
+
52
+ const info = {
53
+ key: this.key,
54
+ id: this.getSeqId(),
55
+ args,
56
+ } as EggScheduleJobInfo;
57
+
58
+ this.logger.info(`[Job#${info.id}] ${info.key} triggered, send random by agent`);
59
+ this.agent.messenger.sendRandom('egg-schedule', info);
60
+ this.onJobStart(info);
61
+ }
62
+
63
+ /**
64
+ * trigger all worker
65
+ *
66
+ * @param {...any} args - pass to job task
67
+ */
68
+ sendAll(...args: any[]) {
69
+ /* istanbul ignore next */
70
+ if (this.agent.schedule.closed) {
71
+ this.logger.warn(`${this.key} skip due to schedule closed`);
72
+ return;
73
+ }
74
+
75
+ this.count++;
76
+
77
+ const info = {
78
+ key: this.key,
79
+ id: this.getSeqId(),
80
+ args,
81
+ } as EggScheduleJobInfo;
82
+ this.logger.info(`[Job#${info.id}] ${info.key} triggered, send all by agent`);
83
+ // send to all workers
84
+ this.agent.messenger.send('egg-schedule', info);
85
+ this.onJobStart(info);
86
+ }
87
+
88
+ getSeqId() {
89
+ return `${Date.now()}${process.hrtime().join('')}${this.count}`;
90
+ }
91
+ }
@@ -0,0 +1,106 @@
1
+ import assert from 'node:assert';
2
+ import { parseExpression, type CronExpression } from 'cron-parser';
3
+ import { ms } from 'humanize-ms';
4
+ import safeTimers from 'safe-timers';
5
+ import { logDate } from 'utility';
6
+ import type { Agent } from 'egg';
7
+ import type { EggScheduleConfig } from '../types.js';
8
+ import { BaseStrategy } from './base.js';
9
+
10
+ export abstract class TimerStrategy extends BaseStrategy {
11
+ protected cronInstance?: CronExpression;
12
+
13
+ constructor(scheduleConfig: EggScheduleConfig, agent: Agent, key: string) {
14
+ super(scheduleConfig, agent, key);
15
+
16
+ const { interval, cron, cronOptions, immediate } = this.scheduleConfig;
17
+ assert(interval || cron || immediate,
18
+ `[@eggjs/schedule] ${this.key} \`schedule.interval\` or \`schedule.cron\` or \`schedule.immediate\` must be present`);
19
+
20
+ // init cron parser
21
+ if (cron) {
22
+ try {
23
+ this.cronInstance = parseExpression(cron, cronOptions);
24
+ } catch (err: any) {
25
+ throw new TypeError(
26
+ `[@eggjs/schedule] ${this.key} parse cron instruction(${cron}) error: ${err.message}`,
27
+ { cause: err });
28
+ }
29
+ }
30
+ }
31
+
32
+ protected handler() {
33
+ throw new TypeError(`[@eggjs/schedule] ${this.key} strategy should override \`handler()\` method`);
34
+ }
35
+
36
+
37
+ start() {
38
+ /* istanbul ignore next */
39
+ if (this.agent.schedule.closed) return;
40
+
41
+ if (this.scheduleConfig.immediate) {
42
+ this.logger.info(`[Timer] ${this.key} next time will execute immediate`);
43
+ setImmediate(() => this.handler());
44
+ } else {
45
+ this.#scheduleNext();
46
+ }
47
+ }
48
+
49
+ #scheduleNext() {
50
+ /* istanbul ignore next */
51
+ if (this.agent.schedule.closed) return;
52
+
53
+ // get next tick
54
+ const nextTick = this.getNextTick();
55
+ if (nextTick) {
56
+ this.logger.info(
57
+ `[Timer] ${this.key} next time will execute after ${nextTick}ms at ${logDate(new Date(Date.now() + nextTick))}`);
58
+ this.safeTimeout(() => this.handler(), nextTick);
59
+ } else {
60
+ this.logger.info(`[Timer] ${this.key} reach endDate, will stop`);
61
+ }
62
+ }
63
+
64
+ onJobStart() {
65
+ // Next execution will trigger task at a fix rate, regardless of its execution time.
66
+ this.#scheduleNext();
67
+ }
68
+
69
+ /**
70
+ * calculate next tick
71
+ *
72
+ * @return {Number|undefined} time interval, if out of range then return `undefined`
73
+ */
74
+ protected getNextTick(): number | undefined {
75
+ // interval-style
76
+ if (this.scheduleConfig.interval) {
77
+ return ms(this.scheduleConfig.interval);
78
+ }
79
+
80
+ // cron-style
81
+ if (this.cronInstance) {
82
+ // calculate next cron tick
83
+ const now = Date.now();
84
+ let nextTick: number;
85
+
86
+ // loop to find next feature time
87
+ do {
88
+ try {
89
+ const nextInterval = this.cronInstance.next();
90
+ nextTick = nextInterval.getTime();
91
+ } catch (err) {
92
+ // Error: Out of the timespan range
93
+ this.logger.info(`[Timer] ${this.key} cron out of the timespan range, error: %s`, err);
94
+ return;
95
+ }
96
+ } while (now >= nextTick);
97
+ return nextTick - now;
98
+ }
99
+ // won\'t run here
100
+ }
101
+
102
+ protected safeTimeout(handler: () => void, delay: number, ...args: any[]) {
103
+ const fn = delay < safeTimers.maxInterval ? setTimeout : safeTimers.setTimeout;
104
+ return fn(handler, delay, ...args);
105
+ }
106
+ }
@@ -0,0 +1,7 @@
1
+ import { TimerStrategy } from './timer.js';
2
+
3
+ export class WorkerStrategy extends TimerStrategy {
4
+ handler() {
5
+ this.sendOne();
6
+ }
7
+ }
@@ -0,0 +1,58 @@
1
+ import type { ParserOptions as CronOptions } from 'cron-parser';
2
+ import type { Schedule } from './schedule.js';
3
+ import type { ScheduleWorker } from './schedule_worker.js';
4
+
5
+ /**
6
+ * Schedule Config
7
+ * @see https://www.eggjs.org/zh-CN/basics/schedule
8
+ */
9
+ export interface EggScheduleConfig {
10
+ type?: 'worker' | 'all';
11
+ interval?: string | number;
12
+ cron?: string;
13
+ cronOptions?: CronOptions;
14
+ immediate?: boolean;
15
+ disable?: boolean;
16
+ env?: string[];
17
+ }
18
+
19
+ export type EggScheduleTask = (ctx: any, ...args: any[]) => Promise<void>;
20
+
21
+ export interface EggScheduleItem {
22
+ schedule: EggScheduleConfig;
23
+ scheduleQueryString: string;
24
+ task: EggScheduleTask;
25
+ key: string;
26
+ }
27
+
28
+ export interface EggScheduleJobInfo {
29
+ id: string;
30
+ key: string;
31
+ workerId: number;
32
+ args: any[];
33
+ success?: boolean;
34
+ message?: string;
35
+ rt?: number;
36
+ }
37
+
38
+ declare module 'egg' {
39
+ export interface EggScheduleAgent {
40
+ schedule: Schedule;
41
+ }
42
+ export interface Agent extends EggScheduleAgent {}
43
+
44
+ export interface EggScheduleApplication {
45
+ scheduleWorker: ScheduleWorker;
46
+ /** runSchedule in unittest */
47
+ runSchedule: (schedulePath: string, ...args: any[]) => Promise<void>;
48
+ }
49
+ export interface Application extends EggScheduleApplication {}
50
+
51
+ export interface EggScheduleAppConfig {
52
+ schedule: {
53
+ directory: string[];
54
+ };
55
+ }
56
+
57
+ export interface EggAppConfig extends EggScheduleAppConfig {}
58
+ }