@cleverbrush/scheduler 1.1.11 → 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/package.json CHANGED
@@ -5,7 +5,10 @@
5
5
  "email": "andrew_zol@cleverbrush.com"
6
6
  },
7
7
  "dependencies": {
8
- "@cleverbrush/schema": "1.1.11"
8
+ "@cleverbrush/schema": "^2.0.0"
9
+ },
10
+ "devDependencies": {
11
+ "@types/node": "^25.4.0"
9
12
  },
10
13
  "description": "Job Scheduler for NodeJS",
11
14
  "files": [
@@ -20,6 +23,13 @@
20
23
  ],
21
24
  "license": "BSD 3-Clause",
22
25
  "main": "./dist/index.js",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "sideEffects": false,
23
33
  "name": "@cleverbrush/scheduler",
24
34
  "readme": "https://github.com/cleverbrush/framework/tree/master/libs/scheduler#readme",
25
35
  "repository": {
@@ -27,10 +37,11 @@
27
37
  "url": "github:cleverbrush/framework"
28
38
  },
29
39
  "scripts": {
30
- "watch": "tsc --watch",
31
- "build": "tsc"
40
+ "watch": "tsc --build --watch",
41
+ "build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
42
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
32
43
  },
33
44
  "type": "module",
34
45
  "types": "./dist/index.d.ts",
35
- "version": "1.1.11"
46
+ "version": "2.0.0"
36
47
  }
