@cleverbrush/scheduler 1.1.10 → 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/dist/index.js CHANGED
@@ -1,408 +1,2 @@
1
- import fs from 'fs';
2
- import { EventEmitter } from 'events';
3
- import { PassThrough } from 'stream';
4
- import { access as fsAccess } from 'fs/promises';
5
- import { join as pathJoin } from 'path';
6
- import { Worker } from 'worker_threads';
7
- import { Schemas } from './types.js';
8
- import { ScheduleCalculator } from './ScheduleCalculator.js';
9
- import { InMemoryJobRepository } from './jobRepository.js';
10
- export { ScheduleCalculator, Schemas };
11
- const MAX_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB
12
- const CHECK_INTERVAL = 1000 * 10; // every 10 seconds
13
- const SCHEDULE_JOB_SPAN = 1000 * 60; // 1 minute
14
- const DEFAULT_JOB_TIMEOUT = 1000 * 20; // 20 seconds
15
- const DEFAULT_MAX_CONSEQUENT_FAILS = 3;
16
- const DEFAULT_MAX_RETRIES = 2;
17
- /**
18
- * Job scheduler class that handles all the scheduling and running of jobs.
19
- */
20
- export class JobScheduler extends EventEmitter {
21
- _rootFolder;
22
- _status = 'stopped';
23
- _defaultTimezone;
24
- _checkTimer;
25
- _jobsRepository = new InMemoryJobRepository();
26
- _jobProps = new Map();
27
- /**
28
- * Status of the scheduler.
29
- */
30
- get status() {
31
- return this._status;
32
- }
33
- set status(val) {
34
- if (val === this._status)
35
- return;
36
- this._status = val;
37
- }
38
- scheduleCalculatorCache = new Map();
39
- /**
40
- * Get the schedule calculator for a job by its id.
41
- * @param {Job} job
42
- * @returns {Promise<ScheduleCalculator>}
43
- */
44
- async getJobSchedule(job) {
45
- if (this.scheduleCalculatorCache.has(job.id)) {
46
- return this.scheduleCalculatorCache.get(job.id);
47
- }
48
- const schedule = {
49
- ...job.schedule
50
- };
51
- if (typeof job.firstInstanceEndedAt !== 'undefined') {
52
- schedule.startsOn = job.firstInstanceEndedAt;
53
- }
54
- if (typeof job.successfullTimesRunned === 'number') {
55
- schedule.skipFirst = job.successfullTimesRunned - 1;
56
- }
57
- const res = new ScheduleCalculator(schedule);
58
- this.scheduleCalculatorCache.set(job.id, res);
59
- return res;
60
- }
61
- runWorkerWithTimeout(file, props, timeout) {
62
- const worker = new Worker(file, {
63
- workerData: props,
64
- execArgv: ['--unhandled-rejections=strict'],
65
- stderr: true,
66
- stdout: true
67
- });
68
- const promise = new Promise((resolve) => {
69
- let timedOut = false;
70
- let isFinished = false;
71
- let error;
72
- const timeoutTimer = setTimeout(() => {
73
- if (isFinished)
74
- return;
75
- timedOut = true;
76
- worker.terminate();
77
- resolve({
78
- exitCode: 1,
79
- status: 'timedout'
80
- });
81
- }, timeout);
82
- worker.on('error', (e) => {
83
- error = e;
84
- });
85
- worker.on('exit', (exitCode) => {
86
- if (isFinished)
87
- return;
88
- if (timedOut)
89
- return;
90
- clearTimeout(timeoutTimer);
91
- isFinished = true;
92
- resolve({
93
- status: exitCode === 0 ? 'succeeded' : 'errored',
94
- exitCode,
95
- error
96
- });
97
- });
98
- });
99
- return {
100
- promise,
101
- worker
102
- };
103
- }
104
- readToEnd(source) {
105
- return new Promise((res, rej) => {
106
- const chunks = [];
107
- source.on('data', (chunk) => chunks.push(chunk));
108
- source.on('end', () => res(Buffer.concat(chunks)));
109
- source.on('error', (err) => rej(err));
110
- });
111
- }
112
- /**
113
- * Start a job instance. This will run the job and update the status of the instance.
114
- * @param {JobInstance} instance - The job instance to start.
115
- */
116
- async startJobInstance(instance) {
117
- const startDate = new Date();
118
- instance = await this._jobsRepository.saveInstance({
119
- ...instance,
120
- status: 'running',
121
- startDate
122
- });
123
- let status, exitCode;
124
- try {
125
- const job = await this._jobsRepository.getJobById(instance.jobId);
126
- const fileName = pathJoin(this._rootFolder, job.path);
127
- const props = this._jobProps.get(job.id);
128
- let finalProps = typeof props === 'function' ? props() : props;
129
- if (finalProps instanceof Promise) {
130
- finalProps = await finalProps;
131
- }
132
- instance = await this._jobsRepository.saveInstance({
133
- ...instance,
134
- status: 'running'
135
- });
136
- const { promise, worker } = this.runWorkerWithTimeout(fileName, finalProps, job.timeout);
137
- const stdOutPass = worker.stdout.pipe(new PassThrough({
138
- highWaterMark: MAX_BUFFER_SIZE
139
- }));
140
- const stdErrPass = worker.stderr.pipe(new PassThrough({
141
- highWaterMark: MAX_BUFFER_SIZE
142
- }));
143
- const stdOutForJobStart = worker.stdout.pipe(new PassThrough({
144
- highWaterMark: MAX_BUFFER_SIZE
145
- }));
146
- const stdErrForJobStart = worker.stderr.pipe(new PassThrough({
147
- highWaterMark: MAX_BUFFER_SIZE
148
- }));
149
- const stdOutForJobEnd = worker.stdout.pipe(new PassThrough({
150
- highWaterMark: MAX_BUFFER_SIZE
151
- }));
152
- const stdErrForJobEnd = worker.stderr.pipe(new PassThrough({
153
- highWaterMark: MAX_BUFFER_SIZE
154
- }));
155
- worker.on('message', (value) => {
156
- this.emit('job:message', {
157
- instanceId: instance.id,
158
- jobId: job.id,
159
- startDate,
160
- value
161
- });
162
- });
163
- this.emit('job:start', {
164
- instanceId: instance.id,
165
- jobId: job.id,
166
- stderr: stdErrForJobStart,
167
- stdout: stdOutForJobStart,
168
- startDate
169
- });
170
- const stdOutStr = (await this.readToEnd(stdOutPass)).toString();
171
- const stdErrStr = (await this.readToEnd(stdErrPass)).toString();
172
- const result = await promise;
173
- status = result.status;
174
- exitCode = result.exitCode;
175
- const { error } = result;
176
- const endDate = new Date();
177
- switch (status) {
178
- case 'errored':
179
- this.emit('job:error', {
180
- instanceId: instance.id,
181
- jobId: job.id,
182
- stderr: stdErrForJobEnd,
183
- stdout: stdOutForJobEnd,
184
- startDate,
185
- endDate,
186
- error
187
- });
188
- break;
189
- case 'timedout':
190
- this.emit('job:timeout', {
191
- instanceId: instance.id,
192
- jobId: job.id,
193
- stderr: stdErrForJobEnd,
194
- stdout: stdOutForJobEnd,
195
- startDate,
196
- endDate,
197
- error
198
- });
199
- break;
200
- default:
201
- this.emit('job:end', {
202
- instanceId: instance.id,
203
- jobId: job.id,
204
- stderr: stdErrForJobEnd,
205
- stdout: stdOutForJobEnd,
206
- startDate,
207
- endDate
208
- });
209
- }
210
- instance = await this._jobsRepository.saveInstance({
211
- ...instance,
212
- status,
213
- exitCode,
214
- stdErr: stdErrStr,
215
- stdOut: stdOutStr,
216
- endDate
217
- });
218
- }
219
- finally {
220
- const job = {
221
- ...(await this._jobsRepository.getJobById(instance.jobId))
222
- };
223
- job.timesRunned++;
224
- let shouldRetry = false;
225
- const schedule = await this.getJobSchedule(job);
226
- if (status !== 'succeeded') {
227
- if (job.consequentFailsCount + 1 >= job.maxConsequentFails &&
228
- job.maxConsequentFails > 0) {
229
- job.status = 'disabled';
230
- }
231
- else {
232
- job.consequentFailsCount += 1;
233
- shouldRetry = true;
234
- }
235
- }
236
- else {
237
- job.successfullTimesRunned++;
238
- job.consequentFailsCount = 0;
239
- if (!schedule.hasNext()) {
240
- job.status = 'finished';
241
- }
242
- }
243
- await this._jobsRepository.saveJob(job);
244
- if (shouldRetry && instance.retryIndex < instance.maxRetries) {
245
- await this.startJobInstance({
246
- ...instance,
247
- retryIndex: instance.retryIndex + 1
248
- });
249
- }
250
- }
251
- }
252
- async scheduleJobTo(job, date, index) {
253
- let timer;
254
- try {
255
- const now = new Date();
256
- let interval = date.getTime() - now.getTime();
257
- if (interval < 0) {
258
- interval = 0;
259
- }
260
- const instance = await this._jobsRepository.addInstance(job.id, {
261
- scheduledTo: date,
262
- status: 'scheduled',
263
- timeout: job.timeout,
264
- index,
265
- retryIndex: 0,
266
- maxRetries: job.maxRetries
267
- });
268
- timer = setTimeout(async () => {
269
- const actualJob = await this._jobsRepository.getJobById(job.id);
270
- if (!actualJob || actualJob.status !== 'active') {
271
- instance.status = 'canceled';
272
- await this._jobsRepository.saveInstance(instance);
273
- return;
274
- }
275
- await this.startJobInstance(instance);
276
- }, interval);
277
- return instance;
278
- }
279
- catch (e) {
280
- clearTimeout(timer);
281
- // console.log(e);
282
- // console.log('task failed!');
283
- return null;
284
- }
285
- }
286
- async checkForUpcomingJobs() {
287
- const jobs = await this._jobsRepository.getJobs();
288
- for (let i = 0; i < jobs.length; i++) {
289
- if (jobs[i].status !== 'active')
290
- continue;
291
- const schedule = await this.getJobSchedule(jobs[i]);
292
- if (schedule.hasNext()) {
293
- const scheduledInstances = await this._jobsRepository.getInstancesWithStatus(jobs[i].id, 'scheduled');
294
- while (schedule.hasNext(SCHEDULE_JOB_SPAN)) {
295
- const { date: nextRun, index } = schedule.next();
296
- if (nextRun < new Date())
297
- continue;
298
- if (jobs[i].noConcurrentRuns) {
299
- const runingInstances = await this._jobsRepository.getInstancesWithStatus(jobs[i].id, 'running');
300
- if (runingInstances.length > 0) {
301
- return;
302
- }
303
- }
304
- const alreadyScheduled = scheduledInstances.find((i) => i.index === index);
305
- if (alreadyScheduled)
306
- continue;
307
- await this.scheduleJobTo(jobs[i], nextRun, index);
308
- }
309
- }
310
- else {
311
- jobs[i].status = 'finished';
312
- await this._jobsRepository.saveJob(jobs[i]);
313
- }
314
- }
315
- }
316
- /**
317
- * Starts the scheduler (all jobs will be scheduled and executed when it's time).
318
- */
319
- async start() {
320
- if (this._status === 'started') {
321
- throw new Error('Scheduler is already started');
322
- }
323
- this.status = 'started';
324
- this._checkTimer = setInterval(this.checkForUpcomingJobs.bind(this), CHECK_INTERVAL);
325
- // TODO: add logic
326
- }
327
- /**
328
- * Stops the scheduler (all jobs will be stopped and no new jobs will be scheduled).
329
- */
330
- stop() {
331
- if (this._status === 'stopped') {
332
- throw new Error('Scheduler is already stopped');
333
- }
334
- clearInterval(this._checkTimer);
335
- this.status = 'stopped';
336
- // TODO: add logic
337
- }
338
- /**
339
- * Checks if job exists by id
340
- * @param {string} jobId - id of the job
341
- * @returns {Promise<boolean>} - true if job exists, false otherwise
342
- */
343
- async jobExists(jobId) {
344
- return (await this._jobsRepository.getJobById(jobId)) !== null;
345
- }
346
- /**
347
- * Removes job by its id
348
- * @param jobId {string} - id of the job
349
- * @returns {Promise<void>}
350
- */
351
- async removeJob(jobId) {
352
- if (typeof jobId !== 'string' || !jobId) {
353
- throw new Error('id is required');
354
- }
355
- await this._jobsRepository.removeJob(jobId);
356
- }
357
- /**
358
- * Adds job to the scheduler
359
- * @param job {CreateJobRequest} - job to add
360
- */
361
- async addJob(job) {
362
- const validationResult = await Schemas.CreateJobRequestSchema.validate(job);
363
- if (!validationResult.valid) {
364
- throw new Error(`Invalid CreateJobRequest: ${validationResult.errors?.join('; ')}`);
365
- }
366
- const path = pathJoin(this._rootFolder, job.path);
367
- await fsAccess(path, fs.constants.R_OK);
368
- this._jobProps.set(job.id, job.props);
369
- await this._jobsRepository.createJob({
370
- id: job.id,
371
- createdAt: new Date(),
372
- schedule: job.schedule,
373
- timeout: job.timeout || DEFAULT_JOB_TIMEOUT,
374
- path: job.path,
375
- consequentFailsCount: 0,
376
- timesRunned: 0,
377
- successfullTimesRunned: 0,
378
- maxConsequentFails: typeof job.maxConsequentFails === 'number'
379
- ? job.maxConsequentFails
380
- : DEFAULT_MAX_CONSEQUENT_FAILS,
381
- maxRetries: typeof job.maxRetries === 'number'
382
- ? job.maxRetries
383
- : DEFAULT_MAX_RETRIES,
384
- noConcurrentRuns: job.noConcurrentRuns || false
385
- });
386
- }
387
- /**
388
- *
389
- * @param props {JobSchedulerProps} - scheduler properties
390
- */
391
- constructor(props) {
392
- super();
393
- if (typeof props.rootFolder !== 'string') {
394
- throw new Error('rootFolder must be a string');
395
- }
396
- if (typeof props.defaultTimeZone === 'string') {
397
- this._defaultTimezone = props.defaultTimeZone;
398
- }
399
- if (typeof props.persistRepository === 'object') {
400
- this._jobsRepository = props.persistRepository;
401
- }
402
- this._rootFolder = props.rootFolder;
403
- }
404
- on(name, callback) {
405
- super.on(name, callback);
406
- return this;
407
- }
408
- }
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"]}
@@ -1,6 +1,13 @@
1
- import { Job, JobInstance, JobInstanceStatus, JobStatus } from './types.js';
1
+ import type { Job, JobInstance, JobInstanceStatus, JobStatus } from './types.js';
2
2
  type AddJobRequest = Omit<Job, 'status'>;
3
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
+ */
4
11
  export interface IJobRepository {
5
12
  getJobs(): Promise<Job[]>;
6
13
  getJobById(jobId: string): Promise<Job>;
@@ -11,6 +18,11 @@ export interface IJobRepository {
11
18
  addInstance(jobId: string, instance: AddJobInstanceRequest): Promise<JobInstance>;
12
19
  saveInstance(instance: JobInstance): Promise<JobInstance>;
13
20
  }
21
+ /**
22
+ * In-memory {@link IJobRepository} implementation.
23
+ *
24
+ * Useful for tests and development. All data is lost when the process exits.
25
+ */
14
26
  export declare class InMemoryJobRepository implements IJobRepository {
15
27
  private _jobs;
16
28
  private _jobInstances;