@ramplex/clujo
Version:
Schedule functions on a cron-like schedule. Built in distributed locking to prevent overlapping executions in a clustered environment.
1 lines • 29.2 kB
Source Map (JSON)
{"version":3,"sources":["../src/clujo.ts","../src/_cron.ts","../src/scheduler.ts","../src/index.ts"],"sourcesContent":["/* --------------------------------------------------------------------------\n\n croner - MIT License - Hexagon <hexagon@56k.guru>\n ioredis - MIT License - Zihua Li\n redis-semaphore - MIT License - Alexander Mochalin\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 type { CronOptions } from \"croner\";\nimport type { Redis } from \"ioredis\";\nimport { type LockOptions, Mutex } from \"redis-semaphore\";\n\nimport { Cron } from \"./_cron\";\n\n/**\n * Represents a Clujo instance, which is a cron job that executes a runner with a trigger function.\n *\n * @template T - Type of the value returned by the runner's trigger function\n\n * @param input The input to the Clujo constructor.\n * @param input.id The unique identifier for the Clujo instance.\n * @param input.runner The runner object with a trigger function to execute. Can be any object that implements { trigger: () => T | Promise<T> }\n * @param input.cron The cron schedule for the Clujo instance.\n * @param input.cron.pattern The cron pattern to use for scheduling. If a Date object is provided, the runner will execute once at the specified time.\n * @param input.cron.options Optional options to use when creating the cron job.\n * @param input.redis The redis settings for distributed locking\n * @param input.redis.client The IORedis client instance\n * @param input.redis.lockOptions The redis-semaphore lock options for lock acquisition\n * @param input.runOnStartup If `true`, executes the runner immediately on start, independent of the cron schedule\n *\n * @throw An error if the Clujo ID, runner, or cron pattern is not provided.\n *\n * @example\n * const clujo = new Clujo({\n * id: 'my-clujo-instance',\n * runner: myWorkflowRunner,\n * cron: {\n * pattern: '0 0 * * *', // Run daily at midnight\n * options: { timezone: 'America/New_York' }\n * },\n * runOnStartup: false,\n * redis: { client: myRedisClient }\n * });\n */\nexport class Clujo<T> {\n readonly #id: string;\n readonly #cron: Cron;\n readonly #runner: IRunner<T>;\n readonly #redis?: { client: Redis; lockOptions?: LockOptions };\n readonly #enabled: boolean;\n readonly #logger?: IClujoLogger;\n\n #hasStarted = false;\n #runOnStartup = false;\n\n constructor({\n id,\n runner,\n cron,\n enabled,\n runOnStartup,\n redis,\n logger,\n }: {\n id: string;\n runner: IRunner<T>;\n cron: ({ pattern: string | Date } | { patterns: (string | Date)[] }) & {\n options?: CronOptions;\n };\n enabled?: boolean;\n runOnStartup?: boolean;\n redis?: { client: Redis; lockOptions?: LockOptions };\n logger?: IClujoLogger;\n }) {\n logger?.debug?.(`Initializing Clujo instance with ID: ${id}`);\n if (!id) {\n throw new Error(\"Clujo ID is required.\");\n }\n if (!runner) {\n throw new Error(\"runner is required\");\n }\n if (!runner.trigger || typeof runner.trigger !== \"function\") {\n throw new Error(\"runner must have a trigger function\");\n }\n if (!(\"pattern\" in cron || \"patterns\" in cron)) {\n throw new Error(\"Either cron.pattern or cron.patterns is required.\");\n }\n if (\"pattern\" in cron && !cron.pattern) {\n throw new Error(\"cron.pattern is required\");\n }\n if (\"patterns\" in cron && !cron.patterns) {\n throw new Error(\"cron.patterns is required\");\n }\n if (enabled && typeof enabled !== \"boolean\") {\n throw new Error(\"enabled must be a boolean\");\n }\n if (runOnStartup && typeof runOnStartup !== \"boolean\") {\n throw new Error(\"runOnStartup must be a boolean.\");\n }\n if (redis && !redis.client) {\n throw new Error(\"Redis client is required in redis input.\");\n }\n if (redis) {\n logger?.debug?.(`Redis configuration provided for Clujo ${id}`);\n }\n if (enabled === false) {\n logger?.log?.(`Clujo instance ${id} initialized in disabled state`);\n }\n if (runOnStartup) {\n logger?.debug?.(`Clujo ${id} configured to run on startup`);\n }\n this.#id = id;\n this.#runner = runner;\n this.#cron = new Cron(\"pattern\" in cron ? cron.pattern : cron.patterns, cron.options);\n this.#runOnStartup = Boolean(runOnStartup);\n // default to enabled\n this.#enabled = enabled ?? true;\n this.#redis = redis;\n this.#logger = logger;\n logger?.log?.(`Clujo instance ${id} successfully initialized`);\n }\n\n get id(): string {\n return this.#id;\n }\n\n /**\n * Starts the cron job, which will execute the task graph according to the cron schedule.\n * @throws An error if the Clujo has already started.\n */\n start(): void {\n this.#logger?.debug?.(`Attempting to start Clujo ${this.#id}`);\n if (this.#hasStarted) {\n this.#logger?.error?.(`Failed to start Clujo ${this.#id}: already started`);\n throw new Error(\"Cannot start a Clujo that has already started.\");\n }\n\n const handler = async () => {\n try {\n this.#logger?.debug?.(`Cron trigger received for Clujo ${this.#id}`);\n if (!this.#enabled) {\n this.#logger?.log?.(`Skipping execution - Clujo ${this.#id} is disabled`);\n return;\n }\n if (!this.#redis) {\n this.#logger?.debug?.(`Executing runner for Clujo ${this.#id} without distributed lock`);\n await this.#runner.trigger();\n this.#logger?.log?.(`Successfully completed runner execution for Clujo ${this.#id}`);\n } else {\n this.#logger?.debug?.(`Attempting to acquire distributed lock for Clujo ${this.#id}`);\n await using lock = await this.#tryAcquire(this.#redis.client, this.#redis.lockOptions);\n if (lock) {\n this.#logger?.debug?.(`Executing runner for Clujo ${this.#id} with distributed lock`);\n await this.#runner.trigger();\n this.#logger?.log?.(`Successfully completed runner execution for Clujo ${this.#id}`);\n } else {\n this.#logger?.log?.(`Skipping execution - Could not acquire lock for Clujo ${this.#id}`);\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.#logger?.error?.(`Failed to execute runner for Clujo ${this.#id}: ${message}`);\n }\n };\n this.#cron.start(handler);\n this.#hasStarted = true;\n this.#logger?.log?.(`Clujo ${this.#id} started successfully`);\n\n // we use the cron trigger here so that prevent overlapping is active by default\n // i.e., if no lock is used, and the trigger is executing, and the schedule time is reached, the scheduled execution will be skipped\n if (this.#runOnStartup) {\n void this.#cron.trigger();\n }\n }\n\n /**\n * Stops the cron job and prevents any further executions of the task graph.\n * If the task graph is currently executing, it will be allowed to finish for up to the specified timeout.\n *\n * @param timeout The maximum time to wait for the task graph to finish executing before stopping the cron.\n * @returns A promise that resolves when the cron has stopped.\n * @throws An error if the Clujo has not started.\n */\n async stop(timeout = 5000): Promise<void> {\n this.#logger?.debug?.(`Attempting to stop Clujo ${this.#id} with timeout ${timeout}ms`);\n if (!this.#hasStarted) {\n this.#logger?.error?.(`Failed to stop Clujo ${this.#id}: not started`);\n throw new Error(\"Cannot stop a Clujo that has not started.\");\n }\n try {\n await this.#cron.stop(timeout);\n this.#logger?.log?.(`Clujo ${this.#id} stopped successfully`);\n } catch (error) {\n this.#logger?.error?.(`Failed to stop Clujo ${this.#id}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Trigger an execution of the runner immediately, independent of the cron schedule.\n * In the event the cron is running, the runner will still execute.\n *\n * @returns The final context returned by the runner.\n */\n async trigger(): Promise<T> {\n // we do not trigger via the cron here so that we can make use of the result of the runner\n this.#logger?.debug?.(`Manual trigger initiated for Clujo ${this.#id}`);\n try {\n const result = await this.#runner.trigger();\n this.#logger?.log?.(`Manual trigger completed successfully for Clujo ${this.#id}`);\n return result;\n } catch (error) {\n this.#logger?.error?.(`Manual trigger failed for Clujo ${this.#id}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Tries to acquire a lock from redis-semaphore. If the lock is acquired, the lock will be released when the lock is disposed.\n *\n * @param redis The Redis client to use.\n * @param lockOptions The options to use when acquiring the lock.\n *\n * @returns An AsyncDisposable lock if it was acquired, otherwise null.\n */\n async #tryAcquire(redis: Redis, lockOptions: LockOptions | undefined): Promise<AsyncDisposableMutex | null> {\n this.#logger?.debug?.(`Attempting to acquire mutex for Clujo ${this.#id}`);\n const mutex = new Mutex(redis, this.#id, {\n acquireAttemptsLimit: 1,\n lockTimeout: 30000,\n refreshInterval: 24000,\n onLockLost: (lockLostError) => {\n this.#logger?.error?.(`Lock lost for Clujo ${this.#id}: ${lockLostError.message}`);\n throw lockLostError;\n },\n ...lockOptions,\n });\n\n try {\n const lock = await mutex.tryAcquire();\n if (!lock) {\n this.#logger?.debug?.(\n `Could not acquire mutex for Clujo ${this.#id} - another instance is likely running`,\n );\n return null;\n }\n this.#logger?.debug?.(`Successfully acquired mutex for Clujo ${this.#id}`);\n return {\n mutex,\n [Symbol.asyncDispose]: async () => {\n try {\n await mutex.release();\n this.#logger?.debug?.(`Successfully released mutex for Clujo ${this.#id}`);\n } catch (error) {\n this.#logger?.error?.(`Failed to release mutex for Clujo ${this.#id}: ${error}`);\n throw error;\n }\n },\n };\n } catch (error) {\n this.#logger?.error?.(`Failed to acquire mutex for Clujo ${this.#id}: ${error}`);\n throw error;\n }\n }\n}\n\ninterface AsyncDisposableMutex extends AsyncDisposable {\n mutex: Mutex;\n}\n\nexport interface IClujoLogger {\n log?: (message: string) => void;\n debug?: (message: string) => void;\n error?: (message: string) => void;\n}\n\nexport interface IRunner<T> {\n trigger: () => T | Promise<T>;\n}\n","/* --------------------------------------------------------------------------\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","/* --------------------------------------------------------------------------\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 type { Clujo } from \"./clujo\";\n\n/**\n * Scheduler class for managing and running Clujo jobs.\n * This class allows adding, starting, and stopping multiple Clujo jobs in a centralized manner.\n */\nexport class Scheduler {\n // biome-ignore lint/suspicious/noExplicitAny: want any here\n readonly #jobs: Clujo<any>[] = [];\n\n /**\n * Adds a Clujo job to the scheduler.\n * @param input - Object containing the job and optional completion handler.\n * @param input.job - The Clujo job to be added.\n * @param input.completionHandler - Optional function to invoke after the job completes.\n */\n\n // biome-ignore lint/suspicious/noExplicitAny: want any here\n addJob(job: Clujo<any>) {\n if (this.#jobs.some((addedJob) => addedJob.id === job.id)) {\n throw new Error(`Job with id ${job.id} is already added to the scheduler.`);\n }\n this.#jobs.push(job);\n }\n\n /**\n * Starts all added jobs in the scheduler.\n *\n * @param redis - Optional Redis instance to be passed to the jobs. If provided, enables distributed locking.\n */\n start() {\n for (const job of this.#jobs) {\n job.start();\n }\n }\n /**\n * Stops all running jobs in the scheduler.\n *\n * @param timeout - The maximum time (in milliseconds) to wait for jobs to stop. Defaults to 5000ms.\n * @returns A promise that resolves when all jobs have stopped or the timeout is reached.\n */\n async stop(timeout = 5000) {\n await Promise.all(this.#jobs.map((job) => job.stop(timeout)));\n }\n\n /**\n * Returns the list of jobs added to the scheduler.\n */\n\n // biome-ignore lint/suspicious/noExplicitAny: want any here\n get jobs(): Clujo<any>[] {\n return this.#jobs;\n }\n}\n","/* --------------------------------------------------------------------------\n\n Exports make use of the following third-party libraries:\n\n croner - MIT License - Hexagon <hexagon@56k.guru>\n ioredis - MIT License - Zihua Li\n redis-semaphore - MIT License - Alexander Mochalin\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 { Clujo, type IClujoLogger, type IRunner } from \"./clujo\";\nimport { Scheduler } from \"./scheduler\";\n\nexport { Clujo, Scheduler };\nexport type { IClujoLogger, IRunner };\nexport default {\n Clujo,\n Scheduler,\n};\n"],"mappings":"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","names":[]}