@cleverbrush/scheduler 0.0.0-beta-20260410073748

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 ADDED
@@ -0,0 +1,174 @@
1
+ # @cleverbrush/scheduler
2
+ <!-- coverage-badge-start -->
3
+ ![Coverage](https://img.shields.io/badge/coverage-96.7%25-brightgreen)
4
+ <!-- coverage-badge-end -->
5
+
6
+ A job scheduler for Node.js that runs tasks in worker threads on configurable schedules.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @cleverbrush/scheduler
12
+ ```
13
+
14
+ This library uses the Node.js `worker_threads` module. It is tested on Node.js v16+.
15
+
16
+ ## Quick Start
17
+
18
+ ```typescript
19
+ import { JobScheduler } from '@cleverbrush/scheduler';
20
+
21
+ const scheduler = new JobScheduler({
22
+ rootFolder: '/path/to/your/jobs'
23
+ });
24
+
25
+ scheduler.addJob({
26
+ id: 'my-job-1',
27
+ path: 'job1.js',
28
+ schedule: {
29
+ every: 'minute',
30
+ interval: 5
31
+ }
32
+ });
33
+
34
+ scheduler.addJob({
35
+ id: 'my-job-2',
36
+ path: 'job2.js',
37
+ schedule: {
38
+ every: 'week',
39
+ interval: 2,
40
+ dayOfWeek: [1, 3, 5],
41
+ hour: 9,
42
+ minute: 30
43
+ },
44
+ timeout: 1000 * 60 * 4,
45
+ maxRetries: 3
46
+ });
47
+
48
+ scheduler.start();
49
+ ```
50
+
51
+ ## API
52
+
53
+ ### `JobScheduler`
54
+
55
+ The main class for scheduling and running jobs. Extends `EventEmitter`.
56
+
57
+ #### Constructor
58
+
59
+ ```typescript
60
+ const scheduler = new JobScheduler(props: JobSchedulerProps);
61
+ ```
62
+
63
+ | Property | Type | Default | Description |
64
+ | --- | --- | --- | --- |
65
+ | `rootFolder` | `string` | — | Path to the folder containing job files |
66
+ | `defaultTimeZone` | `string` | `'UTC'` | Timezone used for scheduling |
67
+
68
+ #### Methods
69
+
70
+ | Method | Description |
71
+ | --- | --- |
72
+ | `start()` | Starts the scheduler |
73
+ | `stop()` | Stops the scheduler |
74
+ | `addJob(job)` | Adds a job to the scheduler |
75
+ | `removeJob(jobId)` | Removes a job by ID |
76
+ | `jobExists(jobId)` | Returns `true` if a job with the given ID exists |
77
+ | `status` | Current scheduler status: `'started'` or `'stopped'` |
78
+
79
+ #### Events
80
+
81
+ ```typescript
82
+ scheduler.on('job:start', ({ jobId, instanceId, startDate }) => {
83
+ console.log(`Job ${jobId} started`);
84
+ });
85
+
86
+ scheduler.on('job:end', ({ jobId, instanceId, startDate, endDate, stdout, stderr }) => {
87
+ console.log(`Job ${jobId} finished`);
88
+ });
89
+
90
+ scheduler.on('job:error', ({ jobId, instanceId, startDate, endDate, stdout, stderr }) => {
91
+ console.error(`Job ${jobId} failed`);
92
+ });
93
+
94
+ scheduler.on('job:timeout', ({ jobId, instanceId, startDate, endDate }) => {
95
+ console.warn(`Job ${jobId} timed out`);
96
+ });
97
+
98
+ scheduler.on('job:message', ({ jobId, instanceId, message }) => {
99
+ console.log(`Message from ${jobId}:`, message);
100
+ });
101
+ ```
102
+
103
+ ### Job Configuration
104
+
105
+ ```typescript
106
+ scheduler.addJob({
107
+ id: 'unique-job-id',
108
+ path: 'relative/path/to/job.js',
109
+ schedule: { /* see Schedule Types */ },
110
+ timeout: 60000, // timeout in milliseconds
111
+ maxRetries: 3, // retry on failure
112
+ maxConsequentFails: 10, // disable after N consecutive failures
113
+ noConcurrentRuns: true, // prevent overlapping executions
114
+ props: { key: 'value' } // data passed to the worker
115
+ });
116
+ ```
117
+
118
+ ### Schedule Types
119
+
120
+ All schedules share these optional properties:
121
+
122
+ | Property | Type | Description |
123
+ | --- | --- | --- |
124
+ | `interval` | `number` | Number of periods between repeats (1–356) |
125
+ | `startsOn` | `Date` | Do not start before this date |
126
+ | `endsOn` | `Date` | Do not repeat after this date |
127
+ | `maxOccurences` | `number` | Maximum number of executions |
128
+ | `skipFirst` | `number` | Skip this many initial executions |
129
+
130
+ #### Minute
131
+
132
+ ```typescript
133
+ { every: 'minute', interval: 5 }
134
+ ```
135
+
136
+ Runs every N minutes.
137
+
138
+ #### Day
139
+
140
+ ```typescript
141
+ { every: 'day', interval: 1, hour: 9, minute: 30 }
142
+ ```
143
+
144
+ Runs every N days at the specified time.
145
+
146
+ #### Week
147
+
148
+ ```typescript
149
+ { every: 'week', interval: 2, dayOfWeek: [1, 3, 5], hour: 9, minute: 30 }
150
+ ```
151
+
152
+ Runs every N weeks on the specified days (1 = Monday, 7 = Sunday).
153
+
154
+ #### Month
155
+
156
+ ```typescript
157
+ { every: 'month', interval: 1, day: 15, hour: 0, minute: 0 }
158
+ // or on the last day of the month:
159
+ { every: 'month', interval: 1, day: 'last', hour: 0, minute: 0 }
160
+ ```
161
+
162
+ Runs every N months on the specified day (1–28 or `'last'`).
163
+
164
+ #### Year
165
+
166
+ ```typescript
167
+ { every: 'year', interval: 1, month: 6, day: 1, hour: 12, minute: 0 }
168
+ ```
169
+
170
+ Runs every N years on the specified month (1–12) and day (1–28 or `'last'`).
171
+
172
+ ## License
173
+
174
+ BSD-3-Clause
@@ -0,0 +1,52 @@
1
+ import type { Schedule } from './types.js';
2
+ /**
3
+ * Iterates over the dates defined by a {@link Schedule}.
4
+ *
5
+ * Given a schedule configuration (e.g. every 2 days, every week on Monday/Friday,
6
+ * every month on the 15th) the calculator produces the sequence of `Date` objects
7
+ * that match that schedule. Use {@link hasNext} / {@link next} to walk through
8
+ * the sequence.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const calc = new ScheduleCalculator({
13
+ * every: 'day',
14
+ * interval: 1,
15
+ * hour: 9,
16
+ * minute: 0,
17
+ * startsOn: new Date('2025-01-01T00:00:00Z'),
18
+ * maxOccurences: 5
19
+ * });
20
+ *
21
+ * while (calc.hasNext()) {
22
+ * const { date, index } = calc.next();
23
+ * console.log(`Run #${index} at ${date.toISOString()}`);
24
+ * }
25
+ * ```
26
+ */
27
+ export declare class ScheduleCalculator {
28
+ #private;
29
+ /**
30
+ * @param schedule - The schedule definition to iterate over.
31
+ * @throws If `schedule` is falsy.
32
+ */
33
+ constructor(schedule: Schedule);
34
+ /**
35
+ * Returns `true` when the schedule has at least one more date.
36
+ *
37
+ * @param span - Optional millisecond window. When provided the method
38
+ * returns `true` only if the next date falls within `span` ms from now.
39
+ */
40
+ hasNext(span?: number): boolean;
41
+ /**
42
+ * Advances to the next scheduled date and returns it together with
43
+ * its 1-based index in the sequence.
44
+ *
45
+ * @returns An object with the scheduled `date` and its `index`.
46
+ * @throws If the schedule has no more dates ({@link hasNext} is `false`).
47
+ */
48
+ next(): {
49
+ date: Date;
50
+ index: number;
51
+ };
52
+ }
@@ -0,0 +1,107 @@
1
+ import { EventEmitter } from 'events';
2
+ import { type Readable } from 'stream';
3
+ import { Worker } from 'worker_threads';
4
+ import { type IJobRepository } from './jobRepository.js';
5
+ import { ScheduleCalculator } from './ScheduleCalculator.js';
6
+ import { type CreateJobRequest, type Job, type JobInstance, type JobInstanceStatus, type JobSchedulerProps, Schedule, type SchedulerStatus, Schemas } from './types.js';
7
+ export { Schedule as TaskSchedule, ScheduleCalculator, Schemas };
8
+ type WorkerResult = {
9
+ status: JobInstanceStatus;
10
+ exitCode: number;
11
+ error?: Error;
12
+ };
13
+ type JobStartItem = {
14
+ jobId: string;
15
+ instanceId: number;
16
+ stdout: Readable;
17
+ stderr: Readable;
18
+ startDate: Date;
19
+ };
20
+ type JobEndItem = JobStartItem & {
21
+ endDate: Date;
22
+ };
23
+ type JobErrorItem = JobStartItem & {
24
+ endDate: Date;
25
+ error?: Error;
26
+ };
27
+ type JobMessageItem = Omit<JobStartItem, 'stdout' | 'stderr'> & {
28
+ value: any;
29
+ };
30
+ type Events = {
31
+ 'job:start': (job: JobStartItem) => any;
32
+ 'job:end': (job: JobEndItem) => any;
33
+ 'job:error': (job: JobErrorItem) => any;
34
+ 'job:timeout': (job: JobErrorItem) => any;
35
+ 'job:message': (msg: JobMessageItem) => any;
36
+ };
37
+ interface IJobScheduler {
38
+ on<T extends keyof Events>(name: T, callback: Events[T]): this;
39
+ addJob(job: CreateJobRequest): Promise<void>;
40
+ removeJob(id: string): Promise<void>;
41
+ }
42
+ /**
43
+ * Job scheduler class that handles all the scheduling and running of jobs.
44
+ */
45
+ export declare class JobScheduler extends EventEmitter implements IJobScheduler {
46
+ protected _rootFolder: string;
47
+ protected _status: SchedulerStatus;
48
+ protected _defaultTimezone: string;
49
+ protected _checkTimer: ReturnType<typeof setTimeout> | undefined;
50
+ protected _jobsRepository: IJobRepository;
51
+ protected _jobProps: Map<string, any>;
52
+ /**
53
+ * Status of the scheduler.
54
+ */
55
+ get status(): SchedulerStatus;
56
+ protected set status(val: SchedulerStatus);
57
+ private scheduleCalculatorCache;
58
+ /**
59
+ * Get the schedule calculator for a job by its id.
60
+ * @param {Job} job
61
+ * @returns {Promise<ScheduleCalculator>}
62
+ */
63
+ protected getJobSchedule(job: Job): Promise<ScheduleCalculator | undefined>;
64
+ protected runWorkerWithTimeout(file: string, props: any, timeout: number): {
65
+ promise: Promise<WorkerResult>;
66
+ worker: Worker;
67
+ };
68
+ private readToEnd;
69
+ /**
70
+ * Start a job instance. This will run the job and update the status of the instance.
71
+ * @param {JobInstance} instance - The job instance to start.
72
+ */
73
+ protected startJobInstance(instance: JobInstance): Promise<void>;
74
+ protected scheduleJobTo(job: Job, date: Date, index: number): Promise<JobInstance | null>;
75
+ protected checkForUpcomingJobs(): Promise<void>;
76
+ /**
77
+ * Starts the scheduler (all jobs will be scheduled and executed when it's time).
78
+ */
79
+ start(): Promise<void>;
80
+ /**
81
+ * Stops the scheduler (all jobs will be stopped and no new jobs will be scheduled).
82
+ */
83
+ stop(): void;
84
+ /**
85
+ * Checks if job exists by id
86
+ * @param {string} jobId - id of the job
87
+ * @returns {Promise<boolean>} - true if job exists, false otherwise
88
+ */
89
+ jobExists(jobId: string): Promise<boolean>;
90
+ /**
91
+ * Removes job by its id
92
+ * @param jobId {string} - id of the job
93
+ * @returns {Promise<void>}
94
+ */
95
+ removeJob(jobId: string): Promise<void>;
96
+ /**
97
+ * Adds job to the scheduler
98
+ * @param job {CreateJobRequest} - job to add
99
+ */
100
+ addJob(job: CreateJobRequest): Promise<void>;
101
+ /**
102
+ *
103
+ * @param props {JobSchedulerProps} - scheduler properties
104
+ */
105
+ constructor(props: JobSchedulerProps);
106
+ on<T extends keyof Events>(name: T, callback: Events[T]): this;
107
+ }
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{EventEmitter as V}from"events";import X from"fs";import{access as z}from"fs/promises";import{join as P}from"path";import{PassThrough as b}from"stream";import{Worker as $}from"worker_threads";var I=class{_jobs=[];_jobInstances=[];_instanceId=1;async getJobs(){return this._jobs}async removeJob(t){await this.getJobById(t),this._jobs=this._jobs.filter(e=>e.id!==t)}async createJob(t){let e={...t,status:"active"};return this._jobs.push(e),e}async getInstances(t){return this._jobInstances.filter(e=>e.jobId===t)}async addInstance(t,e){let s={...e,id:this._instanceId++,jobId:t};return this._jobInstances.push(s),s}async getJobById(t){let e=this._jobs.find(s=>s.id===t);if(!e)throw new Error(`Job with id ${t} not found`);return e}async setJobStatus(t,e){let s=await this.getJobById(t);return s.status=e,s}async saveJob(t){let e=this._jobs.findIndex(n=>n.id===t.id);if(e!==-1)return this._jobs[e]={...t},this._jobs[e];let s={...t};return this._jobs.push(s),s}async getInstancesWithStatus(t,e){return(await this.getInstances(t)).filter(s=>s.status===e)}async getInstanceById(t){return this._jobInstances.find(e=>e.id===t)}async saveInstance(t){let e=this._jobInstances.findIndex(n=>n.id===t.id);if(e!==-1)return this._jobInstances[e]={...t},this._jobInstances[e];let s={...t,id:this._instanceId++};return this._jobInstances.push(s),s}};var x=i=>{let t=i.getUTCDay();return t===0?7:t},C=i=>new Date(Date.UTC(i.getUTCFullYear(),i.getUTCMonth()+1,0,0,0,0,0)).getDate(),J=class{#t;#e=new Date;#r=9;#n=0;#a=-1;#s=0;#o=!1;#i;constructor(t){if(!t)throw new Error("schedule is required");if(this.#t={...t},typeof t.startsOn<"u"?this.#e=t.startsOn:(this.#t.startsOn=new Date,this.#e=this.#t.startsOn),t.every!=="minute"&&(typeof t.hour=="number"&&(this.#r=t.hour),typeof t.minute=="number"&&(this.#n=t.minute),t.every==="day"&&new Date(Date.UTC(this.#e.getUTCFullYear(),this.#e.getUTCMonth(),this.#e.getUTCDate(),this.#r,this.#n,0,0)).getTime()<this.#e.getTime())){let n=new Date(Date.UTC(this.#e.getUTCFullYear(),this.#e.getUTCMonth(),this.#e.getUTCDate()+1,this.#r,this.#n,0,0));this.#e=n}typeof t.maxOccurences=="number"&&(this.#a=t.maxOccurences);let e=this.#d();typeof e<"u"&&(this.#i=e,this.#o=!0);let s=typeof this.#t.skipFirst=="number"?this.#t.skipFirst:0;for(;s-- >0&&this.#o;)this.next()}#d(){let t=null,e;switch(this.#t.every){case"minute":t=this.#s===0?this.#t.startsOn:new Date(Date.UTC(this.#e.getUTCFullYear(),this.#e.getUTCMonth(),this.#e.getUTCDate(),this.#e.getUTCHours(),this.#e.getUTCMinutes()+this.#t.interval,this.#e.getUTCSeconds(),this.#e.getUTCMilliseconds()));break;case"day":{let s=new Date(this.#e.getTime()+864e5*(this.#s===0?0:this.#t.interval-1));t=new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate(),this.#r,this.#n,0,0))}break;case"week":{let s=this.#e;do{let n=!1;if(e=x(s),Number.isNaN(e))return;for(let r=0;r<7-e+1;r++){if(s=new Date(s.getTime()+(r===0?0:864e5)),this.#t.endsOn&&s>this.#t.endsOn)return;if(this.#t.dayOfWeek.includes(x(s))){let o=new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate(),this.#r,this.#n,0,0));if(o>this.#t.startsOn){t=o,n=!0;break}}}if(n)break;s=new Date(s.getTime()+864e5+(this.#s===0?0:(this.#t.interval-1)*6048e5))}while(this.#t.endsOn&&s<=this.#t.endsOn||!this.#t.endsOn)}break;case"month":{let s,n=this.#e,r=0;do{let o=this.#t.day==="last"?C(new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+r*(this.#s===0?1:this.#t.interval),1,0,0,0,0))):this.#t.day;s=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+r*(this.#s===0?1:this.#t.interval),o,this.#r,this.#n,0,0)),r++}while(s<this.#t.startsOn||s<=this.#e);t=s}break;case"year":{let s,n=this.#e,r=0;do{let o=this.#t.day==="last"?C(new Date(Date.UTC(n.getUTCFullYear()+r*(this.#s===0?1:this.#t.interval),this.#t.month-1,1,0,0,0,0))):this.#t.day;s=new Date(Date.UTC(n.getUTCFullYear()+r*(this.#s===0?1:this.#t.interval),this.#t.month-1,o,this.#r,this.#n,0,0)),r++}while(s<this.#t.startsOn||s<=this.#e);t=s}break;default:throw new Error("unknown schedule type")}if(t&&!(typeof this.#t.endsOn<"u"&&t>this.#t.endsOn))return t}hasNext(t){return this.#o?typeof t!="number"?this.#o:this.#i?this.#i.getTime()-Date.now()<=t:!1:!1}next(){if(!this.#o)throw new Error("schedule is over");let t=this.#i;this.#e=new Date(t.getTime()+(["day","week"].includes(this.#t.every)?864e5:0)),this.#s++;let e=this.#d();return this.#i=e,this.#o=typeof e<"u",this.#a>0&&this.#s>=this.#a&&(this.#i=void 0,this.#o=!1),{date:t,index:this.#s}}};import{array as L,boolean as K,date as R,func as Z,number as u,object as _,string as h,union as g}from"@cleverbrush/schema";var l=_({interval:u().min(1).max(356),hour:u().min(0).max(23).optional(),minute:u().min(0).max(59).optional(),startsOn:R().acceptJsonString().optional(),endsOn:R().acceptJsonString().optional(),maxOccurences:u().min(1).optional(),skipFirst:u().min(1).optional()}).addValidator(i=>"endsOn"in i&&"maxOccurences"in i&&typeof i.endsOn<"u"?{valid:!1,errors:[{message:"either endsOn or maxOccurences is required"}]}:{valid:!0}),v=l.omit("hour").omit("minute").addProps({every:h("minute")}),E=l.addProps({every:h("day")}),j=l.addProps({every:h("week"),dayOfWeek:L().of(u().min(1).max(7)).minLength(1).maxLength(7).addValidator(i=>{let t={};for(let e=0;e<i.length;e++){if(t[i[e]])return{valid:!1,errors:[{message:"no duplicates allowed"}]};t[i[e]]=!0}return{valid:!0}})}),k=l.addProps({every:h("month"),day:g(h("last")).or(u().min(1).max(28))}),U=l.addProps({every:h("year"),day:g(h("last")).or(u().min(1).max(28)),month:u().min(1).max(12)}),F=g(v).or(E).or(j).or(k).or(U),H=_({id:h(),path:h().minLength(1),schedule:F,timeout:u().min(0).optional(),props:g(_().acceptUnknownProps()).or(Z()).optional(),maxConsequentFails:u().optional(),maxRetries:u().optional().min(1),noConcurrentRuns:K().optional()}),O={ScheduleSchemaBase:l,ScheduleSchema:F,CreateJobRequestSchema:H,ScheduleMinuteSchema:v,ScheduleDaySchema:E,ScheduleWeekSchema:j,ScheduleMonthSchema:k,ScheduleYearSchema:U};var p=10*1024*1024,Q=1e3*10,G=1e3*60,tt=1e3*20,et=3,st=2,M=class extends V{_rootFolder;_status="stopped";_defaultTimezone;_checkTimer;_jobsRepository=new I;_jobProps=new Map;get status(){return this._status}set status(t){t!==this._status&&(this._status=t)}scheduleCalculatorCache=new Map;async getJobSchedule(t){if(this.scheduleCalculatorCache.has(t.id))return this.scheduleCalculatorCache.get(t.id);let e={...t.schedule};typeof t.firstInstanceEndedAt<"u"&&(e.startsOn=t.firstInstanceEndedAt),typeof t.successfullTimesRunned=="number"&&(e.skipFirst=t.successfullTimesRunned-1);let s=new J(e);return this.scheduleCalculatorCache.set(t.id,s),s}runWorkerWithTimeout(t,e,s){let n=new $(t,{workerData:e,execArgv:["--unhandled-rejections=strict"],stderr:!0,stdout:!0});return{promise:new Promise(o=>{let a=!1,d=!1,f,c=setTimeout(()=>{d||(a=!0,n.terminate(),o({exitCode:1,status:"timedout"}))},s);n.on("error",m=>{f=m}),n.on("exit",m=>{d||a||(clearTimeout(c),d=!0,o({status:m===0?"succeeded":"errored",exitCode:m,error:f}))})}),worker:n}}readToEnd(t){return new Promise((e,s)=>{let n=[];t.on("data",r=>n.push(r)),t.on("end",()=>e(Buffer.concat(n))),t.on("error",r=>s(r))})}async startJobInstance(t){let e=new Date;t=await this._jobsRepository.saveInstance({...t,status:"running",startDate:e});let s="errored",n=1;try{let r=await this._jobsRepository.getJobById(t.jobId),o=P(this._rootFolder,r.path),a=this._jobProps.get(r.id),d=typeof a=="function"?a():a;d instanceof Promise&&(d=await d),t=await this._jobsRepository.saveInstance({...t,status:"running"});let{promise:f,worker:c}=this.runWorkerWithTimeout(o,d,r.timeout),m=c.stdout.pipe(new b({highWaterMark:p})),A=c.stderr.pipe(new b({highWaterMark:p})),q=c.stdout.pipe(new b({highWaterMark:p})),W=c.stderr.pipe(new b({highWaterMark:p})),w=c.stdout.pipe(new b({highWaterMark:p})),T=c.stderr.pipe(new b({highWaterMark:p}));c.on("message",Y=>{this.emit("job:message",{instanceId:t.id,jobId:r.id,startDate:e,value:Y})}),this.emit("job:start",{instanceId:t.id,jobId:r.id,stderr:W,stdout:q,startDate:e});let N=(await this.readToEnd(m)).toString(),B=(await this.readToEnd(A)).toString(),S=await f;s=S.status,n=S.exitCode;let{error:D}=S,y=new Date;switch(s){case"errored":this.emit("job:error",{instanceId:t.id,jobId:r.id,stderr:T,stdout:w,startDate:e,endDate:y,error:D});break;case"timedout":this.emit("job:timeout",{instanceId:t.id,jobId:r.id,stderr:T,stdout:w,startDate:e,endDate:y,error:D});break;default:this.emit("job:end",{instanceId:t.id,jobId:r.id,stderr:T,stdout:w,startDate:e,endDate:y})}t=await this._jobsRepository.saveInstance({...t,status:s,exitCode:n,stdErr:B,stdOut:N,endDate:y})}finally{let r={...await this._jobsRepository.getJobById(t.jobId)};r.timesRunned++;let o=!1,a=await this.getJobSchedule(r);s!=="succeeded"?r.consequentFailsCount+1>=r.maxConsequentFails&&r.maxConsequentFails>0?r.status="disabled":(r.consequentFailsCount+=1,o=!0):(r.successfullTimesRunned++,r.consequentFailsCount=0,a.hasNext()||(r.status="finished")),await this._jobsRepository.saveJob(r),o&&t.retryIndex<t.maxRetries&&await this.startJobInstance({...t,retryIndex:t.retryIndex+1})}}async scheduleJobTo(t,e,s){let n;try{let r=new Date,o=e.getTime()-r.getTime();o<0&&(o=0);let a=await this._jobsRepository.addInstance(t.id,{scheduledTo:e,status:"scheduled",timeout:t.timeout,index:s,retryIndex:0,maxRetries:t.maxRetries});return n=setTimeout(async()=>{let d=await this._jobsRepository.getJobById(t.id);if(!d||d.status!=="active"){a.status="canceled",await this._jobsRepository.saveInstance(a);return}await this.startJobInstance(a)},o),a}catch{return clearTimeout(n),null}}async checkForUpcomingJobs(){let t=await this._jobsRepository.getJobs();for(let e=0;e<t.length;e++){if(t[e].status!=="active")continue;let s=await this.getJobSchedule(t[e]);if(s.hasNext()){let n=await this._jobsRepository.getInstancesWithStatus(t[e].id,"scheduled");for(;s.hasNext(G);){let{date:r,index:o}=s.next();if(r<new Date)continue;if(t[e].noConcurrentRuns&&(await this._jobsRepository.getInstancesWithStatus(t[e].id,"running")).length>0)return;n.find(d=>d.index===o)||await this.scheduleJobTo(t[e],r,o)}}else t[e].status="finished",await this._jobsRepository.saveJob(t[e])}}async start(){if(this._status==="started")throw new Error("Scheduler is already started");this.status="started",this._checkTimer=setInterval(this.checkForUpcomingJobs.bind(this),Q)}stop(){if(this._status==="stopped")throw new Error("Scheduler is already stopped");clearInterval(this._checkTimer),this.status="stopped"}async jobExists(t){return await this._jobsRepository.getJobById(t)!==null}async removeJob(t){if(typeof t!="string"||!t)throw new Error("id is required");await this._jobsRepository.removeJob(t)}async addJob(t){let e=O.CreateJobRequestSchema.validate(t);if(!e.valid)throw new Error(`Invalid CreateJobRequest: ${e.errors?.join("; ")}`);let s=P(this._rootFolder,t.path);await z(s,X.constants.R_OK),this._jobProps.set(t.id,t.props),await this._jobsRepository.createJob({id:t.id,createdAt:new Date,schedule:t.schedule,timeout:t.timeout||tt,path:t.path,consequentFailsCount:0,timesRunned:0,successfullTimesRunned:0,maxConsequentFails:typeof t.maxConsequentFails=="number"?t.maxConsequentFails:et,maxRetries:typeof t.maxRetries=="number"?t.maxRetries:st,noConcurrentRuns:t.noConcurrentRuns||!1})}constructor(t){if(super(),typeof t.rootFolder!="string")throw new Error("rootFolder must be a string");typeof t.defaultTimeZone=="string"&&(this._defaultTimezone=t.defaultTimeZone),typeof t.persistRepository=="object"&&(this._jobsRepository=t.persistRepository),this._rootFolder=t.rootFolder}on(t,e){return super.on(t,e),this}};export{M as JobScheduler,J as ScheduleCalculator,O as Schemas};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/jobRepository.ts","../src/ScheduleCalculator.ts","../src/types.ts"],"sourcesContent":["import { EventEmitter } from 'events';\nimport fs from 'fs';\nimport { access as fsAccess } from 'fs/promises';\nimport { join as pathJoin } from 'path';\nimport { PassThrough, type Readable } from 'stream';\nimport { Worker } from 'worker_threads';\nimport { type IJobRepository, InMemoryJobRepository } from './jobRepository.js';\n\nimport { ScheduleCalculator } from './ScheduleCalculator.js';\nimport {\n type CreateJobRequest,\n type Job,\n type JobInstance,\n type JobInstanceStatus,\n type JobSchedulerProps,\n Schedule,\n type SchedulerStatus,\n Schemas\n} from './types.js';\n\nexport { Schedule as TaskSchedule, ScheduleCalculator, Schemas };\n\ntype WorkerResult = {\n status: JobInstanceStatus;\n exitCode: number;\n error?: Error;\n};\n\nconst MAX_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB\nconst CHECK_INTERVAL = 1000 * 10; // every 10 seconds\nconst SCHEDULE_JOB_SPAN = 1000 * 60; // 1 minute\nconst DEFAULT_JOB_TIMEOUT = 1000 * 20; // 20 seconds\nconst DEFAULT_MAX_CONSEQUENT_FAILS = 3;\nconst DEFAULT_MAX_RETRIES = 2;\n\ntype JobStartItem = {\n jobId: string;\n instanceId: number;\n stdout: Readable;\n stderr: Readable;\n startDate: Date;\n};\n\ntype JobEndItem = JobStartItem & {\n endDate: Date;\n};\n\ntype JobErrorItem = JobStartItem & {\n endDate: Date;\n error?: Error;\n};\n\ntype JobMessageItem = Omit<JobStartItem, 'stdout' | 'stderr'> & {\n value: any;\n};\n\ntype Events = {\n 'job:start': (job: JobStartItem) => any;\n 'job:end': (job: JobEndItem) => any;\n 'job:error': (job: JobErrorItem) => any;\n 'job:timeout': (job: JobErrorItem) => any;\n 'job:message': (msg: JobMessageItem) => any;\n};\n\ninterface IJobScheduler {\n on<T extends keyof Events>(name: T, callback: Events[T]): this;\n addJob(job: CreateJobRequest): Promise<void>;\n removeJob(id: string): Promise<void>;\n}\n\n/**\n * Job scheduler class that handles all the scheduling and running of jobs.\n */\nexport class JobScheduler extends EventEmitter implements IJobScheduler {\n protected _rootFolder: string;\n protected _status: SchedulerStatus = 'stopped';\n protected _defaultTimezone!: string;\n\n protected _checkTimer: ReturnType<typeof setTimeout> | undefined;\n\n protected _jobsRepository: IJobRepository = new InMemoryJobRepository();\n\n protected _jobProps: Map<string, any> = new Map<string, any>();\n\n /**\n * Status of the scheduler.\n */\n public get status() {\n return this._status;\n }\n\n protected set status(val: SchedulerStatus) {\n if (val === this._status) return;\n this._status = val;\n }\n\n private scheduleCalculatorCache = new Map<string, ScheduleCalculator>();\n\n /**\n * Get the schedule calculator for a job by its id.\n * @param {Job} job\n * @returns {Promise<ScheduleCalculator>}\n */\n protected async getJobSchedule(job: Job) {\n if (this.scheduleCalculatorCache.has(job.id)) {\n return this.scheduleCalculatorCache.get(job.id);\n }\n\n const schedule = {\n ...job.schedule\n };\n\n if (typeof job.firstInstanceEndedAt !== 'undefined') {\n schedule.startsOn = job.firstInstanceEndedAt;\n }\n\n if (typeof job.successfullTimesRunned === 'number') {\n schedule.skipFirst = job.successfullTimesRunned - 1;\n }\n\n const res = new ScheduleCalculator(schedule);\n this.scheduleCalculatorCache.set(job.id, res);\n\n return res;\n }\n\n protected runWorkerWithTimeout(file: string, props: any, timeout: number) {\n const worker = new Worker(file, {\n workerData: props,\n execArgv: ['--unhandled-rejections=strict'],\n stderr: true,\n stdout: true\n });\n const promise = new Promise<WorkerResult>(resolve => {\n let timedOut = false;\n let isFinished = false;\n let error: Error | undefined;\n\n const timeoutTimer = setTimeout(() => {\n if (isFinished) return;\n timedOut = true;\n worker.terminate();\n resolve({\n exitCode: 1,\n status: 'timedout'\n });\n }, timeout);\n\n worker.on('error', (e: Error) => {\n error = e;\n });\n\n worker.on('exit', (exitCode: number) => {\n if (isFinished) return;\n if (timedOut) return;\n clearTimeout(timeoutTimer);\n isFinished = true;\n resolve({\n status: exitCode === 0 ? 'succeeded' : 'errored',\n exitCode,\n error\n });\n });\n });\n\n return {\n promise,\n worker\n };\n }\n\n private readToEnd(source: Readable): Promise<Buffer> {\n return new Promise<Buffer>((res, rej) => {\n const chunks: Buffer[] = [];\n source.on('data', (chunk: Buffer) => chunks.push(chunk));\n source.on('end', () => res(Buffer.concat(chunks)));\n source.on('error', (err: Error) => rej(err));\n });\n }\n\n /**\n * Start a job instance. This will run the job and update the status of the instance.\n * @param {JobInstance} instance - The job instance to start.\n */\n protected async startJobInstance(instance: JobInstance): Promise<void> {\n const startDate = new Date();\n instance = await this._jobsRepository.saveInstance({\n ...instance,\n status: 'running',\n startDate\n });\n\n let status: JobInstanceStatus = 'errored',\n exitCode: number = 1;\n\n try {\n const job = await this._jobsRepository.getJobById(instance.jobId);\n\n const fileName = pathJoin(this._rootFolder, job.path);\n\n const props = this._jobProps.get(job.id);\n\n let finalProps = typeof props === 'function' ? props() : props;\n if (finalProps instanceof Promise) {\n finalProps = await finalProps;\n }\n\n instance = await this._jobsRepository.saveInstance({\n ...instance,\n status: 'running'\n });\n\n const { promise, worker } = this.runWorkerWithTimeout(\n fileName,\n finalProps,\n job.timeout\n );\n\n const stdOutPass = worker.stdout.pipe(\n new PassThrough({\n highWaterMark: MAX_BUFFER_SIZE\n })\n );\n const stdErrPass = worker.stderr.pipe(\n new PassThrough({\n highWaterMark: MAX_BUFFER_SIZE\n })\n );\n\n const stdOutForJobStart = worker.stdout.pipe(\n new PassThrough({\n highWaterMark: MAX_BUFFER_SIZE\n })\n );\n const stdErrForJobStart = worker.stderr.pipe(\n new PassThrough({\n highWaterMark: MAX_BUFFER_SIZE\n })\n );\n\n const stdOutForJobEnd = worker.stdout.pipe(\n new PassThrough({\n highWaterMark: MAX_BUFFER_SIZE\n })\n );\n const stdErrForJobEnd = worker.stderr.pipe(\n new PassThrough({\n highWaterMark: MAX_BUFFER_SIZE\n })\n );\n\n worker.on('message', (value: any) => {\n this.emit('job:message', {\n instanceId: instance.id,\n jobId: job.id,\n startDate,\n value\n });\n });\n\n this.emit('job:start', {\n instanceId: instance.id,\n jobId: job.id,\n stderr: stdErrForJobStart,\n stdout: stdOutForJobStart,\n startDate\n } as JobStartItem);\n\n const stdOutStr = (await this.readToEnd(stdOutPass)).toString();\n const stdErrStr = (await this.readToEnd(stdErrPass)).toString();\n\n const result = await promise;\n status = result.status;\n exitCode = result.exitCode;\n const { error } = result;\n\n const endDate = new Date();\n\n switch (status) {\n case 'errored':\n this.emit('job:error', {\n instanceId: instance.id,\n jobId: job.id,\n stderr: stdErrForJobEnd,\n stdout: stdOutForJobEnd,\n startDate,\n endDate,\n error\n } as JobErrorItem);\n break;\n case 'timedout':\n this.emit('job:timeout', {\n instanceId: instance.id,\n jobId: job.id,\n stderr: stdErrForJobEnd,\n stdout: stdOutForJobEnd,\n startDate,\n endDate,\n error\n } as JobErrorItem);\n break;\n default:\n this.emit('job:end', {\n instanceId: instance.id,\n jobId: job.id,\n stderr: stdErrForJobEnd,\n stdout: stdOutForJobEnd,\n startDate,\n endDate\n } as JobStartItem);\n }\n\n instance = await this._jobsRepository.saveInstance({\n ...instance,\n status,\n exitCode,\n stdErr: stdErrStr,\n stdOut: stdOutStr,\n endDate\n });\n } finally {\n const job = {\n ...(await this._jobsRepository.getJobById(instance.jobId))\n };\n\n job.timesRunned!++;\n\n let shouldRetry = false;\n\n const schedule = await this.getJobSchedule(job);\n\n if (status !== 'succeeded') {\n if (\n job.consequentFailsCount + 1 >= job.maxConsequentFails &&\n job.maxConsequentFails > 0\n ) {\n job.status = 'disabled';\n } else {\n job.consequentFailsCount += 1;\n shouldRetry = true;\n }\n } else {\n job.successfullTimesRunned!++;\n job.consequentFailsCount = 0;\n if (!schedule!.hasNext()) {\n job.status = 'finished';\n }\n }\n\n await this._jobsRepository.saveJob(job);\n\n if (shouldRetry && instance.retryIndex < instance.maxRetries) {\n await this.startJobInstance({\n ...instance,\n retryIndex: instance.retryIndex + 1\n });\n }\n }\n }\n\n protected async scheduleJobTo(\n job: Job,\n date: Date,\n index: number\n ): Promise<JobInstance | null> {\n let timer;\n\n try {\n const now = new Date();\n let interval = date.getTime() - now.getTime();\n if (interval < 0) {\n interval = 0;\n }\n\n const instance = await this._jobsRepository.addInstance(job.id, {\n scheduledTo: date,\n status: 'scheduled',\n timeout: job.timeout,\n index,\n retryIndex: 0,\n maxRetries: job.maxRetries\n });\n\n timer = setTimeout(async () => {\n const actualJob = await this._jobsRepository.getJobById(job.id);\n\n if (!actualJob || actualJob.status !== 'active') {\n instance.status = 'canceled';\n await this._jobsRepository.saveInstance(instance);\n return;\n }\n\n await this.startJobInstance(instance);\n }, interval);\n\n return instance;\n } catch (_e) {\n clearTimeout(timer);\n // console.log(e);\n // console.log('task failed!');\n return null;\n }\n }\n\n protected async checkForUpcomingJobs(): Promise<void> {\n const jobs = await this._jobsRepository.getJobs();\n for (let i = 0; i < jobs.length; i++) {\n if (jobs[i].status !== 'active') continue;\n\n const schedule = await this.getJobSchedule(jobs[i]);\n\n if (schedule!.hasNext()) {\n const scheduledInstances =\n await this._jobsRepository.getInstancesWithStatus(\n jobs[i].id,\n 'scheduled'\n );\n\n while (schedule!.hasNext(SCHEDULE_JOB_SPAN)) {\n const { date: nextRun, index } = schedule!.next();\n if (nextRun < new Date()) continue;\n\n if (jobs[i].noConcurrentRuns) {\n const runingInstances =\n await this._jobsRepository.getInstancesWithStatus(\n jobs[i].id,\n 'running'\n );\n\n if (runingInstances.length > 0) {\n return;\n }\n }\n\n const alreadyScheduled = scheduledInstances.find(\n i => i.index === index\n );\n if (alreadyScheduled) continue;\n\n await this.scheduleJobTo(jobs[i], nextRun, index);\n }\n } else {\n jobs[i].status = 'finished';\n await this._jobsRepository.saveJob(jobs[i]);\n }\n }\n }\n\n /**\n * Starts the scheduler (all jobs will be scheduled and executed when it's time).\n */\n public async start() {\n if (this._status === 'started') {\n throw new Error('Scheduler is already started');\n }\n\n this.status = 'started';\n\n this._checkTimer = setInterval(\n this.checkForUpcomingJobs.bind(this),\n CHECK_INTERVAL\n );\n\n // TODO: add logic\n }\n\n /**\n * Stops the scheduler (all jobs will be stopped and no new jobs will be scheduled).\n */\n public stop() {\n if (this._status === 'stopped') {\n throw new Error('Scheduler is already stopped');\n }\n\n clearInterval(this._checkTimer);\n\n this.status = 'stopped';\n // TODO: add logic\n }\n\n /**\n * Checks if job exists by id\n * @param {string} jobId - id of the job\n * @returns {Promise<boolean>} - true if job exists, false otherwise\n */\n public async jobExists(jobId: string): Promise<boolean> {\n return (await this._jobsRepository.getJobById(jobId)) !== null;\n }\n\n /**\n * Removes job by its id\n * @param jobId {string} - id of the job\n * @returns {Promise<void>}\n */\n public async removeJob(jobId: string): Promise<void> {\n if (typeof jobId !== 'string' || !jobId) {\n throw new Error('id is required');\n }\n\n await this._jobsRepository.removeJob(jobId);\n }\n\n /**\n * Adds job to the scheduler\n * @param job {CreateJobRequest} - job to add\n */\n public async addJob(job: CreateJobRequest) {\n const validationResult = Schemas.CreateJobRequestSchema.validate(job);\n if (!validationResult.valid) {\n throw new Error(\n `Invalid CreateJobRequest: ${validationResult.errors?.join(\n '; '\n )}`\n );\n }\n\n const path = pathJoin(this._rootFolder, job.path);\n await fsAccess(path, fs.constants.R_OK);\n\n this._jobProps.set(job.id, job.props);\n\n await this._jobsRepository.createJob({\n id: job.id,\n createdAt: new Date(),\n schedule: job.schedule,\n timeout: job.timeout || DEFAULT_JOB_TIMEOUT,\n path: job.path,\n consequentFailsCount: 0,\n timesRunned: 0,\n successfullTimesRunned: 0,\n maxConsequentFails:\n typeof job.maxConsequentFails === 'number'\n ? job.maxConsequentFails\n : DEFAULT_MAX_CONSEQUENT_FAILS,\n maxRetries:\n typeof job.maxRetries === 'number'\n ? job.maxRetries\n : DEFAULT_MAX_RETRIES,\n noConcurrentRuns: job.noConcurrentRuns || false\n });\n }\n\n /**\n *\n * @param props {JobSchedulerProps} - scheduler properties\n */\n constructor(props: JobSchedulerProps) {\n super();\n if (typeof props.rootFolder !== 'string') {\n throw new Error('rootFolder must be a string');\n }\n if (typeof props.defaultTimeZone === 'string') {\n this._defaultTimezone = props.defaultTimeZone;\n }\n\n if (typeof props.persistRepository === 'object') {\n this._jobsRepository = props.persistRepository;\n }\n\n this._rootFolder = props.rootFolder;\n }\n\n public on<T extends keyof Events>(name: T, callback: Events[T]): this {\n super.on(name, callback);\n return this;\n }\n}\n","import type {\n Job,\n JobInstance,\n JobInstanceStatus,\n JobStatus\n} from './types.js';\n\ntype AddJobRequest = Omit<Job, 'status'>;\ntype AddJobInstanceRequest = Omit<JobInstance, 'id' | 'jobId'>;\n\n/**\n * Persistence contract for jobs and their running instances.\n *\n * Implement this interface to store jobs in a database, file system, or\n * any other persistent backend. See {@link InMemoryJobRepository} for a\n * reference implementation.\n */\nexport interface IJobRepository {\n getJobs(): Promise<Job[]>;\n getJobById(jobId: string): Promise<Job>;\n createJob(item: AddJobRequest): Promise<Job>;\n removeJob(jobId: string): Promise<void>;\n\n saveJob(job: Job): Promise<Job>;\n\n getInstancesWithStatus(\n jobId: string,\n status: JobInstanceStatus\n ): Promise<JobInstance[]>;\n\n addInstance(\n jobId: string,\n instance: AddJobInstanceRequest\n ): Promise<JobInstance>;\n\n saveInstance(instance: JobInstance): Promise<JobInstance>;\n}\n\n/**\n * In-memory {@link IJobRepository} implementation.\n *\n * Useful for tests and development. All data is lost when the process exits.\n */\nexport class InMemoryJobRepository implements IJobRepository {\n private _jobs: Array<Job> = [];\n private _jobInstances: Array<JobInstance> = [];\n private _instanceId = 1;\n\n async getJobs(): Promise<Job[]> {\n return this._jobs;\n }\n\n async removeJob(jobId: string) {\n await this.getJobById(jobId);\n this._jobs = this._jobs.filter(j => j.id !== jobId);\n }\n\n async createJob(item: AddJobRequest): Promise<Job> {\n const job: Job = {\n ...item,\n status: 'active'\n };\n this._jobs.push(job);\n return job;\n }\n\n async getInstances(jobId: string): Promise<JobInstance[]> {\n return this._jobInstances.filter(ji => ji.jobId === jobId);\n }\n\n async addInstance(\n jobId: string,\n instance: AddJobInstanceRequest\n ): Promise<JobInstance> {\n const newInstance = {\n ...instance,\n id: this._instanceId++,\n jobId\n };\n\n this._jobInstances.push(newInstance);\n\n return newInstance;\n }\n\n async getJobById(jobId: string): Promise<Job> {\n const job = this._jobs.find(j => j.id === jobId);\n if (!job) throw new Error(`Job with id ${jobId} not found`);\n return job;\n }\n\n async setJobStatus(jobId: string, status: JobStatus): Promise<Job> {\n const job = await this.getJobById(jobId);\n job.status = status;\n return job;\n }\n\n async saveJob(job: Job): Promise<Job> {\n const index = this._jobs.findIndex(j => j.id === job.id);\n if (index !== -1) {\n this._jobs[index] = {\n ...job\n };\n return this._jobs[index];\n }\n\n const result = {\n ...job\n };\n\n this._jobs.push(result);\n return result;\n }\n\n async getInstancesWithStatus(\n jobId: string,\n status: JobInstanceStatus\n ): Promise<JobInstance[]> {\n return (await this.getInstances(jobId)).filter(\n i => i.status === status\n );\n }\n\n async getInstanceById(id: number): Promise<JobInstance> {\n return this._jobInstances.find(ji => ji.id === id)!;\n }\n\n async saveInstance(instance: JobInstance): Promise<JobInstance> {\n const oldIndex = this._jobInstances.findIndex(\n ji => ji.id === instance.id\n );\n if (oldIndex !== -1) {\n this._jobInstances[oldIndex] = {\n ...instance\n };\n return this._jobInstances[oldIndex];\n }\n\n const result = {\n ...instance,\n id: this._instanceId++\n };\n\n this._jobInstances.push(result);\n return result;\n }\n}\n","import type { Schedule } from './types.js';\n\nconst MS_IN_DAY = 1000 * 60 * 60 * 24;\nconst MS_IN_WEEK = MS_IN_DAY * 7;\n\nconst getDayOfWeek = (date: Date) => {\n const res = date.getUTCDay();\n if (res === 0) return 7;\n return res;\n};\n\nconst getNumberOfDaysInMonth = (date: Date) => {\n return new Date(\n Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0, 0, 0, 0, 0)\n ).getDate();\n};\n\n/**\n * Iterates over the dates defined by a {@link Schedule}.\n *\n * Given a schedule configuration (e.g. every 2 days, every week on Monday/Friday,\n * every month on the 15th) the calculator produces the sequence of `Date` objects\n * that match that schedule. Use {@link hasNext} / {@link next} to walk through\n * the sequence.\n *\n * @example\n * ```ts\n * const calc = new ScheduleCalculator({\n * every: 'day',\n * interval: 1,\n * hour: 9,\n * minute: 0,\n * startsOn: new Date('2025-01-01T00:00:00Z'),\n * maxOccurences: 5\n * });\n *\n * while (calc.hasNext()) {\n * const { date, index } = calc.next();\n * console.log(`Run #${index} at ${date.toISOString()}`);\n * }\n * ```\n */\nexport class ScheduleCalculator {\n #schedule: Schedule;\n #currentDate = new Date();\n #hour = 9;\n #minute = 0;\n #maxRepeat = -1;\n #repeatCount = 0;\n\n #hasNext = false;\n #next: Date | undefined;\n\n /**\n * @param schedule - The schedule definition to iterate over.\n * @throws If `schedule` is falsy.\n */\n constructor(schedule: Schedule) {\n if (!schedule) throw new Error('schedule is required');\n this.#schedule = { ...schedule };\n\n if (typeof schedule.startsOn !== 'undefined') {\n this.#currentDate = schedule.startsOn;\n } else {\n this.#schedule.startsOn = new Date();\n this.#currentDate = this.#schedule.startsOn;\n }\n\n if (schedule.every !== 'minute') {\n if (typeof schedule.hour === 'number') {\n this.#hour = schedule.hour;\n }\n\n if (typeof schedule.minute === 'number') {\n this.#minute = schedule.minute;\n }\n\n if (\n schedule.every === 'day' &&\n new Date(\n Date.UTC(\n this.#currentDate.getUTCFullYear(),\n this.#currentDate.getUTCMonth(),\n this.#currentDate.getUTCDate(),\n this.#hour,\n this.#minute,\n 0,\n 0\n )\n ).getTime() < this.#currentDate.getTime()\n ) {\n const date = new Date(\n Date.UTC(\n this.#currentDate.getUTCFullYear(),\n this.#currentDate.getUTCMonth(),\n this.#currentDate.getUTCDate() + 1,\n this.#hour,\n this.#minute,\n 0,\n 0\n )\n );\n this.#currentDate = date;\n }\n }\n\n if (typeof schedule.maxOccurences === 'number') {\n this.#maxRepeat = schedule.maxOccurences;\n }\n\n const next = this.#getNext();\n\n if (typeof next !== 'undefined') {\n this.#next = next;\n this.#hasNext = true;\n }\n\n let leftToSkip =\n typeof this.#schedule.skipFirst === 'number'\n ? this.#schedule.skipFirst\n : 0;\n\n while (leftToSkip-- > 0 && this.#hasNext) {\n this.next();\n }\n }\n\n #getNext(): Date | undefined {\n let candidate: Date | null = null;\n let dayOfWeek: number;\n\n switch (this.#schedule.every) {\n case 'minute':\n candidate =\n this.#repeatCount === 0\n ? (this.#schedule.startsOn as Date)\n : new Date(\n Date.UTC(\n this.#currentDate.getUTCFullYear(),\n this.#currentDate.getUTCMonth(),\n this.#currentDate.getUTCDate(),\n this.#currentDate.getUTCHours(),\n this.#currentDate.getUTCMinutes() +\n this.#schedule.interval,\n this.#currentDate.getUTCSeconds(),\n this.#currentDate.getUTCMilliseconds()\n )\n );\n break;\n case 'day':\n {\n const date = new Date(\n this.#currentDate.getTime() +\n MS_IN_DAY *\n (this.#repeatCount === 0\n ? 0\n : this.#schedule.interval - 1)\n );\n\n candidate = new Date(\n Date.UTC(\n date.getUTCFullYear(),\n date.getUTCMonth(),\n date.getUTCDate(),\n this.#hour,\n this.#minute,\n 0,\n 0\n )\n );\n }\n break;\n case 'week':\n {\n let date = this.#currentDate;\n\n do {\n let found = false;\n dayOfWeek = getDayOfWeek(date);\n if (Number.isNaN(dayOfWeek)) return;\n for (let i = 0; i < 7 - dayOfWeek + 1; i++) {\n date = new Date(\n date.getTime() + (i === 0 ? 0 : MS_IN_DAY)\n );\n if (\n this.#schedule.endsOn &&\n date > this.#schedule.endsOn\n ) {\n return;\n }\n if (\n this.#schedule.dayOfWeek.includes(\n getDayOfWeek(date)\n )\n ) {\n const dateWithTime = new Date(\n Date.UTC(\n date.getUTCFullYear(),\n date.getUTCMonth(),\n date.getUTCDate(),\n this.#hour,\n this.#minute,\n 0,\n 0\n )\n );\n if (\n dateWithTime >\n (this.#schedule.startsOn as Date)\n ) {\n candidate = dateWithTime;\n found = true;\n break;\n }\n }\n }\n\n if (found) break;\n\n date = new Date(\n date.getTime() +\n MS_IN_DAY +\n (this.#repeatCount === 0\n ? 0\n : (this.#schedule.interval - 1) *\n MS_IN_WEEK)\n );\n } while (\n (this.#schedule.endsOn &&\n date <= this.#schedule.endsOn) ||\n !this.#schedule.endsOn\n );\n }\n break;\n case 'month':\n {\n let dateTime;\n const cDate = this.#currentDate;\n let iteration = 0;\n do {\n const date =\n this.#schedule.day === 'last'\n ? getNumberOfDaysInMonth(\n new Date(\n Date.UTC(\n cDate.getUTCFullYear(),\n cDate.getUTCMonth() +\n iteration *\n (this.#repeatCount === 0\n ? 1\n : this.#schedule\n .interval),\n 1,\n 0,\n 0,\n 0,\n 0\n )\n )\n )\n : (this.#schedule.day as number);\n dateTime = new Date(\n Date.UTC(\n cDate.getUTCFullYear(),\n cDate.getUTCMonth() +\n iteration *\n (this.#repeatCount === 0\n ? 1\n : this.#schedule.interval),\n date,\n this.#hour,\n this.#minute,\n 0,\n 0\n )\n );\n iteration++;\n } while (\n dateTime < (this.#schedule.startsOn as Date) ||\n dateTime <= this.#currentDate\n );\n candidate = dateTime;\n }\n break;\n case 'year':\n {\n let dateTime;\n const cDate = this.#currentDate;\n let iteration = 0;\n do {\n const date =\n this.#schedule.day === 'last'\n ? getNumberOfDaysInMonth(\n new Date(\n Date.UTC(\n cDate.getUTCFullYear() +\n iteration *\n (this.#repeatCount === 0\n ? 1\n : this.#schedule\n .interval),\n this.#schedule.month - 1,\n 1,\n 0,\n 0,\n 0,\n 0\n )\n )\n )\n : (this.#schedule.day as number);\n dateTime = new Date(\n Date.UTC(\n cDate.getUTCFullYear() +\n iteration *\n (this.#repeatCount === 0\n ? 1\n : this.#schedule.interval),\n this.#schedule.month - 1,\n date,\n this.#hour,\n this.#minute,\n 0,\n 0\n )\n );\n iteration++;\n } while (\n dateTime < (this.#schedule.startsOn as Date) ||\n dateTime <= this.#currentDate\n );\n candidate = dateTime;\n }\n break;\n default:\n throw new Error('unknown schedule type');\n }\n\n if (!candidate) return;\n\n if (\n typeof this.#schedule.endsOn !== 'undefined' &&\n candidate > this.#schedule.endsOn\n ) {\n return;\n }\n\n return candidate;\n }\n\n /**\n * Returns `true` when the schedule has at least one more date.\n *\n * @param span - Optional millisecond window. When provided the method\n * returns `true` only if the next date falls within `span` ms from now.\n */\n public hasNext(span?: number): boolean {\n if (!this.#hasNext) {\n return false;\n }\n\n if (typeof span !== 'number') return this.#hasNext;\n\n if (!this.#next) return false;\n return this.#next.getTime() - Date.now() <= span;\n }\n\n /**\n * Advances to the next scheduled date and returns it together with\n * its 1-based index in the sequence.\n *\n * @returns An object with the scheduled `date` and its `index`.\n * @throws If the schedule has no more dates ({@link hasNext} is `false`).\n */\n public next(): {\n date: Date;\n index: number;\n } {\n if (!this.#hasNext) throw new Error('schedule is over');\n\n const result = this.#next as Date;\n\n this.#currentDate = new Date(\n result.getTime() +\n (['day', 'week'].includes(this.#schedule.every) ? MS_IN_DAY : 0)\n );\n\n this.#repeatCount++;\n const next = this.#getNext();\n\n this.#next = next as Date;\n this.#hasNext = typeof next !== 'undefined';\n\n if (this.#maxRepeat > 0 && this.#repeatCount >= this.#maxRepeat) {\n this.#next = undefined;\n this.#hasNext = false;\n }\n\n return {\n date: result,\n index: this.#repeatCount\n };\n }\n}\n","import {\n array,\n boolean,\n date,\n func,\n type InferType,\n number,\n object,\n string,\n union\n} from '@cleverbrush/schema';\n\nimport type { IJobRepository } from './jobRepository.js';\n\nconst ScheduleSchemaBase = object({\n /** Number of intervals (days, months, minutes or weeks)\n * between repeats. Interval type depends of `every` value */\n interval: number().min(1).max(356),\n /** Hour (0-23) */\n hour: number().min(0).max(23).optional(),\n /** Minute (0-59) */\n minute: number().min(0).max(59).optional(),\n /** Do not start earlier than this date */\n startsOn: date().acceptJsonString().optional(),\n /** Do not repeat after this date */\n endsOn: date().acceptJsonString().optional(),\n /** Max number of repeats (min 1) */\n maxOccurences: number().min(1).optional(),\n /** Skip this number of repeats. Min value is 1. */\n skipFirst: number().min(1).optional()\n}).addValidator(val => {\n if (\n 'endsOn' in val &&\n 'maxOccurences' in val &&\n typeof val.endsOn !== 'undefined'\n ) {\n return {\n valid: false,\n errors: [{ message: 'either endsOn or maxOccurences is required' }]\n };\n }\n return { valid: true };\n});\n\nconst ScheduleMinuteSchema = ScheduleSchemaBase.omit('hour')\n .omit('minute')\n .addProps({\n /** Repeat every minute */\n every: string('minute')\n });\n\nconst ScheduleDaySchema = ScheduleSchemaBase.addProps({\n /** Repeat every day */\n every: string('day')\n});\n\nconst ScheduleWeekSchema = ScheduleSchemaBase.addProps({\n /** Repeat every week */\n every: string('week'),\n /** Days of week for schedule */\n dayOfWeek: array()\n .of(number().min(1).max(7))\n .minLength(1)\n .maxLength(7)\n .addValidator(val => {\n const map: Record<number, boolean> = {};\n for (let i = 0; i < val.length; i++) {\n if (map[val[i]]) {\n return {\n valid: false,\n errors: [{ message: 'no duplicates allowed' }]\n };\n }\n map[val[i]] = true;\n }\n return {\n valid: true\n };\n })\n});\n\nconst ScheduleMonthSchema = ScheduleSchemaBase.addProps({\n /** Repeat every month */\n every: string('month'),\n /** Day - 'last' or number from 1 to 28 */\n day: union(string('last')).or(number().min(1).max(28))\n});\n\nconst ScheduleYearSchema = ScheduleSchemaBase.addProps({\n /** Repeat every year */\n every: string('year'),\n /** Day - 'last' or number from 1 to 28 */\n day: union(string('last')).or(number().min(1).max(28)),\n /** Month - number from 1 to 12 */\n month: number().min(1).max(12)\n});\n\nconst ScheduleSchema = union(ScheduleMinuteSchema)\n .or(ScheduleDaySchema)\n .or(ScheduleWeekSchema)\n .or(ScheduleMonthSchema)\n .or(ScheduleYearSchema);\n\nconst CreateJobRequestSchema = object({\n /** Id of job, must be uniq */\n id: string(),\n /** Path to js file (relative to root folder) */\n path: string().minLength(1),\n /** Job's schedule */\n schedule: ScheduleSchema,\n /** Timeout for job (in milliseconds) */\n timeout: number().min(0).optional(),\n /** Arbitrary props for job (can be a callback returning props or Promise<props>) */\n props: union(object().acceptUnknownProps()).or(func()).optional(),\n /** Job will be considered as disabled when more than that count of runs fails consequently\n * unlimited if negative\n */\n maxConsequentFails: number().optional(),\n /**\n * Job will be retried right away this times. Job will be retried on next schedule run if this number is exceeded.\n */\n maxRetries: number().optional().min(1),\n /**\n * If true, job will not be runned if previous run is not finished yet.\n */\n noConcurrentRuns: boolean().optional()\n});\n\n/**\n * Schedule for job. Can be one of:\n * - every N minutes\n * - every N days\n * - every N weeks\n * - every N months\n * - every N years\n * - every N days of week\n * - every N months on N day\n * - every N years on N day of N month\n * - every N years on last day of N month\n */\nexport type Schedule = InferType<typeof ScheduleSchema>;\n\n/**\n * Object used to create new job.\n */\nexport type CreateJobRequest = InferType<typeof CreateJobRequestSchema>;\n\nexport type Job = {\n /** Id of job */\n id: string;\n /** Job status */\n status: JobStatus;\n /** Job's schedule */\n schedule: Schedule;\n /** Path to the job's file (relative to the `rootFolder`) */\n path: string;\n /** Timeout for job (in milliseconds) */\n timeout: number;\n /** Date when job was created */\n createdAt: Date;\n /**\n * Date when job was started for the first time\n */\n startedAt?: Date;\n /**\n * Date when job was ended for the first time\n */\n firstInstanceEndedAt?: Date;\n /**\n * Number of times job was runned\n */\n timesRunned?: number;\n /**\n * Number of times job was runned successfully\n */\n successfullTimesRunned?: number;\n /**\n * Current number of consequent fails, resets to 0 when\n * job is runned successfully. If this number is greater than\n * `maxConsequentFails` job will be disabled.\n */\n consequentFailsCount: number;\n /**\n * Job will be considered as disabled when more than that count of runs fails consequently\n */\n maxConsequentFails: number;\n /**\n * Job will be retried right away this times. Job will be retried on\n * next schedule run if this number is exceeded.\n */\n maxRetries: number;\n /**\n * If true, job will not be runned if previous run is not finished yet.\n */\n noConcurrentRuns?: boolean;\n};\n\nexport type JobInstance = {\n /** Id of job instance */\n id: number;\n /** Id of job */\n jobId: string;\n /** Index of job instance in the schedule sequence */\n index: number;\n /** Status of the job instance */\n status: JobInstanceStatus;\n /** Timeout value for job */\n timeout: number;\n scheduledTo: Date;\n /** Date when job instance was started */\n startDate?: Date;\n /** Date when job instance was ended */\n endDate?: Date;\n /** Stdout of the job saved to string */\n stdOut?: string;\n /** Stderr of the job saved to string */\n stdErr?: string;\n /** Exit code of the job */\n exitCode?: number;\n /** Count of unsucessfull job retries in row*/\n retryIndex: number;\n /** Max number of retries */\n maxRetries: number;\n};\n\nexport type JobSchedulerProps = {\n /**\n * Path to the folder where job files are located.\n */\n rootFolder: string;\n /**\n * Timezone for scheduling. Default is 'UTC'.\n */\n defaultTimeZone?: string;\n /**\n * Repository for persisting jobs.\n * @experimental\n */\n persistRepository?: IJobRepository;\n};\n\n/** Lifecycle status of a job definition. */\nexport type JobStatus = 'active' | 'disabled' | 'finished';\n/** Runtime status of the scheduler itself. */\nexport type SchedulerStatus = 'started' | 'stopped';\n/** Status of a single job execution instance. */\nexport type JobInstanceStatus =\n | 'running'\n | 'errored'\n | 'succeeded'\n | 'scheduled'\n | 'timedout'\n | 'canceled';\n\n/**\n * Pre-built `@cleverbrush/schema` schemas used internally by the scheduler.\n * Exported for consumers who need to validate schedule/job payloads manually.\n */\nexport const Schemas = {\n ScheduleSchemaBase,\n ScheduleSchema,\n CreateJobRequestSchema,\n ScheduleMinuteSchema,\n ScheduleDaySchema,\n ScheduleWeekSchema,\n ScheduleMonthSchema,\n ScheduleYearSchema\n};\n"],"mappings":"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","names":["EventEmitter","fs","fsAccess","pathJoin","PassThrough","Worker","InMemoryJobRepository","jobId","j","item","job","ji","instance","newInstance","status","index","result","i","id","oldIndex","getDayOfWeek","date","res","getNumberOfDaysInMonth","ScheduleCalculator","#schedule","#currentDate","#hour","#minute","#maxRepeat","#repeatCount","#hasNext","#next","schedule","next","#getNext","leftToSkip","candidate","dayOfWeek","found","i","dateWithTime","dateTime","cDate","iteration","span","result","array","boolean","date","func","number","object","string","union","ScheduleSchemaBase","val","ScheduleMinuteSchema","ScheduleDaySchema","ScheduleWeekSchema","map","i","ScheduleMonthSchema","ScheduleYearSchema","ScheduleSchema","CreateJobRequestSchema","Schemas","MAX_BUFFER_SIZE","CHECK_INTERVAL","SCHEDULE_JOB_SPAN","DEFAULT_JOB_TIMEOUT","DEFAULT_MAX_CONSEQUENT_FAILS","DEFAULT_MAX_RETRIES","JobScheduler","EventEmitter","InMemoryJobRepository","val","job","schedule","res","ScheduleCalculator","file","props","timeout","worker","Worker","resolve","timedOut","isFinished","error","timeoutTimer","e","exitCode","source","rej","chunks","chunk","err","instance","startDate","status","fileName","pathJoin","finalProps","promise","stdOutPass","PassThrough","stdErrPass","stdOutForJobStart","stdErrForJobStart","stdOutForJobEnd","stdErrForJobEnd","value","stdOutStr","stdErrStr","result","endDate","shouldRetry","date","index","timer","now","interval","actualJob","jobs","i","scheduledInstances","nextRun","jobId","validationResult","Schemas","path","fsAccess","fs","name","callback"]}
@@ -0,0 +1,42 @@
1
+ import type { Job, JobInstance, JobInstanceStatus, JobStatus } from './types.js';
2
+ type AddJobRequest = Omit<Job, 'status'>;
3
+ type AddJobInstanceRequest = Omit<JobInstance, 'id' | 'jobId'>;
4
+ /**
5
+ * Persistence contract for jobs and their running instances.
6
+ *
7
+ * Implement this interface to store jobs in a database, file system, or
8
+ * any other persistent backend. See {@link InMemoryJobRepository} for a
9
+ * reference implementation.
10
+ */
11
+ export interface IJobRepository {
12
+ getJobs(): Promise<Job[]>;
13
+ getJobById(jobId: string): Promise<Job>;
14
+ createJob(item: AddJobRequest): Promise<Job>;
15
+ removeJob(jobId: string): Promise<void>;
16
+ saveJob(job: Job): Promise<Job>;
17
+ getInstancesWithStatus(jobId: string, status: JobInstanceStatus): Promise<JobInstance[]>;
18
+ addInstance(jobId: string, instance: AddJobInstanceRequest): Promise<JobInstance>;
19
+ saveInstance(instance: JobInstance): Promise<JobInstance>;
20
+ }
21
+ /**
22
+ * In-memory {@link IJobRepository} implementation.
23
+ *
24
+ * Useful for tests and development. All data is lost when the process exits.
25
+ */
26
+ export declare class InMemoryJobRepository implements IJobRepository {
27
+ private _jobs;
28
+ private _jobInstances;
29
+ private _instanceId;
30
+ getJobs(): Promise<Job[]>;
31
+ removeJob(jobId: string): Promise<void>;
32
+ createJob(item: AddJobRequest): Promise<Job>;
33
+ getInstances(jobId: string): Promise<JobInstance[]>;
34
+ addInstance(jobId: string, instance: AddJobInstanceRequest): Promise<JobInstance>;
35
+ getJobById(jobId: string): Promise<Job>;
36
+ setJobStatus(jobId: string, status: JobStatus): Promise<Job>;
37
+ saveJob(job: Job): Promise<Job>;
38
+ getInstancesWithStatus(jobId: string, status: JobInstanceStatus): Promise<JobInstance[]>;
39
+ getInstanceById(id: number): Promise<JobInstance>;
40
+ saveInstance(instance: JobInstance): Promise<JobInstance>;
41
+ }
42
+ export {};