@@ -1,208 +0,0 @@
1
- const MS_IN_DAY = 1000 * 60 * 60 * 24;
2
- const MS_IN_WEEK = MS_IN_DAY * 7;
3
- const getDayOfWeek = (date) => {
4
- const res = date.getUTCDay();
5
- if (res === 0)
6
- return 7;
7
- return res;
8
- };
9
- const getNumberOfDaysInMonth = (date) => {
10
- return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0, 0, 0, 0, 0)).getDate();
11
- };
12
- export class ScheduleCalculator {
13
- #schedule;
14
- #currentDate = new Date();
15
- #hour = 9;
16
- #minute = 0;
17
- #maxRepeat = -1;
18
- #repeatCount = 0;
19
- #hasNext = false;
20
- #next;
21
- constructor(schedule) {
22
- if (!schedule)
23
- throw new Error('schedule is required');
24
- this.#schedule = { ...schedule };
25
- if (typeof schedule.startsOn !== 'undefined') {
26
- this.#currentDate = schedule.startsOn;
27
- }
28
- else {
29
- this.#schedule.startsOn = new Date();
30
- this.#currentDate = this.#schedule.startsOn;
31
- }
32
- if (schedule.every !== 'minute') {
33
- if (typeof schedule.hour === 'number') {
34
- this.#hour = schedule.hour;
35
- }
36
- if (typeof schedule.minute === 'number') {
37
- this.#minute = schedule.minute;
38
- }
39
- if (schedule.every === 'day' &&
40
- new Date(Date.UTC(this.#currentDate.getUTCFullYear(), this.#currentDate.getUTCMonth(), this.#currentDate.getUTCDate(), this.#hour, this.#minute, 0, 0)).getTime() < this.#currentDate.getTime()) {
41
- const date = new Date(Date.UTC(this.#currentDate.getUTCFullYear(), this.#currentDate.getUTCMonth(), this.#currentDate.getUTCDate() + 1, this.#hour, this.#minute, 0, 0));
42
- this.#currentDate = date;
43
- }
44
- }
45
- if (typeof schedule.maxOccurences === 'number') {
46
- this.#maxRepeat = schedule.maxOccurences;
47
- }
48
- const next = this.#getNext();
49
- if (typeof next !== 'undefined') {
50
- this.#next = next;
51
- this.#hasNext = true;
52
- }
53
- let leftToSkip = typeof this.#schedule.skipFirst === 'number'
54
- ? this.#schedule.skipFirst
55
- : 0;
56
- while (leftToSkip-- > 0 && this.#hasNext) {
57
- this.next();
58
- }
59
- }
60
- #getNext() {
61
- let candidate = null;
62
- let dayOfWeek;
63
- switch (this.#schedule.every) {
64
- case 'minute':
65
- candidate =
66
- this.#repeatCount === 0
67
- ? this.#schedule.startsOn
68
- : new Date(Date.UTC(this.#currentDate.getUTCFullYear(), this.#currentDate.getUTCMonth(), this.#currentDate.getUTCDate(), this.#currentDate.getUTCHours(), this.#currentDate.getUTCMinutes() +
69
- this.#schedule.interval, this.#currentDate.getUTCSeconds(), this.#currentDate.getUTCMilliseconds()));
70
- break;
71
- case 'day':
72
- {
73
- const date = new Date(this.#currentDate.getTime() +
74
- MS_IN_DAY *
75
- (this.#repeatCount === 0
76
- ? 0
77
- : this.#schedule.interval - 1));
78
- candidate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), this.#hour, this.#minute, 0, 0));
79
- }
80
- break;
81
- case 'week':
82
- {
83
- let date = this.#currentDate;
84
- do {
85
- let found = false;
86
- dayOfWeek = getDayOfWeek(date);
87
- if (Number.isNaN(dayOfWeek))
88
- return;
89
- for (let i = 0; i < 7 - dayOfWeek + 1; i++) {
90
- date = new Date(date.getTime() + (i == 0 ? 0 : MS_IN_DAY));
91
- if (this.#schedule.endsOn &&
92
- date > this.#schedule.endsOn) {
93
- return;
94
- }
95
- if (this.#schedule.dayOfWeek.includes(getDayOfWeek(date))) {
96
- const dateWithTime = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), this.#hour, this.#minute, 0, 0));
97
- if (dateWithTime >
98
- this.#schedule.startsOn) {
99
- candidate = dateWithTime;
100
- found = true;
101
- break;
102
- }
103
- }
104
- }
105
- if (found)
106
- break;
107
- date = new Date(date.getTime() +
108
- MS_IN_DAY +
109
- (this.#repeatCount == 0
110
- ? 0
111
- : (this.#schedule.interval - 1) *
112
- MS_IN_WEEK));
113
- } while ((this.#schedule.endsOn &&
114
- date <= this.#schedule.endsOn) ||
115
- !this.#schedule.endsOn);
116
- }
117
- break;
118
- case 'month':
119
- {
120
- let dateTime;
121
- const cDate = this.#currentDate;
122
- let iteration = 0;
123
- do {
124
- const date = this.#schedule.day === 'last'
125
- ? getNumberOfDaysInMonth(new Date(Date.UTC(cDate.getUTCFullYear(), cDate.getUTCMonth() +
126
- iteration *
127
- (this.#repeatCount === 0
128
- ? 1
129
- : this.#schedule
130
- .interval), 1, 0, 0, 0, 0)))
131
- : this.#schedule.day;
132
- dateTime = new Date(Date.UTC(cDate.getUTCFullYear(), cDate.getUTCMonth() +
133
- iteration *
134
- (this.#repeatCount === 0
135
- ? 1
136
- : this.#schedule.interval), date, this.#hour, this.#minute, 0, 0));
137
- iteration++;
138
- } while (dateTime < this.#schedule.startsOn ||
139
- dateTime <= this.#currentDate);
140
- candidate = dateTime;
141
- }
142
- break;
143
- case 'year':
144
- {
145
- let dateTime;
146
- const cDate = this.#currentDate;
147
- let iteration = 0;
148
- do {
149
- const date = this.#schedule.day === 'last'
150
- ? getNumberOfDaysInMonth(new Date(Date.UTC(cDate.getUTCFullYear() +
151
- iteration *
152
- (this.#repeatCount === 0
153
- ? 1
154
- : this.#schedule
155
- .interval), this.#schedule.month - 1, 1, 0, 0, 0, 0)))
156
- : this.#schedule.day;
157
- dateTime = new Date(Date.UTC(cDate.getUTCFullYear() +
158
- iteration *
159
- (this.#repeatCount === 0
160
- ? 1
161
- : this.#schedule.interval), this.#schedule.month - 1, date, this.#hour, this.#minute, 0, 0));
162
- iteration++;
163
- } while (dateTime < this.#schedule.startsOn ||
164
- dateTime <= this.#currentDate);
165
- candidate = dateTime;
166
- }
167
- break;
168
- default:
169
- throw new Error('unknown schedule type');
170
- }
171
- if (!candidate)
172
- return;
173
- if (typeof this.#schedule.endsOn !== 'undefined' &&
174
- candidate > this.#schedule.endsOn) {
175
- return;
176
- }
177
- return candidate;
178
- }
179
- hasNext(span) {
180
- if (!this.#hasNext) {
181
- return false;
182
- }
183
- if (typeof span !== 'number')
184
- return this.#hasNext;
185
- if (!this.#next)
186
- return false;
187
- return this.#next.getTime() - new Date().getTime() <= span;
188
- }
189
- next() {
190
- if (!this.#hasNext)
191
- throw new Error('schedule is over');
192
- const result = this.#next;
193
- this.#currentDate = new Date(result.getTime() +
194
- (['day', 'week'].includes(this.#schedule.every) ? MS_IN_DAY : 0));
195
- this.#repeatCount++;
196
- const next = this.#getNext();
197
- this.#next = next;
198
- this.#hasNext = typeof next !== 'undefined';
199
- if (this.#maxRepeat > 0 && this.#repeatCount >= this.#maxRepeat) {
200
- this.#next = undefined;
201
- this.#hasNext = false;
202
- }
203
- return {
204
- date: result,
205
- index: this.#repeatCount
206
- };
207
- }
208
- }
@@ -1,79 +0,0 @@
1
- export class InMemoryJobRepository {
2
- _jobs = [];
3
- _jobInstances = [];
4
- _instanceId = 1;
5
- async getJobs() {
6
- return this._jobs;
7
- }
8
- async removeJob(jobId) {
9
- const job = this.getJobById(jobId);
10
- if (!job)
11
- throw new Error(`job with id ${jobId} doesn't exist`);
12
- this._jobs = this._jobs.filter((j) => j.id !== jobId);
13
- }
14
- async createJob(item) {
15
- const job = {
16
- ...item,
17
- status: 'active'
18
- };
19
- this._jobs.push(job);
20
- return job;
21
- }
22
- async getInstances(jobId) {
23
- return this._jobInstances.filter((ji) => ji.jobId === jobId);
24
- }
25
- async addInstance(jobId, instance) {
26
- const newInstance = {
27
- ...instance,
28
- id: this._instanceId++,
29
- jobId
30
- };
31
- this._jobInstances.push(newInstance);
32
- return newInstance;
33
- }
34
- async getJobById(jobId) {
35
- return this._jobs.find((j) => j.id === jobId);
36
- }
37
- async setJobStatus(jobId, status) {
38
- const job = await this.getJobById(jobId);
39
- if (!job)
40
- return null;
41
- job.status = status;
42
- return job;
43
- }
44
- async saveJob(job) {
45
- const index = this._jobs.findIndex((j) => j.id === job.id);
46
- if (index !== -1) {
47
- this._jobs[index] = {
48
- ...job
49
- };
50
- return this._jobs[index];
51
- }
52
- const result = {
53
- ...job
54
- };
55
- this._jobs.push(result);
56
- return result;
57
- }
58
- async getInstancesWithStatus(jobId, status) {
59
- return (await this.getInstances(jobId)).filter((i) => i.status === status);
60
- }
61
- async getInstanceById(id) {
62
- return this._jobInstances.find((ji) => ji.id === id);
63
- }
64
- async saveInstance(instance) {
65
- const oldIndex = this._jobInstances.findIndex((ji) => ji.id === instance.id);
66
- if (oldIndex !== -1) {
67
- this._jobInstances[oldIndex] = {
68
- ...instance
69
- };
70
- return this._jobInstances[oldIndex];
71
- }
72
- const result = {
73
- ...instance,
74
- id: this._instanceId++
75
- };
76
- this._jobInstances.push(result);
77
- return result;
78
- }
79
- }
package/dist/types.js DELETED
@@ -1,115 +0,0 @@
1
- import { array, boolean, date, func, number, object, string, union } from '@cleverbrush/schema';
2
- const ScheduleSchemaBase = object({
3
- /** Number of intervals (days, months, minutes or weeks)
4
- * between repeats. Interval type depends of `every` value */
5
- interval: number().min(1).max(356),
6
- /** Hour (0-23) */
7
- hour: number().min(0).max(23).optional(),
8
- /** Minute (0-59) */
9
- minute: number().min(0).max(59).optional(),
10
- /** Do not start earlier than this date */
11
- startsOn: date().acceptJsonString().optional(),
12
- /** Do not repeat after this date */
13
- endsOn: date().acceptJsonString().optional(),
14
- /** Max number of repeats (min 1) */
15
- maxOccurences: number().min(1).optional(),
16
- /** Skip this number of repeats. Min value is 1. */
17
- skipFirst: number().min(1).optional()
18
- }).addValidator((val) => {
19
- if ('endsOn' in val &&
20
- 'maxOccurences' in val &&
21
- typeof val.endsOn !== 'undefined') {
22
- return {
23
- valid: false,
24
- errors: [{ message: 'either endsOn or maxOccurences is required' }]
25
- };
26
- }
27
- return { valid: true };
28
- });
29
- const ScheduleMinuteSchema = ScheduleSchemaBase.omit('hour')
30
- .omit('minute')
31
- .addProps({
32
- /** Repeat every minute */
33
- every: string('minute')
34
- });
35
- const ScheduleDaySchema = ScheduleSchemaBase.addProps({
36
- /** Repeat every day */
37
- every: string('day')
38
- });
39
- const ScheduleWeekSchema = ScheduleSchemaBase.addProps({
40
- /** Repeat every week */
41
- every: string('week'),
42
- /** Days of week for schedule */
43
- dayOfWeek: array()
44
- .of(number().min(1).max(7))
45
- .minLength(1)
46
- .maxLength(7)
47
- .addValidator((val) => {
48
- const map = {};
49
- for (let i = 0; i < val.length; i++) {
50
- if (map[val[i]]) {
51
- return {
52
- valid: false,
53
- errors: [{ message: 'no duplicates allowed' }]
54
- };
55
- }
56
- map[val[i]] = true;
57
- }
58
- return {
59
- valid: true
60
- };
61
- })
62
- });
63
- const ScheduleMonthSchema = ScheduleSchemaBase.addProps({
64
- /** Repeat every month */
65
- every: string('month'),
66
- /** Day - 'last' or number from 1 to 28 */
67
- day: union(string('last')).or(number().min(1).max(28))
68
- });
69
- const ScheduleYearSchema = ScheduleSchemaBase.addProps({
70
- /** Repeat every year */
71
- every: string('year'),
72
- /** Day - 'last' or number from 1 to 28 */
73
- day: union(string('last')).or(number().min(1).max(28)),
74
- /** Month - number from 1 to 12 */
75
- month: number().min(1).max(12)
76
- });
77
- const ScheduleSchema = union(ScheduleMinuteSchema)
78
- .or(ScheduleDaySchema)
79
- .or(ScheduleWeekSchema)
80
- .or(ScheduleMonthSchema)
81
- .or(ScheduleYearSchema);
82
- const CreateJobRequestSchema = object({
83
- /** Id of job, must be uniq */
84
- id: string(),
85
- /** Path to js file (relative to root folder) */
86
- path: string().minLength(1),
87
- /** Job's schedule */
88
- schedule: ScheduleSchema,
89
- /** Timeout for job (in milliseconds) */
90
- timeout: number().min(0).optional(),
91
- /** Arbitrary props for job (can be a callback returning props or Promise<props>) */
92
- props: union(object().acceptUnknownProps()).or(func()).optional(),
93
- /** Job will be considered as disabled when more than that count of runs fails consequently
94
- * unlimited if negative
95
- */
96
- maxConsequentFails: number().optional(),
97
- /**
98
- * Job will be retried right away this times. Job will be retried on next schedule run if this number is exceeded.
99
- */
100
- maxRetries: number().optional().min(1),
101
- /**
102
- * If true, job will not be runned if previous run is not finished yet.
103
- */
104
- noConcurrentRuns: boolean().optional()
105
- });
106
- export const Schemas = {
107
- ScheduleSchemaBase,
108
- ScheduleSchema,
109
- CreateJobRequestSchema,
110
- ScheduleMinuteSchema,
111
- ScheduleDaySchema,
112
- ScheduleWeekSchema,
113
- ScheduleMonthSchema,
114
- ScheduleYearSchema
115
- };