UNPKG

@ramplex/clujo

Version:

Schedule functions on a cron-like schedule. Built in distributed locking to prevent overlapping executions in a clustered environment.

1 lines 6.55 kB
{"version":3,"sources":["../src/_cron.ts"],"sourcesContent":["/* --------------------------------------------------------------------------\n\n croner - MIT License - Hexagon <hexagon@56k.guru>\n\n ---------------------------------------------------------------------------\n\n MIT License\n\n Copyright (c) 2024 Rami Pellumbi\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n-----------------------------------------------------------------------------*/\n\nimport { Cron as Croner, type CronOptions } from \"croner\";\n\nexport class Cron {\n #jobs: Croner[] | null = null;\n readonly #cronExpression: string | Date | (string | Date)[];\n readonly #cronOptions: CronOptions | undefined;\n\n #isRunning = false;\n\n constructor(cronExpression: string | Date | (string | Date)[], cronOptions?: CronOptions) {\n this.#cronExpression = cronExpression;\n // default to protect mode (prevent overlapping executions) on the same process in the single pattern case\n this.#cronOptions = { protect: true, ...cronOptions };\n }\n\n /**\n * Starts the cron job with the specified handler.\n *\n * @param handler A function to be executed when the cron job triggers.\n * @throws {Error} If attempting to start a job that has already been started.\n */\n start(handler: () => Promise<void> | void): void {\n if (this.#jobs) {\n throw new Error(\"Attempting to start an already started job\");\n }\n // if using multiple expressions, prevent all from overlapping\n const wrapHandler = async () => {\n if (this.#cronOptions?.protect && this.#isRunning) {\n return;\n }\n try {\n this.#isRunning = true;\n await handler();\n } finally {\n this.#isRunning = false;\n }\n };\n this.#jobs = Array.isArray(this.#cronExpression)\n ? this.#cronExpression.map((expression) => new Croner(expression, this.#cronOptions, wrapHandler))\n : [new Croner(this.#cronExpression, this.#cronOptions, wrapHandler)];\n }\n\n /**\n * Stops the cron job. If the job is currently running, it will wait for the job to finish before stopping it.\n * This can be safely invoked even if the job hasn't been started.\n *\n * @param timeout The maximum time (in ms) to wait for the job to finish before stopping it forcefully.\n * @returns A promise that resolves when the job has been stopped\n */\n stop(timeout: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const startTime = Date.now();\n const checkAndStop = () => {\n if (!this.#jobs) {\n resolve(); // resolve if job has cleared\n return;\n }\n\n if (this.#jobs.some((job) => job.isBusy())) {\n if (Date.now() - startTime > timeout) {\n for (const job of this.#jobs) {\n job.stop();\n }\n this.#jobs = null;\n resolve();\n return;\n }\n setTimeout(checkAndStop, 100);\n } else {\n for (const job of this.#jobs) {\n job.stop();\n }\n this.#jobs = null;\n resolve();\n return;\n }\n };\n\n checkAndStop();\n });\n }\n\n /**\n * Triggers the cron job to run immediately. A triggered execution will prevent the job from running at its scheduled time\n * unless `protect` is set to `false` in the cron options.\n *\n * @throws {Error} If attempting to trigger a job that is not running.\n */\n async trigger(): Promise<void> {\n if (!this.#jobs) {\n throw new Error(\"Attempting to trigger a job that is not running\");\n }\n await this.#jobs[0].trigger();\n }\n}\n"],"mappings":";AA6BA,SAAS,QAAQ,cAAgC;AAE1C,IAAM,OAAN,MAAW;AAAA,EACd,QAAyB;AAAA,EAChB;AAAA,EACA;AAAA,EAET,aAAa;AAAA,EAEb,YAAY,gBAAmD,aAA2B;AACtF,SAAK,kBAAkB;AAEvB,SAAK,eAAe,EAAE,SAAS,MAAM,GAAG,YAAY;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAA2C;AAC7C,QAAI,KAAK,OAAO;AACZ,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAChE;AAEA,UAAM,cAAc,YAAY;AAC5B,UAAI,KAAK,cAAc,WAAW,KAAK,YAAY;AAC/C;AAAA,MACJ;AACA,UAAI;AACA,aAAK,aAAa;AAClB,cAAM,QAAQ;AAAA,MAClB,UAAE;AACE,aAAK,aAAa;AAAA,MACtB;AAAA,IACJ;AACA,SAAK,QAAQ,MAAM,QAAQ,KAAK,eAAe,IACzC,KAAK,gBAAgB,IAAI,CAAC,eAAe,IAAI,OAAO,YAAY,KAAK,cAAc,WAAW,CAAC,IAC/F,CAAC,IAAI,OAAO,KAAK,iBAAiB,KAAK,cAAc,WAAW,CAAC;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,SAAgC;AACjC,WAAO,IAAI,QAAc,CAAC,YAAY;AAClC,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,eAAe,MAAM;AACvB,YAAI,CAAC,KAAK,OAAO;AACb,kBAAQ;AACR;AAAA,QACJ;AAEA,YAAI,KAAK,MAAM,KAAK,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG;AACxC,cAAI,KAAK,IAAI,IAAI,YAAY,SAAS;AAClC,uBAAW,OAAO,KAAK,OAAO;AAC1B,kBAAI,KAAK;AAAA,YACb;AACA,iBAAK,QAAQ;AACb,oBAAQ;AACR;AAAA,UACJ;AACA,qBAAW,cAAc,GAAG;AAAA,QAChC,OAAO;AACH,qBAAW,OAAO,KAAK,OAAO;AAC1B,gBAAI,KAAK;AAAA,UACb;AACA,eAAK,QAAQ;AACb,kBAAQ;AACR;AAAA,QACJ;AAAA,MACJ;AAEA,mBAAa;AAAA,IACjB,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAyB;AAC3B,QAAI,CAAC,KAAK,OAAO;AACb,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACrE;AACA,UAAM,KAAK,MAAM,CAAC,EAAE,QAAQ;AAAA,EAChC;AACJ;","names":[]}