@jayarjo/scheduler
Advanced tools
| import { Scheduler } from "./Scheduler"; | ||
| import { SchedulerStats } from "./SchedulerStats"; | ||
| export * from './Task'; | ||
| export * from './Scheduler'; | ||
| export * from './SchedulerStats'; | ||
| export declare const scheduler: Scheduler; | ||
| export declare const schedulerStats: SchedulerStats; |
| import { Task, TaskSchema } from './'; | ||
| export declare class Poller { | ||
| private ratePerMin; | ||
| polls: Map<string, Task>; | ||
| floodingPolls: Map<string, boolean>; | ||
| get isStarted(): boolean; | ||
| get spareRate(): number; | ||
| get spareInterval(): number; | ||
| get intervalForFloodingPoll(): number; | ||
| get unscheduledPolls(): Task[]; | ||
| constructor(ratePerMin?: number); | ||
| addPoll(schema: TaskSchema): any; | ||
| addPolls(schemas: TaskSchema[]): any[]; | ||
| removePoll(poll: Task, reallocateFloodIntervals?: boolean): any; | ||
| removePoll(uid: string, reallocateFloodIntervals?: boolean): any; | ||
| isFlooding(uid: string): boolean; | ||
| isFlooding(poll: Task): boolean; | ||
| isScheduled(uid: string): boolean; | ||
| isScheduled(poll: Task): boolean; | ||
| ratePerMinFor(interval: any): number; | ||
| setIntervalInFloodingPolls(interval: any): void; | ||
| start(): any[]; | ||
| stop(): void; | ||
| restart(): void; | ||
| reset(): void; | ||
| destroy(): void; | ||
| } |
+122
| import { Task, scheduler, TimeUnits } from './'; | ||
| export class Poller { | ||
| constructor(ratePerMin = 2000) { | ||
| this.ratePerMin = ratePerMin; | ||
| this.polls = new Map(); | ||
| // registry of polls that do not have specific interval and have to accomodate | ||
| // to remaining quota | ||
| this.floodingPolls = new Map(); | ||
| } | ||
| get isStarted() { | ||
| return !!(this.polls.size && this.polls.size > this.unscheduledPolls.length); | ||
| } | ||
| get spareRate() { | ||
| let spareRate = this.ratePerMin; | ||
| this.polls.forEach((poll) => { | ||
| if (!this.isFlooding(poll)) { | ||
| spareRate -= this.ratePerMinFor(poll.interval); | ||
| } | ||
| }); | ||
| return spareRate; | ||
| } | ||
| get spareInterval() { | ||
| const spareRate = this.spareRate; | ||
| return Math[spareRate < 0 ? 'ceil' : 'floor'](TimeUnits.MINUTE / spareRate); | ||
| } | ||
| get intervalForFloodingPoll() { | ||
| // NOTE: observables might have made it cleaner here | ||
| const spareInterval = this.spareInterval; | ||
| return spareInterval < 0 || !this.floodingPolls.size | ||
| ? spareInterval | ||
| : Math.floor(spareInterval * this.floodingPolls.size); | ||
| } | ||
| get unscheduledPolls() { | ||
| const unscheduledPolls = []; | ||
| this.polls.forEach((poll) => { | ||
| if (!this.isScheduled(poll)) { | ||
| unscheduledPolls.push(poll); | ||
| } | ||
| }); | ||
| return unscheduledPolls; | ||
| } | ||
| addPoll(schema) { | ||
| return this.addPolls([schema])[0]; | ||
| } | ||
| addPolls(schemas) { | ||
| schemas.forEach((schema) => { | ||
| const poll = new Task(schema); | ||
| this.polls.set(poll.uid, poll); | ||
| if (!schema.interval) { | ||
| this.floodingPolls.set(poll.uid, true); | ||
| } | ||
| }); | ||
| return this.start(); | ||
| } | ||
| removePoll(arg, reallocateFloodIntervals = true) { | ||
| const poll = arg instanceof Task ? arg : this.polls.get(arg); | ||
| if (poll) { | ||
| this.floodingPolls.delete(poll.uid); | ||
| this.polls.delete(poll.uid); | ||
| if (reallocateFloodIntervals) { | ||
| this.setIntervalInFloodingPolls(this.intervalForFloodingPoll); | ||
| } | ||
| } | ||
| } | ||
| isFlooding(arg) { | ||
| return this.floodingPolls.has(arg instanceof Task ? arg.uid : arg); | ||
| } | ||
| isScheduled(arg) { | ||
| return !!scheduler.getTask(arg instanceof Task ? arg.uid : arg); | ||
| } | ||
| ratePerMinFor(interval) { | ||
| return interval < 0 ? 0 : TimeUnits.MINUTE / interval; | ||
| } | ||
| setIntervalInFloodingPolls(interval) { | ||
| this.floodingPolls.forEach((_, uid) => { | ||
| this.polls.get(uid).interval = interval; | ||
| }); | ||
| } | ||
| start() { | ||
| const pollsToAdd = this.unscheduledPolls; | ||
| if (pollsToAdd.length) { | ||
| const interval = this.intervalForFloodingPoll; | ||
| // it is meant that dev will test if there's a time slot for a given poll prior to trying to add it, if dev | ||
| // ignores this prerequisite, then we abort everything and throw an exception directly into his face | ||
| if (interval < 0) { | ||
| // unregister polls | ||
| pollsToAdd.forEach((poll) => { | ||
| this.removePoll(poll, false); | ||
| }); | ||
| throw new Error(`poller quota of ${this.ratePerMin}/min exceeded by ${this.ratePerMinFor(Math.abs(interval))}/min`); | ||
| } | ||
| else { | ||
| const pollIds = []; | ||
| this.setIntervalInFloodingPolls(interval); | ||
| // submit new polls as tasks to scheduler | ||
| pollsToAdd.forEach((poll) => { | ||
| pollIds.push(scheduler.addTask(poll)); | ||
| }); | ||
| return pollIds; | ||
| } | ||
| } | ||
| } | ||
| stop() { | ||
| this.polls.forEach((poll) => { | ||
| scheduler.removeTask(poll.uid); | ||
| poll.runAt(0); // at of 0 will be set automatically when task is re-added | ||
| }); | ||
| } | ||
| restart() { | ||
| this.stop(); | ||
| this.start(); | ||
| } | ||
| reset() { | ||
| this.stop(); | ||
| this.polls.clear(); | ||
| this.floodingPolls.clear(); | ||
| } | ||
| // deprecated | ||
| destroy() { | ||
| this.reset(); | ||
| } | ||
| } |
| /// <reference types="node" /> | ||
| import EventEmitter from 'events'; | ||
| import { Task, TaskSchema, TimeUnits } from './Task'; | ||
| export declare class Scheduler extends EventEmitter { | ||
| private randomStart; | ||
| tasks: Map<string, Task>; | ||
| private timer; | ||
| constructor(randomStart?: boolean); | ||
| run(): void; | ||
| addTask(task: Task): string; | ||
| addTask(schema: TaskSchema): string; | ||
| addTask(fn: Function): string; | ||
| getTask(uid: string): Task; | ||
| runIn(fn: Function, amount: number, unit?: TimeUnits, repeatTimesBeforeFail?: number): Promise<any>; | ||
| runOnceIn(fn: Function, amount: number, unit?: TimeUnits): Promise<any>; | ||
| runEvery(fn: Function, interval: number, unit?: TimeUnits, repeatTimesBeforeFail?: any): string; | ||
| removeTask(uid: string): void; | ||
| removeTask(task: Task): void; | ||
| clear(): void; | ||
| destroy(): void; | ||
| private runIfDue; | ||
| private runTask; | ||
| } |
| import { Scheduler } from './Scheduler'; | ||
| export declare class SchedulerStats { | ||
| idle: number; | ||
| running: number; | ||
| completed: number; | ||
| successfulRuns: number; | ||
| failedRuns: number; | ||
| get total(): number; | ||
| get totalRuns(): number; | ||
| constructor(s: Scheduler); | ||
| reset(): void; | ||
| } |
| export declare enum TaskState { | ||
| IDLE = "idle", | ||
| RUNNING = "running", | ||
| SUCCEEDED = "succeeded", | ||
| FAILED = "failed" | ||
| } | ||
| export declare enum TimeUnits { | ||
| MILLISECOND = 1, | ||
| SECOND = 1000, | ||
| MINUTE = 60000, | ||
| HOUR = 3600000, | ||
| DAY = 86400000, | ||
| MONTH = 2592000000, | ||
| YEAR = 31536000000 | ||
| } | ||
| export declare type TaskSchema = { | ||
| fn: Function; | ||
| args?: any[]; | ||
| at?: number; | ||
| interval?: number; | ||
| repeatTimesBeforeFail?: number; | ||
| onSuccess?: (result: any, task: Task) => void; | ||
| onFailure?: (error: Error, task: Task) => void; | ||
| }; | ||
| export declare class Task { | ||
| private schema; | ||
| private fails; | ||
| private _uid; | ||
| get uid(): string; | ||
| get at(): number; | ||
| set at(value: number); | ||
| get interval(): number; | ||
| set interval(value: number); | ||
| private _state; | ||
| get state(): TaskState; | ||
| get isIdle(): boolean; | ||
| get isComplete(): boolean; | ||
| get isDue(): boolean; | ||
| get isOneTime(): boolean; | ||
| constructor(fn: Function); | ||
| constructor(_schema: TaskSchema); | ||
| /** | ||
| * To differentiate between successful or failed run, fn should return a Promise. | ||
| * Otherwise every run will be considered successful. | ||
| */ | ||
| run(): Promise<any>; | ||
| repeatEvery(interval: number, unit?: TimeUnits): Task; | ||
| runAt(time: number): Task; | ||
| runAt(date: Date): Task; | ||
| toString(): string; | ||
| } |
| export declare const isIterable: (obj: any) => boolean; | ||
| export declare const isIterableIterator: (obj: any) => boolean; | ||
| export declare const guid: (prefix?: string) => string; | ||
| export declare const isPromise: (obj: any) => boolean; | ||
| export declare const random: (min: any, max: any) => any; |
+4
-4
@@ -30,3 +30,3 @@ import { guid, isPromise } from './Utils'; | ||
| onSuccess: () => { }, | ||
| onFailure: () => { } | ||
| onFailure: () => { }, | ||
| }; | ||
@@ -70,8 +70,8 @@ this.schema = Object.assign({}, defaults, typeof arg === 'function' ? { fn: arg } : arg); | ||
| run() { | ||
| const { fn, args = [], onSuccess, onFailure, repeatTimesBeforeFail } = this.schema; | ||
| const { fn, args = [], onSuccess, onFailure, repeatTimesBeforeFail, } = this.schema; | ||
| if (this._state !== TaskState.IDLE) { | ||
| return Promise.reject(new Error(`task ${this.uid} is either already ${this.state}!`)); | ||
| } | ||
| const handleCompletion = isOk => { | ||
| return result => { | ||
| const handleCompletion = (isOk) => { | ||
| return (result) => { | ||
| if (isOk) { | ||
@@ -78,0 +78,0 @@ if (this.isOneTime) { |
+0
-5
@@ -13,7 +13,2 @@ export const isIterable = obj => obj && typeof obj[Symbol.iterator] === 'function'; | ||
| })(); | ||
| export const invariant = (expr, errMsg) => { | ||
| if (expr) { | ||
| throw new Error(errMsg); | ||
| } | ||
| }; | ||
| export const isPromise = obj => obj && typeof obj.then === 'function'; | ||
@@ -20,0 +15,0 @@ export const random = (min, max) => { |
+2
-1
| { | ||
| "name": "@jayarjo/scheduler", | ||
| "version": "1.1.0", | ||
| "version": "1.1.1", | ||
| "description": "Schedule a task to execute after some period, regularly with a given interval, just given amount of times, or simply until it succeeds.", | ||
| "main": "lib/index.js", | ||
| "types": "lib/index.d.ts", | ||
| "files": [ | ||
@@ -7,0 +8,0 @@ "lib/" |
19165
74.04%14
100%536
82.31%