@neofinancial/chrono 0.5.1 → 0.5.2
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/build/{index.js → index.cjs} +10 -31
- package/build/index.cjs.map +1 -0
- package/build/{index.d.ts → index.d.cts} +56 -87
- package/build/index.d.mts +55 -86
- package/build/index.mjs +9 -5
- package/build/index.mjs.map +1 -1
- package/package.json +7 -7
- package/build/index.js.map +0 -1
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
-
key = keys[i];
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
-
get: ((k) => from[k]).bind(null, key),
|
|
13
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
-
value: mod,
|
|
20
|
-
enumerable: true
|
|
21
|
-
}) : target, mod));
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
1
|
let node_events = require("node:events");
|
|
25
|
-
node_events = __toESM(node_events);
|
|
26
2
|
let node_timers_promises = require("node:timers/promises");
|
|
27
|
-
node_timers_promises = __toESM(node_timers_promises);
|
|
28
3
|
|
|
29
4
|
//#region src/events.ts
|
|
30
5
|
const ChronoEvents = {
|
|
@@ -122,7 +97,6 @@ const ProcessorEvents = {
|
|
|
122
97
|
TASK_COMPLETION_FAILURE: "taskCompletionFailure",
|
|
123
98
|
UNKNOWN_PROCESSING_ERROR: "unknownProcessingError"
|
|
124
99
|
};
|
|
125
|
-
ProcessorEvents.TASK_CLAIMED;
|
|
126
100
|
|
|
127
101
|
//#endregion
|
|
128
102
|
//#region src/processors/simple-processor.ts
|
|
@@ -150,7 +124,7 @@ var SimpleProcessor = class extends node_events.EventEmitter {
|
|
|
150
124
|
...DEFAULT_CONFIG,
|
|
151
125
|
...config
|
|
152
126
|
};
|
|
153
|
-
this.
|
|
127
|
+
this.validateConfiguration();
|
|
154
128
|
}
|
|
155
129
|
/**
|
|
156
130
|
* Validates that the task handler timeout is less than the claim stale timeout.
|
|
@@ -160,8 +134,9 @@ var SimpleProcessor = class extends node_events.EventEmitter {
|
|
|
160
134
|
*
|
|
161
135
|
* @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.
|
|
162
136
|
*/
|
|
163
|
-
|
|
137
|
+
validateConfiguration() {
|
|
164
138
|
if (this.config.taskHandlerTimeoutMs >= this.config.claimStaleTimeoutMs) throw new Error(`Task handler timeout (${this.config.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.config.claimStaleTimeoutMs}ms)`);
|
|
139
|
+
if (this.config.claimIntervalMs >= this.config.idleIntervalMs) throw new Error(`Claim interval (${this.config.claimIntervalMs}ms) must be less than the idle interval (${this.config.idleIntervalMs}ms)`);
|
|
165
140
|
}
|
|
166
141
|
/**
|
|
167
142
|
* Starts multiple concurrent process loops that claim and process tasks.
|
|
@@ -225,6 +200,7 @@ var SimpleProcessor = class extends node_events.EventEmitter {
|
|
|
225
200
|
* @param task The task to handle.
|
|
226
201
|
*/
|
|
227
202
|
async handleTask(task) {
|
|
203
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
228
204
|
try {
|
|
229
205
|
await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);
|
|
230
206
|
} catch (error) {
|
|
@@ -235,7 +211,8 @@ var SimpleProcessor = class extends node_events.EventEmitter {
|
|
|
235
211
|
const completedTask = await this.datastore.complete(task.id);
|
|
236
212
|
this.emit(ProcessorEvents.TASK_COMPLETED, {
|
|
237
213
|
task: completedTask,
|
|
238
|
-
completedAt: completedTask.completedAt || /* @__PURE__ */ new Date()
|
|
214
|
+
completedAt: completedTask.completedAt || /* @__PURE__ */ new Date(),
|
|
215
|
+
startedAt
|
|
239
216
|
});
|
|
240
217
|
} catch (error) {
|
|
241
218
|
this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {
|
|
@@ -272,7 +249,9 @@ var SimpleProcessor = class extends node_events.EventEmitter {
|
|
|
272
249
|
//#region src/processors/create-processor.ts
|
|
273
250
|
function createProcessor(input) {
|
|
274
251
|
const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);
|
|
275
|
-
|
|
252
|
+
const processorType = input.configuration?.type ?? "simple";
|
|
253
|
+
if (processorType === "simple") return new SimpleProcessor(input.datastore, input.kind, input.handler, backoffStrategy, input.configuration);
|
|
254
|
+
throw new Error(`Unknown processor type: ${processorType}`);
|
|
276
255
|
}
|
|
277
256
|
|
|
278
257
|
//#endregion
|
|
@@ -344,4 +323,4 @@ exports.Chrono = Chrono;
|
|
|
344
323
|
exports.ChronoEvents = ChronoEvents;
|
|
345
324
|
exports.ProcessorEvents = ProcessorEvents;
|
|
346
325
|
exports.TaskStatus = TaskStatus;
|
|
347
|
-
//# sourceMappingURL=index.
|
|
326
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["EventEmitter","EventEmitter"],"sources":["../src/events.ts","../src/backoff-strategy.ts","../src/utils/promise-utils.ts","../src/processors/events.ts","../src/processors/simple-processor.ts","../src/processors/create-processor.ts","../src/chrono.ts","../src/datastore.ts"],"sourcesContent":["export const ChronoEvents = {\n /** Chrono instance has started processors and begun polling tasks */\n STARTED: 'started',\n /** Chrono instance has failed to gracefully stop so shutdown has been aborted */\n STOP_ABORTED: 'stopAborted',\n} as const;\n\nexport type ChronoEvents = (typeof ChronoEvents)[keyof typeof ChronoEvents];\n\nexport type ChronoEventsMap = {\n [ChronoEvents.STARTED]: [{ startedAt: Date }];\n [ChronoEvents.STOP_ABORTED]: [{ timestamp: Date; error: unknown }];\n};\n","/** Input for calculating the next backoff delay. */\nexport type BackoffStrategyInput = {\n /** The number of retries already attempted (0 for the first retry). */\n retryAttempt: number;\n};\n\nexport type DelayMs = number;\n\n/**\n * A function that calculates the backoff delay in milliseconds.\n * @param input - Contains information like the current retry number.\n * @returns The delay duration in milliseconds.\n */\nexport type BackoffStrategy = (input: BackoffStrategyInput) => DelayMs;\n\n/**\n * Creates a strategy that provides no delay (immediate retry).\n */\nexport function createNoBackoffStrategy(): BackoffStrategy {\n return (_input: BackoffStrategyInput) => 0;\n}\n\nexport interface FixedBackoffStrategyConfig {\n /** The constant delay in milliseconds. */\n readonly delayMs: number;\n}\n/**\n * Creates a strategy that waits a fixed amount of time.\n */\nexport function createFixedBackoffStrategy(config: FixedBackoffStrategyConfig): BackoffStrategy {\n return (_input: BackoffStrategyInput) => config.delayMs;\n}\n\nexport interface LinearBackoffStrategyConfig {\n /** The base delay for all retires that will be incremented off of */\n readonly baseDelayMs?: number;\n /** The amount to increase the delay by for each subsequent retry in milliseconds. */\n readonly incrementMs: number;\n}\n\n/**\n * Creates a strategy where the delay increases linearly.\n * Delay = (incrementMs * retryAttempt)\n */\nexport function createLinearBackoffStrategy(config: LinearBackoffStrategyConfig): BackoffStrategy {\n const { incrementMs, baseDelayMs = 0 } = config;\n return (input: BackoffStrategyInput) => {\n return baseDelayMs + input.retryAttempt * incrementMs;\n };\n}\n\nexport interface ExponentialBackoffStrategyConfig {\n /** The base delay for the first retry (retryAttempt = 0) in milliseconds. */\n readonly baseDelayMs: number;\n /** The maximum delay in milliseconds. Defaults to Infinity. */\n readonly maxDelayMs?: number;\n /** Type of jitter to apply. Defaults to 'none'. */\n readonly jitter?: 'none' | 'full' | 'equal';\n}\n/**\n * Creates a strategy where the delay increases exponentially, potentially with jitter.\n * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)\n */\nexport function createExponentialBackoffStrategy(config: ExponentialBackoffStrategyConfig): BackoffStrategy {\n const { baseDelayMs, maxDelayMs = Number.POSITIVE_INFINITY, jitter = 'none' } = config;\n\n return (input: BackoffStrategyInput) => {\n const exponentialDelay = baseDelayMs * 2 ** input.retryAttempt;\n const cappedDelay = Math.min(exponentialDelay, maxDelayMs);\n\n switch (jitter) {\n case 'full':\n // Full Jitter: random_between(0, cappedDelay)\n return Math.floor(Math.random() * cappedDelay);\n case 'equal': {\n // Equal Jitter: (cappedDelay / 2) + random_between(0, cappedDelay / 2)\n const halfDelay = cappedDelay / 2;\n return halfDelay + Math.random() * halfDelay;\n }\n case 'none':\n // No Jitter\n return cappedDelay;\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = jitter;\n throw new Error('Unknown jitter type for exponential backoff strategy');\n }\n }\n };\n}\n\nexport type BackoffStrategyType = 'none' | 'fixed' | 'linear' | 'exponential';\n\nexport type BackoffStrategyOptions =\n | { type: 'none' }\n | ({ type: 'fixed' } & FixedBackoffStrategyConfig)\n | ({ type: 'linear' } & LinearBackoffStrategyConfig)\n | ({ type: 'exponential' } & ExponentialBackoffStrategyConfig);\n\nconst DEFAULT_BACKOFF_STRATEGY: BackoffStrategyOptions = {\n type: 'linear',\n incrementMs: 2000,\n} as const;\n\n/**\n * Factory function to create a backoff strategy based on type and configuration.\n * @param options - Configuration object including the strategy type.\n * @returns A BackoffStrategy function.\n */\nexport function backoffStrategyFactory(options: BackoffStrategyOptions = DEFAULT_BACKOFF_STRATEGY): BackoffStrategy {\n switch (options.type) {\n case 'none':\n return createNoBackoffStrategy();\n case 'fixed':\n // No need to destructure 'type', pass the rest\n return createFixedBackoffStrategy(options);\n case 'linear':\n return createLinearBackoffStrategy(options);\n case 'exponential':\n return createExponentialBackoffStrategy(options);\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = options;\n throw new Error('Unknown backoff strategy type');\n }\n }\n}\n","export function promiseWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('Promise timed out'));\n }, timeout);\n\n promise\n .then((result) => {\n clearTimeout(timer);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n","import type { Task, TaskMappingBase } from '..';\n\nexport const ProcessorEvents = {\n /** A task has been claimed by the running processor for handling */\n TASK_CLAIMED: 'taskClaimed',\n /** A task has completed processing and successfully marked as completed */\n TASK_COMPLETED: 'taskCompleted',\n /** A task has failed during processing and being scheduled for retry */\n TASK_RETRY_SCHEDULED: 'taskRetryScheduled',\n /** A task has been marked as FAILED due to process failures exceeding max retries */\n TASK_FAILED: 'taskFailed',\n /** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */\n TASK_COMPLETION_FAILURE: 'taskCompletionFailure',\n /** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */\n UNKNOWN_PROCESSING_ERROR: 'unknownProcessingError',\n} as const;\n\nexport type ProcessorEvents = (typeof ProcessorEvents)[keyof typeof ProcessorEvents];\n\nexport type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = {\n [ProcessorEvents.TASK_CLAIMED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; claimedAt: Date }];\n [ProcessorEvents.TASK_COMPLETED]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; completedAt: Date; startedAt: Date },\n ];\n [ProcessorEvents.TASK_RETRY_SCHEDULED]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; retryScheduledAt: Date; errorAt: Date },\n ];\n [ProcessorEvents.TASK_FAILED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date }];\n [ProcessorEvents.TASK_COMPLETION_FAILURE]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date },\n ];\n [ProcessorEvents.UNKNOWN_PROCESSING_ERROR]: [{ error: unknown; timestamp: Date }];\n};\n","import { EventEmitter } from 'node:events';\nimport { setTimeout } from 'node:timers/promises';\nimport type { BackoffStrategy } from '../backoff-strategy';\nimport type { TaskMappingBase } from '../chrono';\nimport type { Datastore, Task } from '../datastore';\nimport { promiseWithTimeout } from '../utils/promise-utils';\nimport { ProcessorEvents, type ProcessorEventsMap } from './events';\nimport type { Processor } from './processor';\n\nconst DEFAULT_CONFIG: SimpleProcessorConfiguration = {\n maxConcurrency: 1,\n claimIntervalMs: 50,\n idleIntervalMs: 5_000,\n claimStaleTimeoutMs: 10_000,\n taskHandlerTimeoutMs: 5_000,\n taskHandlerMaxRetries: 5,\n processLoopRetryIntervalMs: 20_000,\n};\n\nexport type SimpleProcessorConfiguration = {\n /** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */\n maxConcurrency: number;\n /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */\n claimIntervalMs: number;\n /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */\n claimStaleTimeoutMs: number;\n /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */\n idleIntervalMs: number;\n /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */\n taskHandlerTimeoutMs: number;\n /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */\n taskHandlerMaxRetries: number;\n /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */\n processLoopRetryIntervalMs: number;\n};\n\nconst InternalProcessorEvents = { PROCESSOR_LOOP_EXIT: 'processorLoopExit' } as const;\n\ntype InternalProcessorEventsMap = {\n [InternalProcessorEvents.PROCESSOR_LOOP_EXIT]: [];\n};\n\nexport class SimpleProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n >\n extends EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>\n implements Processor<TaskKind, TaskMapping>\n{\n private config: SimpleProcessorConfiguration;\n\n private exitChannels: EventEmitter<InternalProcessorEventsMap>[] = [];\n private stopRequested = false;\n\n constructor(\n private datastore: Datastore<TaskMapping, DatastoreOptions>,\n private taskKind: TaskKind,\n private handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>,\n private backOffStrategy: BackoffStrategy,\n config?: Partial<SimpleProcessorConfiguration>,\n ) {\n super();\n\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n this.validateConfiguration();\n }\n\n /**\n * Validates that the task handler timeout is less than the claim stale timeout.\n * Throws an error if the validation fails.\n * This ensures that the task handler has enough time to complete before the task is considered stale.\n * This is important to prevent tasks from being claimed again while they are still being processed.\n *\n * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.\n */\n private validateConfiguration() {\n if (this.config.taskHandlerTimeoutMs >= this.config.claimStaleTimeoutMs) {\n throw new Error(\n `Task handler timeout (${this.config.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.config.claimStaleTimeoutMs}ms)`,\n );\n }\n\n if (this.config.claimIntervalMs >= this.config.idleIntervalMs) {\n throw new Error(\n `Claim interval (${this.config.claimIntervalMs}ms) must be less than the idle interval (${this.config.idleIntervalMs}ms)`,\n );\n }\n }\n\n /**\n * Starts multiple concurrent process loops that claim and process tasks.\n * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.\n */\n async start(): Promise<void> {\n if (this.stopRequested || this.exitChannels.length > 0) {\n return;\n }\n\n for (let i = 0; i < this.config.maxConcurrency; i++) {\n const exitChannel = new EventEmitter<InternalProcessorEventsMap>();\n\n this.exitChannels.push(exitChannel);\n this.runProcessLoop(exitChannel);\n }\n }\n\n /**\n * Stops the processor by signaling all process loops to exit,\n * then waits for all process loops to finish before resolving.\n */\n async stop(): Promise<void> {\n const exitPromises = this.exitChannels.map(\n (channel) =>\n new Promise((resolve) => channel.once(InternalProcessorEvents.PROCESSOR_LOOP_EXIT, () => resolve(null))),\n );\n\n this.stopRequested = true;\n\n await Promise.all(exitPromises);\n }\n\n /**\n * The main loop that processes tasks.\n *\n * @param exitChannel The channel to signal when the loop exits.\n */\n private async runProcessLoop(exitChannel: EventEmitter<InternalProcessorEventsMap>): Promise<void> {\n while (!this.stopRequested) {\n try {\n const task = await this.datastore.claim({\n kind: this.taskKind,\n claimStaleTimeoutMs: this.config.claimStaleTimeoutMs,\n });\n\n // If no tasks are available, wait before trying again\n if (!task) {\n await setTimeout(this.config.idleIntervalMs);\n\n continue;\n }\n\n this.emit(ProcessorEvents.TASK_CLAIMED, { task, claimedAt: task.claimedAt || new Date() });\n\n // Process the task using the handler\n await this.handleTask(task);\n\n // Wait a bit before claiming the next task\n await setTimeout(this.config.claimIntervalMs);\n } catch (error) {\n this.emit(ProcessorEvents.UNKNOWN_PROCESSING_ERROR, { error, timestamp: new Date() });\n\n await setTimeout(this.config.processLoopRetryIntervalMs);\n }\n }\n\n exitChannel.emit(InternalProcessorEvents.PROCESSOR_LOOP_EXIT);\n }\n\n /**\n * Handles a task by calling the handler and marking it as complete or failed.\n *\n * Emits:\n * - `task.completed` when the task is successfully completed.\n * - `task.failed` when the task fails.\n * - `task.complete.failed` when the task fails to mark as completed.\n *\n * @param task The task to handle.\n */\n private async handleTask(task: Task<TaskKind, TaskMapping[TaskKind]>) {\n const startedAt = new Date();\n try {\n await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);\n } catch (error) {\n await this.handleTaskError(task, error as Error);\n\n return;\n }\n\n try {\n const completedTask = await this.datastore.complete<TaskKind>(task.id);\n\n this.emit(ProcessorEvents.TASK_COMPLETED, {\n task: completedTask,\n completedAt: completedTask.completedAt || new Date(),\n startedAt,\n });\n } catch (error) {\n this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {\n error: error,\n failedAt: new Date(),\n task,\n });\n }\n }\n\n private async handleTaskError(task: Task<TaskKind, TaskMapping[TaskKind]>, error: Error): Promise<void> {\n const failedAt = new Date();\n if (task.retryCount >= this.config.taskHandlerMaxRetries) {\n // Mark the task as failed\n await this.datastore.fail(task.id);\n this.emit(ProcessorEvents.TASK_FAILED, {\n task,\n error,\n failedAt,\n });\n\n return;\n }\n\n const delay = this.backOffStrategy({ retryAttempt: task.retryCount });\n const retryAt = new Date(Date.now() + delay);\n\n await this.datastore.retry(task.id, retryAt);\n this.emit(ProcessorEvents.TASK_RETRY_SCHEDULED, {\n task,\n error,\n errorAt: failedAt,\n retryScheduledAt: retryAt,\n });\n }\n}\n","import type { TaskMappingBase } from '..';\nimport { type BackoffStrategyOptions, backoffStrategyFactory } from '../backoff-strategy';\nimport type { Datastore, Task } from '../datastore';\nimport type { Processor } from './processor';\nimport { SimpleProcessor, type SimpleProcessorConfiguration } from './simple-processor';\n\n/**\n * Configuration for the processor. Default to simple processor.\n * @default { type: 'simple' } if no configuration is provided.\n */\nexport type ProcessorConfiguration = Partial<SimpleProcessorConfiguration> & { type?: 'simple' };\n\nexport type CreateProcessorInput<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n> = {\n kind: TaskKind;\n datastore: Datastore<TaskMapping, DatastoreOptions>;\n handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;\n configuration?: ProcessorConfiguration;\n backoffStrategyOptions?: BackoffStrategyOptions;\n};\n\nexport function createProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n>(input: CreateProcessorInput<TaskKind, TaskMapping, DatastoreOptions>): Processor<TaskKind, TaskMapping> {\n const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);\n\n const processorType = input.configuration?.type ?? 'simple';\n\n if (processorType === 'simple') {\n return new SimpleProcessor<TaskKind, TaskMapping, DatastoreOptions>(\n input.datastore,\n input.kind,\n input.handler,\n backoffStrategy,\n input.configuration,\n );\n }\n\n const _unreachable: never = processorType;\n\n throw new Error(`Unknown processor type: ${processorType}`);\n}\n","import { EventEmitter } from 'node:events';\n\nimport type { BackoffStrategyOptions } from './backoff-strategy';\nimport type { Datastore, ScheduleInput, Task } from './datastore';\nimport { ChronoEvents, type ChronoEventsMap } from './events';\nimport { createProcessor, type Processor } from './processors';\nimport type { ProcessorConfiguration } from './processors/create-processor';\nimport type { ProcessorEventsMap } from './processors/events';\nimport { promiseWithTimeout } from './utils/promise-utils';\n\nexport type TaskMappingBase = Record<string, unknown>;\n\nexport type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<\n TaskKind,\n TaskData,\n DatastoreOptions\n>;\n\nexport type RegisterTaskHandlerInput<TaskKind, TaskData> = {\n /** The type of task */\n kind: TaskKind;\n /** The handler function to process the task */\n handler: (task: Task<TaskKind, TaskData>) => Promise<void>;\n /** The options for the backoff strategy to use when the task handler fails */\n backoffStrategyOptions?: BackoffStrategyOptions;\n /** The configuration for the processor to use when processing the task */\n processorConfiguration?: ProcessorConfiguration;\n};\n\n/**\n * Response from registering a task handler.\n * @returns The processor instance that can be used to start and stop the processor.\n */\nexport type RegisterTaskHandlerResponse<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;\n\n/**\n * The main class for scheduling and processing tasks.\n * @param datastore - The datastore instance to use for storing and retrieving tasks.\n * @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.\n */\nexport class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {\n private readonly datastore: Datastore<TaskMapping, DatastoreOptions>;\n private readonly processors: Map<keyof TaskMapping, Processor<keyof TaskMapping, TaskMapping>> = new Map();\n\n readonly exitTimeoutMs = 60_000;\n\n constructor(datastore: Datastore<TaskMapping, DatastoreOptions>) {\n super();\n\n this.datastore = datastore;\n }\n\n public async start(): Promise<void> {\n for (const processor of this.processors.values()) {\n await processor.start();\n }\n\n this.emit(ChronoEvents.STARTED, { startedAt: new Date() });\n }\n\n public async stop(): Promise<void> {\n const stopPromises = Array.from(this.processors.values()).map((processor) => processor.stop());\n\n try {\n await promiseWithTimeout(Promise.all(stopPromises), this.exitTimeoutMs);\n } catch (error) {\n this.emit(ChronoEvents.STOP_ABORTED, { error, timestamp: new Date() });\n }\n }\n\n public async scheduleTask<TaskKind extends keyof TaskMapping>(\n input: ScheduleTaskInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const task = await this.datastore.schedule({\n when: input.when,\n kind: input.kind,\n data: input.data,\n datastoreOptions: input.datastoreOptions,\n });\n\n return task;\n }\n\n public async deleteTask<TaskKind extends keyof TaskMapping>(\n taskId: string,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const task = await this.datastore.delete<TaskKind>(taskId);\n\n return task;\n }\n\n public registerTaskHandler<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: RegisterTaskHandlerInput<TaskKind, TaskMapping[TaskKind]>,\n ): RegisterTaskHandlerResponse<TaskKind, TaskMapping> {\n if (this.processors.has(input.kind)) {\n throw new Error('Handler for task kind already exists');\n }\n\n const processor = createProcessor({\n kind: input.kind,\n datastore: this.datastore,\n handler: input.handler,\n backoffStrategyOptions: input.backoffStrategyOptions,\n configuration: input.processorConfiguration,\n });\n\n this.processors.set(input.kind, processor);\n\n return processor;\n }\n}\n","import type { TaskMappingBase } from './chrono';\n\nexport const TaskStatus = {\n PENDING: 'PENDING',\n CLAIMED: 'CLAIMED',\n COMPLETED: 'COMPLETED',\n FAILED: 'FAILED',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport type Task<TaskKind, TaskData> = {\n /** A unique identifier for the task */\n id: string;\n /** A human-readable name or type for the task */\n kind: TaskKind;\n /** The current status of the task */\n status: TaskStatus;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** The original scheduled date when the task was first intended to run */\n originalScheduleDate: Date;\n /** The current scheduled execution date, which may change if rescheduled */\n scheduledAt: Date;\n /** The date the task is mark 'claimed */\n claimedAt?: Date;\n /** The date the task is mark 'completed' */\n completedAt?: Date;\n /** The date when the task was last executed (if any) */\n lastExecutedAt?: Date;\n /** A counter to track the number of times the task has been retried */\n retryCount: number;\n};\n\nexport type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {\n /** The date and time when the task is scheduled to run */\n when: Date;\n /** The type of task */\n kind: TaskKind;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/\n datastoreOptions?: DatastoreOptions;\n};\n\nexport type ClaimTaskInput<TaskKind> = {\n kind: TaskKind;\n claimStaleTimeoutMs: number;\n};\n\nexport type DeleteByIdempotencyKeyInput<TaskKind> = {\n kind: TaskKind;\n idempotencyKey: string;\n};\n\nexport type DeleteOptions = {\n force?: boolean;\n};\n\nexport type DeleteInput<TaskKind> = DeleteByIdempotencyKeyInput<TaskKind> | string;\n\nexport interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {\n schedule<TaskKind extends keyof TaskMapping>(\n input: ScheduleInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n delete<TaskKind extends keyof TaskMapping>(\n taskId: string,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n delete<TaskKind extends keyof TaskMapping>(\n key: DeleteByIdempotencyKeyInput<TaskKind>,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n claim<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: ClaimTaskInput<TaskKind>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n retry<TaskKind extends keyof TaskMapping>(\n taskId: string,\n retryAt: Date,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
|
|
3
3
|
//#region src/backoff-strategy.d.ts
|
|
4
|
-
|
|
5
4
|
interface FixedBackoffStrategyConfig {
|
|
6
5
|
/** The constant delay in milliseconds. */
|
|
7
6
|
readonly delayMs: number;
|
|
@@ -18,16 +17,16 @@ interface ExponentialBackoffStrategyConfig {
|
|
|
18
17
|
/** The maximum delay in milliseconds. Defaults to Infinity. */
|
|
19
18
|
readonly maxDelayMs?: number;
|
|
20
19
|
/** Type of jitter to apply. Defaults to 'none'. */
|
|
21
|
-
readonly jitter?:
|
|
20
|
+
readonly jitter?: "none" | "full" | "equal";
|
|
22
21
|
}
|
|
23
22
|
type BackoffStrategyOptions = {
|
|
24
|
-
type:
|
|
23
|
+
type: "none";
|
|
25
24
|
} | ({
|
|
26
|
-
type:
|
|
25
|
+
type: "fixed";
|
|
27
26
|
} & FixedBackoffStrategyConfig) | ({
|
|
28
|
-
type:
|
|
27
|
+
type: "linear";
|
|
29
28
|
} & LinearBackoffStrategyConfig) | ({
|
|
30
|
-
type:
|
|
29
|
+
type: "exponential";
|
|
31
30
|
} & ExponentialBackoffStrategyConfig);
|
|
32
31
|
//#endregion
|
|
33
32
|
//#region src/datastore.d.ts
|
|
@@ -39,43 +38,25 @@ declare const TaskStatus: {
|
|
|
39
38
|
};
|
|
40
39
|
type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];
|
|
41
40
|
type Task<TaskKind, TaskData> = {
|
|
42
|
-
/** A unique identifier for the task */
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
/** The
|
|
49
|
-
|
|
50
|
-
/** The
|
|
51
|
-
|
|
52
|
-
/** A
|
|
53
|
-
idempotencyKey?: string;
|
|
54
|
-
/** The original scheduled date when the task was first intended to run */
|
|
55
|
-
originalScheduleDate: Date;
|
|
56
|
-
/** The current scheduled execution date, which may change if rescheduled */
|
|
57
|
-
scheduledAt: Date;
|
|
58
|
-
/** The date the task is mark 'claimed */
|
|
59
|
-
claimedAt?: Date;
|
|
60
|
-
/** The date the task is mark 'completed' */
|
|
61
|
-
completedAt?: Date;
|
|
62
|
-
/** The date when the task was last executed (if any) */
|
|
63
|
-
lastExecutedAt?: Date;
|
|
64
|
-
/** A counter to track the number of times the task has been retried */
|
|
41
|
+
/** A unique identifier for the task */id: string; /** A human-readable name or type for the task */
|
|
42
|
+
kind: TaskKind; /** The current status of the task */
|
|
43
|
+
status: TaskStatus; /** The payload or data associated with the task */
|
|
44
|
+
data: TaskData; /** The priority level of the task (lower numbers can indicate higher priority) */
|
|
45
|
+
priority?: number; /** A key used for idempotency to prevent duplicate processing */
|
|
46
|
+
idempotencyKey?: string; /** The original scheduled date when the task was first intended to run */
|
|
47
|
+
originalScheduleDate: Date; /** The current scheduled execution date, which may change if rescheduled */
|
|
48
|
+
scheduledAt: Date; /** The date the task is mark 'claimed */
|
|
49
|
+
claimedAt?: Date; /** The date the task is mark 'completed' */
|
|
50
|
+
completedAt?: Date; /** The date when the task was last executed (if any) */
|
|
51
|
+
lastExecutedAt?: Date; /** A counter to track the number of times the task has been retried */
|
|
65
52
|
retryCount: number;
|
|
66
53
|
};
|
|
67
54
|
type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {
|
|
68
|
-
/** The date and time when the task is scheduled to run */
|
|
69
|
-
|
|
70
|
-
/** The
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
data: TaskData;
|
|
74
|
-
/** The priority level of the task (lower numbers can indicate higher priority) */
|
|
75
|
-
priority?: number;
|
|
76
|
-
/** A key used for idempotency to prevent duplicate processing */
|
|
77
|
-
idempotencyKey?: string;
|
|
78
|
-
/** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/
|
|
55
|
+
/** The date and time when the task is scheduled to run */when: Date; /** The type of task */
|
|
56
|
+
kind: TaskKind; /** The payload or data associated with the task */
|
|
57
|
+
data: TaskData; /** The priority level of the task (lower numbers can indicate higher priority) */
|
|
58
|
+
priority?: number; /** A key used for idempotency to prevent duplicate processing */
|
|
59
|
+
idempotencyKey?: string; /** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/
|
|
79
60
|
datastoreOptions?: DatastoreOptions;
|
|
80
61
|
};
|
|
81
62
|
type ClaimTaskInput<TaskKind> = {
|
|
@@ -102,9 +83,7 @@ interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {
|
|
|
102
83
|
//#endregion
|
|
103
84
|
//#region src/events.d.ts
|
|
104
85
|
declare const ChronoEvents: {
|
|
105
|
-
/** Chrono instance has started processors and begun polling tasks */
|
|
106
|
-
readonly STARTED: "started";
|
|
107
|
-
/** Chrono instance has failed to gracefully stop so shutdown has been aborted */
|
|
86
|
+
/** Chrono instance has started processors and begun polling tasks */readonly STARTED: "started"; /** Chrono instance has failed to gracefully stop so shutdown has been aborted */
|
|
108
87
|
readonly STOP_ABORTED: "stopAborted";
|
|
109
88
|
};
|
|
110
89
|
type ChronoEvents = (typeof ChronoEvents)[keyof typeof ChronoEvents];
|
|
@@ -120,17 +99,11 @@ type ChronoEventsMap = {
|
|
|
120
99
|
//#endregion
|
|
121
100
|
//#region src/processors/events.d.ts
|
|
122
101
|
declare const ProcessorEvents: {
|
|
123
|
-
/** A task has been claimed by the running processor for handling */
|
|
124
|
-
readonly
|
|
125
|
-
/** A task has
|
|
126
|
-
readonly
|
|
127
|
-
/**
|
|
128
|
-
readonly TASK_RETRY_SCHEDULED: "taskRetryScheduled";
|
|
129
|
-
/** A task has been marked as FAILED due to process failures exceeding max retries */
|
|
130
|
-
readonly TASK_FAILED: "taskFailed";
|
|
131
|
-
/** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */
|
|
132
|
-
readonly TASK_COMPLETION_FAILURE: "taskCompletionFailure";
|
|
133
|
-
/** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */
|
|
102
|
+
/** A task has been claimed by the running processor for handling */readonly TASK_CLAIMED: "taskClaimed"; /** A task has completed processing and successfully marked as completed */
|
|
103
|
+
readonly TASK_COMPLETED: "taskCompleted"; /** A task has failed during processing and being scheduled for retry */
|
|
104
|
+
readonly TASK_RETRY_SCHEDULED: "taskRetryScheduled"; /** A task has been marked as FAILED due to process failures exceeding max retries */
|
|
105
|
+
readonly TASK_FAILED: "taskFailed"; /** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */
|
|
106
|
+
readonly TASK_COMPLETION_FAILURE: "taskCompletionFailure"; /** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */
|
|
134
107
|
readonly UNKNOWN_PROCESSING_ERROR: "unknownProcessingError";
|
|
135
108
|
};
|
|
136
109
|
type ProcessorEvents = (typeof ProcessorEvents)[keyof typeof ProcessorEvents];
|
|
@@ -142,6 +115,7 @@ type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends
|
|
|
142
115
|
[ProcessorEvents.TASK_COMPLETED]: [{
|
|
143
116
|
task: Task<TaskKind, TaskMapping[TaskKind]>;
|
|
144
117
|
completedAt: Date;
|
|
118
|
+
startedAt: Date;
|
|
145
119
|
}];
|
|
146
120
|
[ProcessorEvents.TASK_RETRY_SCHEDULED]: [{
|
|
147
121
|
task: Task<TaskKind, TaskMapping[TaskKind]>;
|
|
@@ -165,54 +139,49 @@ type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends
|
|
|
165
139
|
}];
|
|
166
140
|
};
|
|
167
141
|
//#endregion
|
|
142
|
+
//#region src/processors/simple-processor.d.ts
|
|
143
|
+
type SimpleProcessorConfiguration = {
|
|
144
|
+
/** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */maxConcurrency: number; /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */
|
|
145
|
+
claimIntervalMs: number; /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */
|
|
146
|
+
claimStaleTimeoutMs: number; /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */
|
|
147
|
+
idleIntervalMs: number; /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */
|
|
148
|
+
taskHandlerTimeoutMs: number; /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */
|
|
149
|
+
taskHandlerMaxRetries: number; /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */
|
|
150
|
+
processLoopRetryIntervalMs: number;
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
168
153
|
//#region src/processors/create-processor.d.ts
|
|
169
154
|
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */
|
|
176
|
-
claimIntervalMs?: number;
|
|
177
|
-
/** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */
|
|
178
|
-
idleIntervalMs?: number;
|
|
179
|
-
/** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */
|
|
180
|
-
claimStaleTimeoutMs?: number;
|
|
181
|
-
/** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */
|
|
182
|
-
taskHandlerTimeoutMs?: number;
|
|
183
|
-
/** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */
|
|
184
|
-
taskHandlerMaxRetries?: number;
|
|
185
|
-
/** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */
|
|
186
|
-
processLoopRetryIntervalMs?: number;
|
|
155
|
+
* Configuration for the processor. Default to simple processor.
|
|
156
|
+
* @default { type: 'simple' } if no configuration is provided.
|
|
157
|
+
*/
|
|
158
|
+
type ProcessorConfiguration = Partial<SimpleProcessorConfiguration> & {
|
|
159
|
+
type?: "simple";
|
|
187
160
|
};
|
|
188
161
|
//#endregion
|
|
189
162
|
//#region src/chrono.d.ts
|
|
190
163
|
type TaskMappingBase = Record<string, unknown>;
|
|
191
164
|
type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<TaskKind, TaskData, DatastoreOptions>;
|
|
192
165
|
type RegisterTaskHandlerInput<TaskKind, TaskData> = {
|
|
193
|
-
/** The type of task */
|
|
194
|
-
|
|
195
|
-
/** The
|
|
196
|
-
handler: (task: Task<TaskKind, TaskData>) => Promise<void>;
|
|
197
|
-
/** The options for the backoff strategy to use when the task handler fails */
|
|
198
|
-
backoffStrategyOptions?: BackoffStrategyOptions;
|
|
199
|
-
/** The configuration for the processor to use when processing the task */
|
|
166
|
+
/** The type of task */kind: TaskKind; /** The handler function to process the task */
|
|
167
|
+
handler: (task: Task<TaskKind, TaskData>) => Promise<void>; /** The options for the backoff strategy to use when the task handler fails */
|
|
168
|
+
backoffStrategyOptions?: BackoffStrategyOptions; /** The configuration for the processor to use when processing the task */
|
|
200
169
|
processorConfiguration?: ProcessorConfiguration;
|
|
201
170
|
};
|
|
202
171
|
/**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
172
|
+
* Response from registering a task handler.
|
|
173
|
+
* @returns The processor instance that can be used to start and stop the processor.
|
|
174
|
+
*/
|
|
206
175
|
type RegisterTaskHandlerResponse<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;
|
|
207
176
|
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
177
|
+
* The main class for scheduling and processing tasks.
|
|
178
|
+
* @param datastore - The datastore instance to use for storing and retrieving tasks.
|
|
179
|
+
* @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.
|
|
180
|
+
*/
|
|
212
181
|
declare class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {
|
|
213
182
|
private readonly datastore;
|
|
214
183
|
private readonly processors;
|
|
215
|
-
readonly exitTimeoutMs =
|
|
184
|
+
readonly exitTimeoutMs = 6e4;
|
|
216
185
|
constructor(datastore: Datastore<TaskMapping, DatastoreOptions>);
|
|
217
186
|
start(): Promise<void>;
|
|
218
187
|
stop(): Promise<void>;
|
|
@@ -222,4 +191,4 @@ declare class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> exte
|
|
|
222
191
|
}
|
|
223
192
|
//#endregion
|
|
224
193
|
export { Chrono, ChronoEvents, type ClaimTaskInput, type Datastore, type DeleteByIdempotencyKeyInput, type DeleteInput, type DeleteOptions, ProcessorEvents, type ProcessorEventsMap, type RegisterTaskHandlerInput, type RegisterTaskHandlerResponse, type ScheduleInput, type ScheduleTaskInput, type Task, type TaskMappingBase, TaskStatus };
|
|
225
|
-
//# sourceMappingURL=index.d.
|
|
194
|
+
//# sourceMappingURL=index.d.cts.map
|
package/build/index.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
|
|
3
3
|
//#region src/backoff-strategy.d.ts
|
|
4
|
-
|
|
5
4
|
interface FixedBackoffStrategyConfig {
|
|
6
5
|
/** The constant delay in milliseconds. */
|
|
7
6
|
readonly delayMs: number;
|
|
@@ -18,16 +17,16 @@ interface ExponentialBackoffStrategyConfig {
|
|
|
18
17
|
/** The maximum delay in milliseconds. Defaults to Infinity. */
|
|
19
18
|
readonly maxDelayMs?: number;
|
|
20
19
|
/** Type of jitter to apply. Defaults to 'none'. */
|
|
21
|
-
readonly jitter?:
|
|
20
|
+
readonly jitter?: "none" | "full" | "equal";
|
|
22
21
|
}
|
|
23
22
|
type BackoffStrategyOptions = {
|
|
24
|
-
type:
|
|
23
|
+
type: "none";
|
|
25
24
|
} | ({
|
|
26
|
-
type:
|
|
25
|
+
type: "fixed";
|
|
27
26
|
} & FixedBackoffStrategyConfig) | ({
|
|
28
|
-
type:
|
|
27
|
+
type: "linear";
|
|
29
28
|
} & LinearBackoffStrategyConfig) | ({
|
|
30
|
-
type:
|
|
29
|
+
type: "exponential";
|
|
31
30
|
} & ExponentialBackoffStrategyConfig);
|
|
32
31
|
//#endregion
|
|
33
32
|
//#region src/datastore.d.ts
|
|
@@ -39,43 +38,25 @@ declare const TaskStatus: {
|
|
|
39
38
|
};
|
|
40
39
|
type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];
|
|
41
40
|
type Task<TaskKind, TaskData> = {
|
|
42
|
-
/** A unique identifier for the task */
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
/** The
|
|
49
|
-
|
|
50
|
-
/** The
|
|
51
|
-
|
|
52
|
-
/** A
|
|
53
|
-
idempotencyKey?: string;
|
|
54
|
-
/** The original scheduled date when the task was first intended to run */
|
|
55
|
-
originalScheduleDate: Date;
|
|
56
|
-
/** The current scheduled execution date, which may change if rescheduled */
|
|
57
|
-
scheduledAt: Date;
|
|
58
|
-
/** The date the task is mark 'claimed */
|
|
59
|
-
claimedAt?: Date;
|
|
60
|
-
/** The date the task is mark 'completed' */
|
|
61
|
-
completedAt?: Date;
|
|
62
|
-
/** The date when the task was last executed (if any) */
|
|
63
|
-
lastExecutedAt?: Date;
|
|
64
|
-
/** A counter to track the number of times the task has been retried */
|
|
41
|
+
/** A unique identifier for the task */id: string; /** A human-readable name or type for the task */
|
|
42
|
+
kind: TaskKind; /** The current status of the task */
|
|
43
|
+
status: TaskStatus; /** The payload or data associated with the task */
|
|
44
|
+
data: TaskData; /** The priority level of the task (lower numbers can indicate higher priority) */
|
|
45
|
+
priority?: number; /** A key used for idempotency to prevent duplicate processing */
|
|
46
|
+
idempotencyKey?: string; /** The original scheduled date when the task was first intended to run */
|
|
47
|
+
originalScheduleDate: Date; /** The current scheduled execution date, which may change if rescheduled */
|
|
48
|
+
scheduledAt: Date; /** The date the task is mark 'claimed */
|
|
49
|
+
claimedAt?: Date; /** The date the task is mark 'completed' */
|
|
50
|
+
completedAt?: Date; /** The date when the task was last executed (if any) */
|
|
51
|
+
lastExecutedAt?: Date; /** A counter to track the number of times the task has been retried */
|
|
65
52
|
retryCount: number;
|
|
66
53
|
};
|
|
67
54
|
type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {
|
|
68
|
-
/** The date and time when the task is scheduled to run */
|
|
69
|
-
|
|
70
|
-
/** The
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
data: TaskData;
|
|
74
|
-
/** The priority level of the task (lower numbers can indicate higher priority) */
|
|
75
|
-
priority?: number;
|
|
76
|
-
/** A key used for idempotency to prevent duplicate processing */
|
|
77
|
-
idempotencyKey?: string;
|
|
78
|
-
/** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/
|
|
55
|
+
/** The date and time when the task is scheduled to run */when: Date; /** The type of task */
|
|
56
|
+
kind: TaskKind; /** The payload or data associated with the task */
|
|
57
|
+
data: TaskData; /** The priority level of the task (lower numbers can indicate higher priority) */
|
|
58
|
+
priority?: number; /** A key used for idempotency to prevent duplicate processing */
|
|
59
|
+
idempotencyKey?: string; /** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/
|
|
79
60
|
datastoreOptions?: DatastoreOptions;
|
|
80
61
|
};
|
|
81
62
|
type ClaimTaskInput<TaskKind> = {
|
|
@@ -102,9 +83,7 @@ interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {
|
|
|
102
83
|
//#endregion
|
|
103
84
|
//#region src/events.d.ts
|
|
104
85
|
declare const ChronoEvents: {
|
|
105
|
-
/** Chrono instance has started processors and begun polling tasks */
|
|
106
|
-
readonly STARTED: "started";
|
|
107
|
-
/** Chrono instance has failed to gracefully stop so shutdown has been aborted */
|
|
86
|
+
/** Chrono instance has started processors and begun polling tasks */readonly STARTED: "started"; /** Chrono instance has failed to gracefully stop so shutdown has been aborted */
|
|
108
87
|
readonly STOP_ABORTED: "stopAborted";
|
|
109
88
|
};
|
|
110
89
|
type ChronoEvents = (typeof ChronoEvents)[keyof typeof ChronoEvents];
|
|
@@ -120,17 +99,11 @@ type ChronoEventsMap = {
|
|
|
120
99
|
//#endregion
|
|
121
100
|
//#region src/processors/events.d.ts
|
|
122
101
|
declare const ProcessorEvents: {
|
|
123
|
-
/** A task has been claimed by the running processor for handling */
|
|
124
|
-
readonly
|
|
125
|
-
/** A task has
|
|
126
|
-
readonly
|
|
127
|
-
/**
|
|
128
|
-
readonly TASK_RETRY_SCHEDULED: "taskRetryScheduled";
|
|
129
|
-
/** A task has been marked as FAILED due to process failures exceeding max retries */
|
|
130
|
-
readonly TASK_FAILED: "taskFailed";
|
|
131
|
-
/** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */
|
|
132
|
-
readonly TASK_COMPLETION_FAILURE: "taskCompletionFailure";
|
|
133
|
-
/** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */
|
|
102
|
+
/** A task has been claimed by the running processor for handling */readonly TASK_CLAIMED: "taskClaimed"; /** A task has completed processing and successfully marked as completed */
|
|
103
|
+
readonly TASK_COMPLETED: "taskCompleted"; /** A task has failed during processing and being scheduled for retry */
|
|
104
|
+
readonly TASK_RETRY_SCHEDULED: "taskRetryScheduled"; /** A task has been marked as FAILED due to process failures exceeding max retries */
|
|
105
|
+
readonly TASK_FAILED: "taskFailed"; /** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */
|
|
106
|
+
readonly TASK_COMPLETION_FAILURE: "taskCompletionFailure"; /** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */
|
|
134
107
|
readonly UNKNOWN_PROCESSING_ERROR: "unknownProcessingError";
|
|
135
108
|
};
|
|
136
109
|
type ProcessorEvents = (typeof ProcessorEvents)[keyof typeof ProcessorEvents];
|
|
@@ -142,6 +115,7 @@ type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends
|
|
|
142
115
|
[ProcessorEvents.TASK_COMPLETED]: [{
|
|
143
116
|
task: Task<TaskKind, TaskMapping[TaskKind]>;
|
|
144
117
|
completedAt: Date;
|
|
118
|
+
startedAt: Date;
|
|
145
119
|
}];
|
|
146
120
|
[ProcessorEvents.TASK_RETRY_SCHEDULED]: [{
|
|
147
121
|
task: Task<TaskKind, TaskMapping[TaskKind]>;
|
|
@@ -165,54 +139,49 @@ type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends
|
|
|
165
139
|
}];
|
|
166
140
|
};
|
|
167
141
|
//#endregion
|
|
142
|
+
//#region src/processors/simple-processor.d.ts
|
|
143
|
+
type SimpleProcessorConfiguration = {
|
|
144
|
+
/** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */maxConcurrency: number; /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */
|
|
145
|
+
claimIntervalMs: number; /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */
|
|
146
|
+
claimStaleTimeoutMs: number; /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */
|
|
147
|
+
idleIntervalMs: number; /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */
|
|
148
|
+
taskHandlerTimeoutMs: number; /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */
|
|
149
|
+
taskHandlerMaxRetries: number; /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */
|
|
150
|
+
processLoopRetryIntervalMs: number;
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
168
153
|
//#region src/processors/create-processor.d.ts
|
|
169
154
|
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */
|
|
176
|
-
claimIntervalMs?: number;
|
|
177
|
-
/** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */
|
|
178
|
-
idleIntervalMs?: number;
|
|
179
|
-
/** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */
|
|
180
|
-
claimStaleTimeoutMs?: number;
|
|
181
|
-
/** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */
|
|
182
|
-
taskHandlerTimeoutMs?: number;
|
|
183
|
-
/** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */
|
|
184
|
-
taskHandlerMaxRetries?: number;
|
|
185
|
-
/** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */
|
|
186
|
-
processLoopRetryIntervalMs?: number;
|
|
155
|
+
* Configuration for the processor. Default to simple processor.
|
|
156
|
+
* @default { type: 'simple' } if no configuration is provided.
|
|
157
|
+
*/
|
|
158
|
+
type ProcessorConfiguration = Partial<SimpleProcessorConfiguration> & {
|
|
159
|
+
type?: "simple";
|
|
187
160
|
};
|
|
188
161
|
//#endregion
|
|
189
162
|
//#region src/chrono.d.ts
|
|
190
163
|
type TaskMappingBase = Record<string, unknown>;
|
|
191
164
|
type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<TaskKind, TaskData, DatastoreOptions>;
|
|
192
165
|
type RegisterTaskHandlerInput<TaskKind, TaskData> = {
|
|
193
|
-
/** The type of task */
|
|
194
|
-
|
|
195
|
-
/** The
|
|
196
|
-
handler: (task: Task<TaskKind, TaskData>) => Promise<void>;
|
|
197
|
-
/** The options for the backoff strategy to use when the task handler fails */
|
|
198
|
-
backoffStrategyOptions?: BackoffStrategyOptions;
|
|
199
|
-
/** The configuration for the processor to use when processing the task */
|
|
166
|
+
/** The type of task */kind: TaskKind; /** The handler function to process the task */
|
|
167
|
+
handler: (task: Task<TaskKind, TaskData>) => Promise<void>; /** The options for the backoff strategy to use when the task handler fails */
|
|
168
|
+
backoffStrategyOptions?: BackoffStrategyOptions; /** The configuration for the processor to use when processing the task */
|
|
200
169
|
processorConfiguration?: ProcessorConfiguration;
|
|
201
170
|
};
|
|
202
171
|
/**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
172
|
+
* Response from registering a task handler.
|
|
173
|
+
* @returns The processor instance that can be used to start and stop the processor.
|
|
174
|
+
*/
|
|
206
175
|
type RegisterTaskHandlerResponse<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;
|
|
207
176
|
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
177
|
+
* The main class for scheduling and processing tasks.
|
|
178
|
+
* @param datastore - The datastore instance to use for storing and retrieving tasks.
|
|
179
|
+
* @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.
|
|
180
|
+
*/
|
|
212
181
|
declare class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {
|
|
213
182
|
private readonly datastore;
|
|
214
183
|
private readonly processors;
|
|
215
|
-
readonly exitTimeoutMs =
|
|
184
|
+
readonly exitTimeoutMs = 6e4;
|
|
216
185
|
constructor(datastore: Datastore<TaskMapping, DatastoreOptions>);
|
|
217
186
|
start(): Promise<void>;
|
|
218
187
|
stop(): Promise<void>;
|
package/build/index.mjs
CHANGED
|
@@ -97,7 +97,6 @@ const ProcessorEvents = {
|
|
|
97
97
|
TASK_COMPLETION_FAILURE: "taskCompletionFailure",
|
|
98
98
|
UNKNOWN_PROCESSING_ERROR: "unknownProcessingError"
|
|
99
99
|
};
|
|
100
|
-
ProcessorEvents.TASK_CLAIMED;
|
|
101
100
|
|
|
102
101
|
//#endregion
|
|
103
102
|
//#region src/processors/simple-processor.ts
|
|
@@ -125,7 +124,7 @@ var SimpleProcessor = class extends EventEmitter {
|
|
|
125
124
|
...DEFAULT_CONFIG,
|
|
126
125
|
...config
|
|
127
126
|
};
|
|
128
|
-
this.
|
|
127
|
+
this.validateConfiguration();
|
|
129
128
|
}
|
|
130
129
|
/**
|
|
131
130
|
* Validates that the task handler timeout is less than the claim stale timeout.
|
|
@@ -135,8 +134,9 @@ var SimpleProcessor = class extends EventEmitter {
|
|
|
135
134
|
*
|
|
136
135
|
* @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.
|
|
137
136
|
*/
|
|
138
|
-
|
|
137
|
+
validateConfiguration() {
|
|
139
138
|
if (this.config.taskHandlerTimeoutMs >= this.config.claimStaleTimeoutMs) throw new Error(`Task handler timeout (${this.config.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.config.claimStaleTimeoutMs}ms)`);
|
|
139
|
+
if (this.config.claimIntervalMs >= this.config.idleIntervalMs) throw new Error(`Claim interval (${this.config.claimIntervalMs}ms) must be less than the idle interval (${this.config.idleIntervalMs}ms)`);
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
142
|
* Starts multiple concurrent process loops that claim and process tasks.
|
|
@@ -200,6 +200,7 @@ var SimpleProcessor = class extends EventEmitter {
|
|
|
200
200
|
* @param task The task to handle.
|
|
201
201
|
*/
|
|
202
202
|
async handleTask(task) {
|
|
203
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
203
204
|
try {
|
|
204
205
|
await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);
|
|
205
206
|
} catch (error) {
|
|
@@ -210,7 +211,8 @@ var SimpleProcessor = class extends EventEmitter {
|
|
|
210
211
|
const completedTask = await this.datastore.complete(task.id);
|
|
211
212
|
this.emit(ProcessorEvents.TASK_COMPLETED, {
|
|
212
213
|
task: completedTask,
|
|
213
|
-
completedAt: completedTask.completedAt || /* @__PURE__ */ new Date()
|
|
214
|
+
completedAt: completedTask.completedAt || /* @__PURE__ */ new Date(),
|
|
215
|
+
startedAt
|
|
214
216
|
});
|
|
215
217
|
} catch (error) {
|
|
216
218
|
this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {
|
|
@@ -247,7 +249,9 @@ var SimpleProcessor = class extends EventEmitter {
|
|
|
247
249
|
//#region src/processors/create-processor.ts
|
|
248
250
|
function createProcessor(input) {
|
|
249
251
|
const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);
|
|
250
|
-
|
|
252
|
+
const processorType = input.configuration?.type ?? "simple";
|
|
253
|
+
if (processorType === "simple") return new SimpleProcessor(input.datastore, input.kind, input.handler, backoffStrategy, input.configuration);
|
|
254
|
+
throw new Error(`Unknown processor type: ${processorType}`);
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
//#endregion
|
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["DEFAULT_BACKOFF_STRATEGY: BackoffStrategyOptions","DEFAULT_CONFIG: SimpleProcessorConfig","datastore: Datastore<TaskMapping, DatastoreOptions>","taskKind: TaskKind","handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>","backOffStrategy: BackoffStrategy","setTimeout"],"sources":["../src/events.ts","../src/backoff-strategy.ts","../src/utils/promise-utils.ts","../src/processors/events.ts","../src/processors/simple-processor.ts","../src/processors/create-processor.ts","../src/chrono.ts","../src/datastore.ts"],"sourcesContent":["export const ChronoEvents = {\n /** Chrono instance has started processors and begun polling tasks */\n STARTED: 'started',\n /** Chrono instance has failed to gracefully stop so shutdown has been aborted */\n STOP_ABORTED: 'stopAborted',\n} as const;\n\nexport type ChronoEvents = (typeof ChronoEvents)[keyof typeof ChronoEvents];\n\nexport type ChronoEventsMap = {\n [ChronoEvents.STARTED]: [{ startedAt: Date }];\n [ChronoEvents.STOP_ABORTED]: [{ timestamp: Date; error: unknown }];\n};\n","/** Input for calculating the next backoff delay. */\nexport type BackoffStrategyInput = {\n /** The number of retries already attempted (0 for the first retry). */\n retryAttempt: number;\n};\n\nexport type DelayMs = number;\n\n/**\n * A function that calculates the backoff delay in milliseconds.\n * @param input - Contains information like the current retry number.\n * @returns The delay duration in milliseconds.\n */\nexport type BackoffStrategy = (input: BackoffStrategyInput) => DelayMs;\n\n/**\n * Creates a strategy that provides no delay (immediate retry).\n */\nexport function createNoBackoffStrategy(): BackoffStrategy {\n return (_input: BackoffStrategyInput) => 0;\n}\n\nexport interface FixedBackoffStrategyConfig {\n /** The constant delay in milliseconds. */\n readonly delayMs: number;\n}\n/**\n * Creates a strategy that waits a fixed amount of time.\n */\nexport function createFixedBackoffStrategy(config: FixedBackoffStrategyConfig): BackoffStrategy {\n return (_input: BackoffStrategyInput) => config.delayMs;\n}\n\nexport interface LinearBackoffStrategyConfig {\n /** The base delay for all retires that will be incremented off of */\n readonly baseDelayMs?: number;\n /** The amount to increase the delay by for each subsequent retry in milliseconds. */\n readonly incrementMs: number;\n}\n\n/**\n * Creates a strategy where the delay increases linearly.\n * Delay = (incrementMs * retryAttempt)\n */\nexport function createLinearBackoffStrategy(config: LinearBackoffStrategyConfig): BackoffStrategy {\n const { incrementMs, baseDelayMs = 0 } = config;\n return (input: BackoffStrategyInput) => {\n return baseDelayMs + input.retryAttempt * incrementMs;\n };\n}\n\nexport interface ExponentialBackoffStrategyConfig {\n /** The base delay for the first retry (retryAttempt = 0) in milliseconds. */\n readonly baseDelayMs: number;\n /** The maximum delay in milliseconds. Defaults to Infinity. */\n readonly maxDelayMs?: number;\n /** Type of jitter to apply. Defaults to 'none'. */\n readonly jitter?: 'none' | 'full' | 'equal';\n}\n/**\n * Creates a strategy where the delay increases exponentially, potentially with jitter.\n * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)\n */\nexport function createExponentialBackoffStrategy(config: ExponentialBackoffStrategyConfig): BackoffStrategy {\n const { baseDelayMs, maxDelayMs = Number.POSITIVE_INFINITY, jitter = 'none' } = config;\n\n return (input: BackoffStrategyInput) => {\n const exponentialDelay = baseDelayMs * 2 ** input.retryAttempt;\n const cappedDelay = Math.min(exponentialDelay, maxDelayMs);\n\n switch (jitter) {\n case 'full':\n // Full Jitter: random_between(0, cappedDelay)\n return Math.floor(Math.random() * cappedDelay);\n case 'equal': {\n // Equal Jitter: (cappedDelay / 2) + random_between(0, cappedDelay / 2)\n const halfDelay = cappedDelay / 2;\n return halfDelay + Math.random() * halfDelay;\n }\n case 'none':\n // No Jitter\n return cappedDelay;\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = jitter;\n throw new Error('Unknown jitter type for exponential backoff strategy');\n }\n }\n };\n}\n\nexport type BackoffStrategyType = 'none' | 'fixed' | 'linear' | 'exponential';\n\nexport type BackoffStrategyOptions =\n | { type: 'none' }\n | ({ type: 'fixed' } & FixedBackoffStrategyConfig)\n | ({ type: 'linear' } & LinearBackoffStrategyConfig)\n | ({ type: 'exponential' } & ExponentialBackoffStrategyConfig);\n\nconst DEFAULT_BACKOFF_STRATEGY: BackoffStrategyOptions = {\n type: 'linear',\n incrementMs: 2000,\n} as const;\n\n/**\n * Factory function to create a backoff strategy based on type and configuration.\n * @param options - Configuration object including the strategy type.\n * @returns A BackoffStrategy function.\n */\nexport function backoffStrategyFactory(options: BackoffStrategyOptions = DEFAULT_BACKOFF_STRATEGY): BackoffStrategy {\n switch (options.type) {\n case 'none':\n return createNoBackoffStrategy();\n case 'fixed':\n // No need to destructure 'type', pass the rest\n return createFixedBackoffStrategy(options);\n case 'linear':\n return createLinearBackoffStrategy(options);\n case 'exponential':\n return createExponentialBackoffStrategy(options);\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = options;\n throw new Error('Unknown backoff strategy type');\n }\n }\n}\n","export function promiseWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('Promise timed out'));\n }, timeout);\n\n promise\n .then((result) => {\n clearTimeout(timer);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n","import type { Task, TaskMappingBase } from '..';\n\nexport const ProcessorEvents = {\n /** A task has been claimed by the running processor for handling */\n TASK_CLAIMED: 'taskClaimed',\n /** A task has completed processing and successfully marked as completed */\n TASK_COMPLETED: 'taskCompleted',\n /** A task has failed during processing and being scheduled for retry */\n TASK_RETRY_SCHEDULED: 'taskRetryScheduled',\n /** A task has been marked as FAILED due to process failures exceeding max retries */\n TASK_FAILED: 'taskFailed',\n /** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */\n TASK_COMPLETION_FAILURE: 'taskCompletionFailure',\n /** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */\n UNKNOWN_PROCESSING_ERROR: 'unknownProcessingError',\n} as const;\n\nexport type ProcessorEvents = (typeof ProcessorEvents)[keyof typeof ProcessorEvents];\n\nexport type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = {\n [ProcessorEvents.TASK_CLAIMED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; claimedAt: Date }];\n [ProcessorEvents.TASK_COMPLETED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; completedAt: Date }];\n [ProcessorEvents.TASK_RETRY_SCHEDULED]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; retryScheduledAt: Date; errorAt: Date },\n ];\n [ProcessorEvents.TASK_FAILED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date }];\n [ProcessorEvents.TASK_COMPLETION_FAILURE]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date },\n ];\n [ProcessorEvents.UNKNOWN_PROCESSING_ERROR]: [{ error: unknown; timestamp: Date }];\n};\n\nProcessorEvents.TASK_CLAIMED;\n","import { EventEmitter } from 'node:events';\nimport { setTimeout } from 'node:timers/promises';\nimport type { BackoffStrategy } from '../backoff-strategy';\nimport type { TaskMappingBase } from '../chrono';\nimport type { Datastore, Task } from '../datastore';\nimport { promiseWithTimeout } from '../utils/promise-utils';\nimport { ProcessorEvents, type ProcessorEventsMap } from './events';\nimport type { Processor } from './processor';\n\nconst DEFAULT_CONFIG: SimpleProcessorConfig = {\n maxConcurrency: 1,\n claimIntervalMs: 50,\n idleIntervalMs: 5_000,\n claimStaleTimeoutMs: 10_000,\n taskHandlerTimeoutMs: 5_000,\n taskHandlerMaxRetries: 5,\n processLoopRetryIntervalMs: 20_000,\n};\n\ntype SimpleProcessorConfig = {\n /** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */\n maxConcurrency: number;\n /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */\n claimIntervalMs: number;\n /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */\n claimStaleTimeoutMs: number;\n /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */\n idleIntervalMs: number;\n /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */\n taskHandlerTimeoutMs: number;\n /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */\n taskHandlerMaxRetries: number;\n /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */\n processLoopRetryIntervalMs: number;\n};\n\nconst InternalProcessorEvents = { PROCESSOR_LOOP_EXIT: 'processorLoopExit' } as const;\n\ntype InternalProcessorEventsMap = {\n [InternalProcessorEvents.PROCESSOR_LOOP_EXIT]: [];\n};\n\nexport class SimpleProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n >\n extends EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>\n implements Processor<TaskKind, TaskMapping>\n{\n private config: SimpleProcessorConfig;\n\n private exitChannels: EventEmitter<InternalProcessorEventsMap>[] = [];\n private stopRequested = false;\n\n constructor(\n private datastore: Datastore<TaskMapping, DatastoreOptions>,\n private taskKind: TaskKind,\n private handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>,\n private backOffStrategy: BackoffStrategy,\n config?: Partial<SimpleProcessorConfig>,\n ) {\n super();\n\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n this.validatedHandlerTimeout();\n }\n\n /**\n * Validates that the task handler timeout is less than the claim stale timeout.\n * Throws an error if the validation fails.\n * This ensures that the task handler has enough time to complete before the task is considered stale.\n * This is important to prevent tasks from being claimed again while they are still being processed.\n *\n * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.\n */\n private validatedHandlerTimeout() {\n if (this.config.taskHandlerTimeoutMs >= this.config.claimStaleTimeoutMs) {\n throw new Error(\n `Task handler timeout (${this.config.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.config.claimStaleTimeoutMs}ms)`,\n );\n }\n }\n\n /**\n * Starts multiple concurrent process loops that claim and process tasks.\n * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.\n */\n async start(): Promise<void> {\n if (this.stopRequested || this.exitChannels.length > 0) {\n return;\n }\n\n for (let i = 0; i < this.config.maxConcurrency; i++) {\n const exitChannel = new EventEmitter<InternalProcessorEventsMap>();\n\n this.exitChannels.push(exitChannel);\n this.runProcessLoop(exitChannel);\n }\n }\n\n /**\n * Stops the processor by signaling all process loops to exit,\n * then waits for all process loops to finish before resolving.\n */\n async stop(): Promise<void> {\n const exitPromises = this.exitChannels.map(\n (channel) =>\n new Promise((resolve) => channel.once(InternalProcessorEvents.PROCESSOR_LOOP_EXIT, () => resolve(null))),\n );\n\n this.stopRequested = true;\n\n await Promise.all(exitPromises);\n }\n\n /**\n * The main loop that processes tasks.\n *\n * @param exitChannel The channel to signal when the loop exits.\n */\n private async runProcessLoop(exitChannel: EventEmitter<InternalProcessorEventsMap>): Promise<void> {\n while (!this.stopRequested) {\n try {\n const task = await this.datastore.claim({\n kind: this.taskKind,\n claimStaleTimeoutMs: this.config.claimStaleTimeoutMs,\n });\n\n // If no tasks are available, wait before trying again\n if (!task) {\n await setTimeout(this.config.idleIntervalMs);\n\n continue;\n }\n\n this.emit(ProcessorEvents.TASK_CLAIMED, { task, claimedAt: task.claimedAt || new Date() });\n\n // Process the task using the handler\n await this.handleTask(task);\n\n // Wait a bit before claiming the next task\n await setTimeout(this.config.claimIntervalMs);\n } catch (error) {\n this.emit(ProcessorEvents.UNKNOWN_PROCESSING_ERROR, { error, timestamp: new Date() });\n\n await setTimeout(this.config.processLoopRetryIntervalMs);\n }\n }\n\n exitChannel.emit(InternalProcessorEvents.PROCESSOR_LOOP_EXIT);\n }\n\n /**\n * Handles a task by calling the handler and marking it as complete or failed.\n *\n * Emits:\n * - `task.completed` when the task is successfully completed.\n * - `task.failed` when the task fails.\n * - `task.complete.failed` when the task fails to mark as completed.\n *\n * @param task The task to handle.\n */\n private async handleTask(task: Task<TaskKind, TaskMapping[TaskKind]>) {\n try {\n await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);\n } catch (error) {\n await this.handleTaskError(task, error as Error);\n\n return;\n }\n\n try {\n const completedTask = await this.datastore.complete<TaskKind>(task.id);\n\n this.emit(ProcessorEvents.TASK_COMPLETED, {\n task: completedTask,\n completedAt: completedTask.completedAt || new Date(),\n });\n } catch (error) {\n this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {\n error: error,\n failedAt: new Date(),\n task,\n });\n }\n }\n\n private async handleTaskError(task: Task<TaskKind, TaskMapping[TaskKind]>, error: Error): Promise<void> {\n const failedAt = new Date();\n if (task.retryCount >= this.config.taskHandlerMaxRetries) {\n // Mark the task as failed\n await this.datastore.fail(task.id);\n this.emit(ProcessorEvents.TASK_FAILED, {\n task,\n error,\n failedAt,\n });\n\n return;\n }\n\n const delay = this.backOffStrategy({ retryAttempt: task.retryCount });\n const retryAt = new Date(Date.now() + delay);\n\n await this.datastore.retry(task.id, retryAt);\n this.emit(ProcessorEvents.TASK_RETRY_SCHEDULED, {\n task,\n error,\n errorAt: failedAt,\n retryScheduledAt: retryAt,\n });\n }\n}\n","import type { Datastore, Task } from 'datastore';\nimport type { TaskMappingBase } from '..';\nimport { type BackoffStrategyOptions, backoffStrategyFactory } from '../backoff-strategy';\nimport type { Processor } from './processor';\nimport { SimpleProcessor } from './simple-processor';\n\n/**\n * Configuration for the processor.\n */\nexport type ProcessorConfiguration = {\n /** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */\n maxConcurrency?: number;\n /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */\n claimIntervalMs?: number;\n /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */\n idleIntervalMs?: number;\n /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */\n claimStaleTimeoutMs?: number;\n /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */\n taskHandlerTimeoutMs?: number;\n /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */\n taskHandlerMaxRetries?: number;\n /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */\n processLoopRetryIntervalMs?: number;\n};\n\nexport type CreateProcessorInput<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n> = {\n kind: TaskKind;\n datastore: Datastore<TaskMapping, DatastoreOptions>;\n handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;\n configuration?: ProcessorConfiguration;\n backoffStrategyOptions?: BackoffStrategyOptions;\n};\n\nexport function createProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n>(input: CreateProcessorInput<TaskKind, TaskMapping, DatastoreOptions>): Processor<TaskKind, TaskMapping> {\n const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);\n // add more processors here\n return new SimpleProcessor<TaskKind, TaskMapping, DatastoreOptions>(\n input.datastore,\n input.kind,\n input.handler,\n backoffStrategy,\n input.configuration,\n );\n}\n","import { EventEmitter } from 'node:events';\n\nimport type { BackoffStrategyOptions } from './backoff-strategy';\nimport type { Datastore, ScheduleInput, Task } from './datastore';\nimport { ChronoEvents, type ChronoEventsMap } from './events';\nimport { createProcessor, type Processor } from './processors';\nimport type { ProcessorConfiguration } from './processors/create-processor';\nimport type { ProcessorEventsMap } from './processors/events';\nimport { promiseWithTimeout } from './utils/promise-utils';\n\nexport type TaskMappingBase = Record<string, unknown>;\n\nexport type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<\n TaskKind,\n TaskData,\n DatastoreOptions\n>;\n\nexport type RegisterTaskHandlerInput<TaskKind, TaskData> = {\n /** The type of task */\n kind: TaskKind;\n /** The handler function to process the task */\n handler: (task: Task<TaskKind, TaskData>) => Promise<void>;\n /** The options for the backoff strategy to use when the task handler fails */\n backoffStrategyOptions?: BackoffStrategyOptions;\n /** The configuration for the processor to use when processing the task */\n processorConfiguration?: ProcessorConfiguration;\n};\n\n/**\n * Response from registering a task handler.\n * @returns The processor instance that can be used to start and stop the processor.\n */\nexport type RegisterTaskHandlerResponse<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;\n\n/**\n * The main class for scheduling and processing tasks.\n * @param datastore - The datastore instance to use for storing and retrieving tasks.\n * @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.\n */\nexport class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {\n private readonly datastore: Datastore<TaskMapping, DatastoreOptions>;\n private readonly processors: Map<keyof TaskMapping, Processor<keyof TaskMapping, TaskMapping>> = new Map();\n\n readonly exitTimeoutMs = 60_000;\n\n constructor(datastore: Datastore<TaskMapping, DatastoreOptions>) {\n super();\n\n this.datastore = datastore;\n }\n\n public async start(): Promise<void> {\n for (const processor of this.processors.values()) {\n await processor.start();\n }\n\n this.emit(ChronoEvents.STARTED, { startedAt: new Date() });\n }\n\n public async stop(): Promise<void> {\n const stopPromises = Array.from(this.processors.values()).map((processor) => processor.stop());\n\n try {\n await promiseWithTimeout(Promise.all(stopPromises), this.exitTimeoutMs);\n } catch (error) {\n this.emit(ChronoEvents.STOP_ABORTED, { error, timestamp: new Date() });\n }\n }\n\n public async scheduleTask<TaskKind extends keyof TaskMapping>(\n input: ScheduleTaskInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const task = await this.datastore.schedule({\n when: input.when,\n kind: input.kind,\n data: input.data,\n datastoreOptions: input.datastoreOptions,\n });\n\n return task;\n }\n\n public async deleteTask<TaskKind extends keyof TaskMapping>(\n taskId: string,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const task = await this.datastore.delete<TaskKind>(taskId);\n\n return task;\n }\n\n public registerTaskHandler<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: RegisterTaskHandlerInput<TaskKind, TaskMapping[TaskKind]>,\n ): RegisterTaskHandlerResponse<TaskKind, TaskMapping> {\n if (this.processors.has(input.kind)) {\n throw new Error('Handler for task kind already exists');\n }\n\n const processor = createProcessor({\n kind: input.kind,\n datastore: this.datastore,\n handler: input.handler,\n backoffStrategyOptions: input.backoffStrategyOptions,\n configuration: input.processorConfiguration,\n });\n\n this.processors.set(input.kind, processor);\n\n return processor;\n }\n}\n","import type { TaskMappingBase } from './chrono';\n\nexport const TaskStatus = {\n PENDING: 'PENDING',\n CLAIMED: 'CLAIMED',\n COMPLETED: 'COMPLETED',\n FAILED: 'FAILED',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport type Task<TaskKind, TaskData> = {\n /** A unique identifier for the task */\n id: string;\n /** A human-readable name or type for the task */\n kind: TaskKind;\n /** The current status of the task */\n status: TaskStatus;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** The original scheduled date when the task was first intended to run */\n originalScheduleDate: Date;\n /** The current scheduled execution date, which may change if rescheduled */\n scheduledAt: Date;\n /** The date the task is mark 'claimed */\n claimedAt?: Date;\n /** The date the task is mark 'completed' */\n completedAt?: Date;\n /** The date when the task was last executed (if any) */\n lastExecutedAt?: Date;\n /** A counter to track the number of times the task has been retried */\n retryCount: number;\n};\n\nexport type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {\n /** The date and time when the task is scheduled to run */\n when: Date;\n /** The type of task */\n kind: TaskKind;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/\n datastoreOptions?: DatastoreOptions;\n};\n\nexport type ClaimTaskInput<TaskKind> = {\n kind: TaskKind;\n claimStaleTimeoutMs: number;\n};\n\nexport type DeleteByIdempotencyKeyInput<TaskKind> = {\n kind: TaskKind;\n idempotencyKey: string;\n};\n\nexport type DeleteOptions = {\n force?: boolean;\n};\n\nexport type DeleteInput<TaskKind> = DeleteByIdempotencyKeyInput<TaskKind> | string;\n\nexport interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {\n schedule<TaskKind extends keyof TaskMapping>(\n input: ScheduleInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n delete<TaskKind extends keyof TaskMapping>(\n taskId: string,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n delete<TaskKind extends keyof TaskMapping>(\n key: DeleteByIdempotencyKeyInput<TaskKind>,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n claim<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: ClaimTaskInput<TaskKind>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n retry<TaskKind extends keyof TaskMapping>(\n taskId: string,\n retryAt: Date,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["setTimeout"],"sources":["../src/events.ts","../src/backoff-strategy.ts","../src/utils/promise-utils.ts","../src/processors/events.ts","../src/processors/simple-processor.ts","../src/processors/create-processor.ts","../src/chrono.ts","../src/datastore.ts"],"sourcesContent":["export const ChronoEvents = {\n /** Chrono instance has started processors and begun polling tasks */\n STARTED: 'started',\n /** Chrono instance has failed to gracefully stop so shutdown has been aborted */\n STOP_ABORTED: 'stopAborted',\n} as const;\n\nexport type ChronoEvents = (typeof ChronoEvents)[keyof typeof ChronoEvents];\n\nexport type ChronoEventsMap = {\n [ChronoEvents.STARTED]: [{ startedAt: Date }];\n [ChronoEvents.STOP_ABORTED]: [{ timestamp: Date; error: unknown }];\n};\n","/** Input for calculating the next backoff delay. */\nexport type BackoffStrategyInput = {\n /** The number of retries already attempted (0 for the first retry). */\n retryAttempt: number;\n};\n\nexport type DelayMs = number;\n\n/**\n * A function that calculates the backoff delay in milliseconds.\n * @param input - Contains information like the current retry number.\n * @returns The delay duration in milliseconds.\n */\nexport type BackoffStrategy = (input: BackoffStrategyInput) => DelayMs;\n\n/**\n * Creates a strategy that provides no delay (immediate retry).\n */\nexport function createNoBackoffStrategy(): BackoffStrategy {\n return (_input: BackoffStrategyInput) => 0;\n}\n\nexport interface FixedBackoffStrategyConfig {\n /** The constant delay in milliseconds. */\n readonly delayMs: number;\n}\n/**\n * Creates a strategy that waits a fixed amount of time.\n */\nexport function createFixedBackoffStrategy(config: FixedBackoffStrategyConfig): BackoffStrategy {\n return (_input: BackoffStrategyInput) => config.delayMs;\n}\n\nexport interface LinearBackoffStrategyConfig {\n /** The base delay for all retires that will be incremented off of */\n readonly baseDelayMs?: number;\n /** The amount to increase the delay by for each subsequent retry in milliseconds. */\n readonly incrementMs: number;\n}\n\n/**\n * Creates a strategy where the delay increases linearly.\n * Delay = (incrementMs * retryAttempt)\n */\nexport function createLinearBackoffStrategy(config: LinearBackoffStrategyConfig): BackoffStrategy {\n const { incrementMs, baseDelayMs = 0 } = config;\n return (input: BackoffStrategyInput) => {\n return baseDelayMs + input.retryAttempt * incrementMs;\n };\n}\n\nexport interface ExponentialBackoffStrategyConfig {\n /** The base delay for the first retry (retryAttempt = 0) in milliseconds. */\n readonly baseDelayMs: number;\n /** The maximum delay in milliseconds. Defaults to Infinity. */\n readonly maxDelayMs?: number;\n /** Type of jitter to apply. Defaults to 'none'. */\n readonly jitter?: 'none' | 'full' | 'equal';\n}\n/**\n * Creates a strategy where the delay increases exponentially, potentially with jitter.\n * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)\n */\nexport function createExponentialBackoffStrategy(config: ExponentialBackoffStrategyConfig): BackoffStrategy {\n const { baseDelayMs, maxDelayMs = Number.POSITIVE_INFINITY, jitter = 'none' } = config;\n\n return (input: BackoffStrategyInput) => {\n const exponentialDelay = baseDelayMs * 2 ** input.retryAttempt;\n const cappedDelay = Math.min(exponentialDelay, maxDelayMs);\n\n switch (jitter) {\n case 'full':\n // Full Jitter: random_between(0, cappedDelay)\n return Math.floor(Math.random() * cappedDelay);\n case 'equal': {\n // Equal Jitter: (cappedDelay / 2) + random_between(0, cappedDelay / 2)\n const halfDelay = cappedDelay / 2;\n return halfDelay + Math.random() * halfDelay;\n }\n case 'none':\n // No Jitter\n return cappedDelay;\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = jitter;\n throw new Error('Unknown jitter type for exponential backoff strategy');\n }\n }\n };\n}\n\nexport type BackoffStrategyType = 'none' | 'fixed' | 'linear' | 'exponential';\n\nexport type BackoffStrategyOptions =\n | { type: 'none' }\n | ({ type: 'fixed' } & FixedBackoffStrategyConfig)\n | ({ type: 'linear' } & LinearBackoffStrategyConfig)\n | ({ type: 'exponential' } & ExponentialBackoffStrategyConfig);\n\nconst DEFAULT_BACKOFF_STRATEGY: BackoffStrategyOptions = {\n type: 'linear',\n incrementMs: 2000,\n} as const;\n\n/**\n * Factory function to create a backoff strategy based on type and configuration.\n * @param options - Configuration object including the strategy type.\n * @returns A BackoffStrategy function.\n */\nexport function backoffStrategyFactory(options: BackoffStrategyOptions = DEFAULT_BACKOFF_STRATEGY): BackoffStrategy {\n switch (options.type) {\n case 'none':\n return createNoBackoffStrategy();\n case 'fixed':\n // No need to destructure 'type', pass the rest\n return createFixedBackoffStrategy(options);\n case 'linear':\n return createLinearBackoffStrategy(options);\n case 'exponential':\n return createExponentialBackoffStrategy(options);\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = options;\n throw new Error('Unknown backoff strategy type');\n }\n }\n}\n","export function promiseWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('Promise timed out'));\n }, timeout);\n\n promise\n .then((result) => {\n clearTimeout(timer);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n","import type { Task, TaskMappingBase } from '..';\n\nexport const ProcessorEvents = {\n /** A task has been claimed by the running processor for handling */\n TASK_CLAIMED: 'taskClaimed',\n /** A task has completed processing and successfully marked as completed */\n TASK_COMPLETED: 'taskCompleted',\n /** A task has failed during processing and being scheduled for retry */\n TASK_RETRY_SCHEDULED: 'taskRetryScheduled',\n /** A task has been marked as FAILED due to process failures exceeding max retries */\n TASK_FAILED: 'taskFailed',\n /** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */\n TASK_COMPLETION_FAILURE: 'taskCompletionFailure',\n /** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */\n UNKNOWN_PROCESSING_ERROR: 'unknownProcessingError',\n} as const;\n\nexport type ProcessorEvents = (typeof ProcessorEvents)[keyof typeof ProcessorEvents];\n\nexport type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = {\n [ProcessorEvents.TASK_CLAIMED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; claimedAt: Date }];\n [ProcessorEvents.TASK_COMPLETED]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; completedAt: Date; startedAt: Date },\n ];\n [ProcessorEvents.TASK_RETRY_SCHEDULED]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; retryScheduledAt: Date; errorAt: Date },\n ];\n [ProcessorEvents.TASK_FAILED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date }];\n [ProcessorEvents.TASK_COMPLETION_FAILURE]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date },\n ];\n [ProcessorEvents.UNKNOWN_PROCESSING_ERROR]: [{ error: unknown; timestamp: Date }];\n};\n","import { EventEmitter } from 'node:events';\nimport { setTimeout } from 'node:timers/promises';\nimport type { BackoffStrategy } from '../backoff-strategy';\nimport type { TaskMappingBase } from '../chrono';\nimport type { Datastore, Task } from '../datastore';\nimport { promiseWithTimeout } from '../utils/promise-utils';\nimport { ProcessorEvents, type ProcessorEventsMap } from './events';\nimport type { Processor } from './processor';\n\nconst DEFAULT_CONFIG: SimpleProcessorConfiguration = {\n maxConcurrency: 1,\n claimIntervalMs: 50,\n idleIntervalMs: 5_000,\n claimStaleTimeoutMs: 10_000,\n taskHandlerTimeoutMs: 5_000,\n taskHandlerMaxRetries: 5,\n processLoopRetryIntervalMs: 20_000,\n};\n\nexport type SimpleProcessorConfiguration = {\n /** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */\n maxConcurrency: number;\n /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */\n claimIntervalMs: number;\n /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */\n claimStaleTimeoutMs: number;\n /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */\n idleIntervalMs: number;\n /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */\n taskHandlerTimeoutMs: number;\n /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */\n taskHandlerMaxRetries: number;\n /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */\n processLoopRetryIntervalMs: number;\n};\n\nconst InternalProcessorEvents = { PROCESSOR_LOOP_EXIT: 'processorLoopExit' } as const;\n\ntype InternalProcessorEventsMap = {\n [InternalProcessorEvents.PROCESSOR_LOOP_EXIT]: [];\n};\n\nexport class SimpleProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n >\n extends EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>\n implements Processor<TaskKind, TaskMapping>\n{\n private config: SimpleProcessorConfiguration;\n\n private exitChannels: EventEmitter<InternalProcessorEventsMap>[] = [];\n private stopRequested = false;\n\n constructor(\n private datastore: Datastore<TaskMapping, DatastoreOptions>,\n private taskKind: TaskKind,\n private handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>,\n private backOffStrategy: BackoffStrategy,\n config?: Partial<SimpleProcessorConfiguration>,\n ) {\n super();\n\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n this.validateConfiguration();\n }\n\n /**\n * Validates that the task handler timeout is less than the claim stale timeout.\n * Throws an error if the validation fails.\n * This ensures that the task handler has enough time to complete before the task is considered stale.\n * This is important to prevent tasks from being claimed again while they are still being processed.\n *\n * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.\n */\n private validateConfiguration() {\n if (this.config.taskHandlerTimeoutMs >= this.config.claimStaleTimeoutMs) {\n throw new Error(\n `Task handler timeout (${this.config.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.config.claimStaleTimeoutMs}ms)`,\n );\n }\n\n if (this.config.claimIntervalMs >= this.config.idleIntervalMs) {\n throw new Error(\n `Claim interval (${this.config.claimIntervalMs}ms) must be less than the idle interval (${this.config.idleIntervalMs}ms)`,\n );\n }\n }\n\n /**\n * Starts multiple concurrent process loops that claim and process tasks.\n * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.\n */\n async start(): Promise<void> {\n if (this.stopRequested || this.exitChannels.length > 0) {\n return;\n }\n\n for (let i = 0; i < this.config.maxConcurrency; i++) {\n const exitChannel = new EventEmitter<InternalProcessorEventsMap>();\n\n this.exitChannels.push(exitChannel);\n this.runProcessLoop(exitChannel);\n }\n }\n\n /**\n * Stops the processor by signaling all process loops to exit,\n * then waits for all process loops to finish before resolving.\n */\n async stop(): Promise<void> {\n const exitPromises = this.exitChannels.map(\n (channel) =>\n new Promise((resolve) => channel.once(InternalProcessorEvents.PROCESSOR_LOOP_EXIT, () => resolve(null))),\n );\n\n this.stopRequested = true;\n\n await Promise.all(exitPromises);\n }\n\n /**\n * The main loop that processes tasks.\n *\n * @param exitChannel The channel to signal when the loop exits.\n */\n private async runProcessLoop(exitChannel: EventEmitter<InternalProcessorEventsMap>): Promise<void> {\n while (!this.stopRequested) {\n try {\n const task = await this.datastore.claim({\n kind: this.taskKind,\n claimStaleTimeoutMs: this.config.claimStaleTimeoutMs,\n });\n\n // If no tasks are available, wait before trying again\n if (!task) {\n await setTimeout(this.config.idleIntervalMs);\n\n continue;\n }\n\n this.emit(ProcessorEvents.TASK_CLAIMED, { task, claimedAt: task.claimedAt || new Date() });\n\n // Process the task using the handler\n await this.handleTask(task);\n\n // Wait a bit before claiming the next task\n await setTimeout(this.config.claimIntervalMs);\n } catch (error) {\n this.emit(ProcessorEvents.UNKNOWN_PROCESSING_ERROR, { error, timestamp: new Date() });\n\n await setTimeout(this.config.processLoopRetryIntervalMs);\n }\n }\n\n exitChannel.emit(InternalProcessorEvents.PROCESSOR_LOOP_EXIT);\n }\n\n /**\n * Handles a task by calling the handler and marking it as complete or failed.\n *\n * Emits:\n * - `task.completed` when the task is successfully completed.\n * - `task.failed` when the task fails.\n * - `task.complete.failed` when the task fails to mark as completed.\n *\n * @param task The task to handle.\n */\n private async handleTask(task: Task<TaskKind, TaskMapping[TaskKind]>) {\n const startedAt = new Date();\n try {\n await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);\n } catch (error) {\n await this.handleTaskError(task, error as Error);\n\n return;\n }\n\n try {\n const completedTask = await this.datastore.complete<TaskKind>(task.id);\n\n this.emit(ProcessorEvents.TASK_COMPLETED, {\n task: completedTask,\n completedAt: completedTask.completedAt || new Date(),\n startedAt,\n });\n } catch (error) {\n this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {\n error: error,\n failedAt: new Date(),\n task,\n });\n }\n }\n\n private async handleTaskError(task: Task<TaskKind, TaskMapping[TaskKind]>, error: Error): Promise<void> {\n const failedAt = new Date();\n if (task.retryCount >= this.config.taskHandlerMaxRetries) {\n // Mark the task as failed\n await this.datastore.fail(task.id);\n this.emit(ProcessorEvents.TASK_FAILED, {\n task,\n error,\n failedAt,\n });\n\n return;\n }\n\n const delay = this.backOffStrategy({ retryAttempt: task.retryCount });\n const retryAt = new Date(Date.now() + delay);\n\n await this.datastore.retry(task.id, retryAt);\n this.emit(ProcessorEvents.TASK_RETRY_SCHEDULED, {\n task,\n error,\n errorAt: failedAt,\n retryScheduledAt: retryAt,\n });\n }\n}\n","import type { TaskMappingBase } from '..';\nimport { type BackoffStrategyOptions, backoffStrategyFactory } from '../backoff-strategy';\nimport type { Datastore, Task } from '../datastore';\nimport type { Processor } from './processor';\nimport { SimpleProcessor, type SimpleProcessorConfiguration } from './simple-processor';\n\n/**\n * Configuration for the processor. Default to simple processor.\n * @default { type: 'simple' } if no configuration is provided.\n */\nexport type ProcessorConfiguration = Partial<SimpleProcessorConfiguration> & { type?: 'simple' };\n\nexport type CreateProcessorInput<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n> = {\n kind: TaskKind;\n datastore: Datastore<TaskMapping, DatastoreOptions>;\n handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;\n configuration?: ProcessorConfiguration;\n backoffStrategyOptions?: BackoffStrategyOptions;\n};\n\nexport function createProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n>(input: CreateProcessorInput<TaskKind, TaskMapping, DatastoreOptions>): Processor<TaskKind, TaskMapping> {\n const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);\n\n const processorType = input.configuration?.type ?? 'simple';\n\n if (processorType === 'simple') {\n return new SimpleProcessor<TaskKind, TaskMapping, DatastoreOptions>(\n input.datastore,\n input.kind,\n input.handler,\n backoffStrategy,\n input.configuration,\n );\n }\n\n const _unreachable: never = processorType;\n\n throw new Error(`Unknown processor type: ${processorType}`);\n}\n","import { EventEmitter } from 'node:events';\n\nimport type { BackoffStrategyOptions } from './backoff-strategy';\nimport type { Datastore, ScheduleInput, Task } from './datastore';\nimport { ChronoEvents, type ChronoEventsMap } from './events';\nimport { createProcessor, type Processor } from './processors';\nimport type { ProcessorConfiguration } from './processors/create-processor';\nimport type { ProcessorEventsMap } from './processors/events';\nimport { promiseWithTimeout } from './utils/promise-utils';\n\nexport type TaskMappingBase = Record<string, unknown>;\n\nexport type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<\n TaskKind,\n TaskData,\n DatastoreOptions\n>;\n\nexport type RegisterTaskHandlerInput<TaskKind, TaskData> = {\n /** The type of task */\n kind: TaskKind;\n /** The handler function to process the task */\n handler: (task: Task<TaskKind, TaskData>) => Promise<void>;\n /** The options for the backoff strategy to use when the task handler fails */\n backoffStrategyOptions?: BackoffStrategyOptions;\n /** The configuration for the processor to use when processing the task */\n processorConfiguration?: ProcessorConfiguration;\n};\n\n/**\n * Response from registering a task handler.\n * @returns The processor instance that can be used to start and stop the processor.\n */\nexport type RegisterTaskHandlerResponse<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;\n\n/**\n * The main class for scheduling and processing tasks.\n * @param datastore - The datastore instance to use for storing and retrieving tasks.\n * @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.\n */\nexport class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {\n private readonly datastore: Datastore<TaskMapping, DatastoreOptions>;\n private readonly processors: Map<keyof TaskMapping, Processor<keyof TaskMapping, TaskMapping>> = new Map();\n\n readonly exitTimeoutMs = 60_000;\n\n constructor(datastore: Datastore<TaskMapping, DatastoreOptions>) {\n super();\n\n this.datastore = datastore;\n }\n\n public async start(): Promise<void> {\n for (const processor of this.processors.values()) {\n await processor.start();\n }\n\n this.emit(ChronoEvents.STARTED, { startedAt: new Date() });\n }\n\n public async stop(): Promise<void> {\n const stopPromises = Array.from(this.processors.values()).map((processor) => processor.stop());\n\n try {\n await promiseWithTimeout(Promise.all(stopPromises), this.exitTimeoutMs);\n } catch (error) {\n this.emit(ChronoEvents.STOP_ABORTED, { error, timestamp: new Date() });\n }\n }\n\n public async scheduleTask<TaskKind extends keyof TaskMapping>(\n input: ScheduleTaskInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const task = await this.datastore.schedule({\n when: input.when,\n kind: input.kind,\n data: input.data,\n datastoreOptions: input.datastoreOptions,\n });\n\n return task;\n }\n\n public async deleteTask<TaskKind extends keyof TaskMapping>(\n taskId: string,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const task = await this.datastore.delete<TaskKind>(taskId);\n\n return task;\n }\n\n public registerTaskHandler<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: RegisterTaskHandlerInput<TaskKind, TaskMapping[TaskKind]>,\n ): RegisterTaskHandlerResponse<TaskKind, TaskMapping> {\n if (this.processors.has(input.kind)) {\n throw new Error('Handler for task kind already exists');\n }\n\n const processor = createProcessor({\n kind: input.kind,\n datastore: this.datastore,\n handler: input.handler,\n backoffStrategyOptions: input.backoffStrategyOptions,\n configuration: input.processorConfiguration,\n });\n\n this.processors.set(input.kind, processor);\n\n return processor;\n }\n}\n","import type { TaskMappingBase } from './chrono';\n\nexport const TaskStatus = {\n PENDING: 'PENDING',\n CLAIMED: 'CLAIMED',\n COMPLETED: 'COMPLETED',\n FAILED: 'FAILED',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport type Task<TaskKind, TaskData> = {\n /** A unique identifier for the task */\n id: string;\n /** A human-readable name or type for the task */\n kind: TaskKind;\n /** The current status of the task */\n status: TaskStatus;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** The original scheduled date when the task was first intended to run */\n originalScheduleDate: Date;\n /** The current scheduled execution date, which may change if rescheduled */\n scheduledAt: Date;\n /** The date the task is mark 'claimed */\n claimedAt?: Date;\n /** The date the task is mark 'completed' */\n completedAt?: Date;\n /** The date when the task was last executed (if any) */\n lastExecutedAt?: Date;\n /** A counter to track the number of times the task has been retried */\n retryCount: number;\n};\n\nexport type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {\n /** The date and time when the task is scheduled to run */\n when: Date;\n /** The type of task */\n kind: TaskKind;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/\n datastoreOptions?: DatastoreOptions;\n};\n\nexport type ClaimTaskInput<TaskKind> = {\n kind: TaskKind;\n claimStaleTimeoutMs: number;\n};\n\nexport type DeleteByIdempotencyKeyInput<TaskKind> = {\n kind: TaskKind;\n idempotencyKey: string;\n};\n\nexport type DeleteOptions = {\n force?: boolean;\n};\n\nexport type DeleteInput<TaskKind> = DeleteByIdempotencyKeyInput<TaskKind> | string;\n\nexport interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {\n schedule<TaskKind extends keyof TaskMapping>(\n input: ScheduleInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n delete<TaskKind extends keyof TaskMapping>(\n taskId: string,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n delete<TaskKind extends keyof TaskMapping>(\n key: DeleteByIdempotencyKeyInput<TaskKind>,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n claim<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: ClaimTaskInput<TaskKind>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n retry<TaskKind extends keyof TaskMapping>(\n taskId: string,\n retryAt: Date,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n}\n"],"mappings":";;;;AAAA,MAAa,eAAe;CAE1B,SAAS;CAET,cAAc;CACf;;;;;;;ACaD,SAAgB,0BAA2C;AACzD,SAAQ,WAAiC;;;;;AAU3C,SAAgB,2BAA2B,QAAqD;AAC9F,SAAQ,WAAiC,OAAO;;;;;;AAclD,SAAgB,4BAA4B,QAAsD;CAChG,MAAM,EAAE,aAAa,cAAc,MAAM;AACzC,SAAQ,UAAgC;AACtC,SAAO,cAAc,MAAM,eAAe;;;;;;;AAgB9C,SAAgB,iCAAiC,QAA2D;CAC1G,MAAM,EAAE,aAAa,aAAa,OAAO,mBAAmB,SAAS,WAAW;AAEhF,SAAQ,UAAgC;EACtC,MAAM,mBAAmB,cAAc,KAAK,MAAM;EAClD,MAAM,cAAc,KAAK,IAAI,kBAAkB,WAAW;AAE1D,UAAQ,QAAR;GACE,KAAK,OAEH,QAAO,KAAK,MAAM,KAAK,QAAQ,GAAG,YAAY;GAChD,KAAK,SAAS;IAEZ,MAAM,YAAY,cAAc;AAChC,WAAO,YAAY,KAAK,QAAQ,GAAG;;GAErC,KAAK,OAEH,QAAO;GACT,QAGE,OAAM,IAAI,MAAM,uDAAuD;;;;AAc/E,MAAM,2BAAmD;CACvD,MAAM;CACN,aAAa;CACd;;;;;;AAOD,SAAgB,uBAAuB,UAAkC,0BAA2C;AAClH,SAAQ,QAAQ,MAAhB;EACE,KAAK,OACH,QAAO,yBAAyB;EAClC,KAAK,QAEH,QAAO,2BAA2B,QAAQ;EAC5C,KAAK,SACH,QAAO,4BAA4B,QAAQ;EAC7C,KAAK,cACH,QAAO,iCAAiC,QAAQ;EAClD,QAGE,OAAM,IAAI,MAAM,gCAAgC;;;;;;AC3HtD,SAAgB,mBAAsB,SAAqB,SAA6B;AACtF,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,QAAQ,iBAAiB;AAC7B,0BAAO,IAAI,MAAM,oBAAoB,CAAC;KACrC,QAAQ;AAEX,UACG,MAAM,WAAW;AAChB,gBAAa,MAAM;AACnB,WAAQ,OAAO;IACf,CACD,OAAO,UAAU;AAChB,gBAAa,MAAM;AACnB,UAAO,MAAM;IACb;GACJ;;;;;ACbJ,MAAa,kBAAkB;CAE7B,cAAc;CAEd,gBAAgB;CAEhB,sBAAsB;CAEtB,aAAa;CAEb,yBAAyB;CAEzB,0BAA0B;CAC3B;;;;ACND,MAAM,iBAA+C;CACnD,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CAChB,qBAAqB;CACrB,sBAAsB;CACtB,uBAAuB;CACvB,4BAA4B;CAC7B;AAmBD,MAAM,0BAA0B,EAAE,qBAAqB,qBAAqB;AAM5E,IAAa,kBAAb,cAKU,aAEV;CACE,AAAQ;CAER,AAAQ,eAA2D,EAAE;CACrE,AAAQ,gBAAgB;CAExB,YACE,AAAQ,WACR,AAAQ,UACR,AAAQ,SACR,AAAQ,iBACR,QACA;AACA,SAAO;EANC;EACA;EACA;EACA;AAKR,OAAK,SAAS;GACZ,GAAG;GACH,GAAG;GACJ;AAED,OAAK,uBAAuB;;;;;;;;;;CAW9B,AAAQ,wBAAwB;AAC9B,MAAI,KAAK,OAAO,wBAAwB,KAAK,OAAO,oBAClD,OAAM,IAAI,MACR,yBAAyB,KAAK,OAAO,qBAAqB,iDAAiD,KAAK,OAAO,oBAAoB,KAC5I;AAGH,MAAI,KAAK,OAAO,mBAAmB,KAAK,OAAO,eAC7C,OAAM,IAAI,MACR,mBAAmB,KAAK,OAAO,gBAAgB,2CAA2C,KAAK,OAAO,eAAe,KACtH;;;;;;CAQL,MAAM,QAAuB;AAC3B,MAAI,KAAK,iBAAiB,KAAK,aAAa,SAAS,EACnD;AAGF,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,OAAO,gBAAgB,KAAK;GACnD,MAAM,cAAc,IAAI,cAA0C;AAElE,QAAK,aAAa,KAAK,YAAY;AACnC,QAAK,eAAe,YAAY;;;;;;;CAQpC,MAAM,OAAsB;EAC1B,MAAM,eAAe,KAAK,aAAa,KACpC,YACC,IAAI,SAAS,YAAY,QAAQ,KAAK,wBAAwB,2BAA2B,QAAQ,KAAK,CAAC,CAAC,CAC3G;AAED,OAAK,gBAAgB;AAErB,QAAM,QAAQ,IAAI,aAAa;;;;;;;CAQjC,MAAc,eAAe,aAAsE;AACjG,SAAO,CAAC,KAAK,cACX,KAAI;GACF,MAAM,OAAO,MAAM,KAAK,UAAU,MAAM;IACtC,MAAM,KAAK;IACX,qBAAqB,KAAK,OAAO;IAClC,CAAC;AAGF,OAAI,CAAC,MAAM;AACT,UAAMA,aAAW,KAAK,OAAO,eAAe;AAE5C;;AAGF,QAAK,KAAK,gBAAgB,cAAc;IAAE;IAAM,WAAW,KAAK,6BAAa,IAAI,MAAM;IAAE,CAAC;AAG1F,SAAM,KAAK,WAAW,KAAK;AAG3B,SAAMA,aAAW,KAAK,OAAO,gBAAgB;WACtC,OAAO;AACd,QAAK,KAAK,gBAAgB,0BAA0B;IAAE;IAAO,2BAAW,IAAI,MAAM;IAAE,CAAC;AAErF,SAAMA,aAAW,KAAK,OAAO,2BAA2B;;AAI5D,cAAY,KAAK,wBAAwB,oBAAoB;;;;;;;;;;;;CAa/D,MAAc,WAAW,MAA6C;EACpE,MAAM,4BAAY,IAAI,MAAM;AAC5B,MAAI;AACF,SAAM,mBAAmB,KAAK,QAAQ,KAAK,EAAE,KAAK,OAAO,qBAAqB;WACvE,OAAO;AACd,SAAM,KAAK,gBAAgB,MAAM,MAAe;AAEhD;;AAGF,MAAI;GACF,MAAM,gBAAgB,MAAM,KAAK,UAAU,SAAmB,KAAK,GAAG;AAEtE,QAAK,KAAK,gBAAgB,gBAAgB;IACxC,MAAM;IACN,aAAa,cAAc,+BAAe,IAAI,MAAM;IACpD;IACD,CAAC;WACK,OAAO;AACd,QAAK,KAAK,gBAAgB,yBAAyB;IAC1C;IACP,0BAAU,IAAI,MAAM;IACpB;IACD,CAAC;;;CAIN,MAAc,gBAAgB,MAA6C,OAA6B;EACtG,MAAM,2BAAW,IAAI,MAAM;AAC3B,MAAI,KAAK,cAAc,KAAK,OAAO,uBAAuB;AAExD,SAAM,KAAK,UAAU,KAAK,KAAK,GAAG;AAClC,QAAK,KAAK,gBAAgB,aAAa;IACrC;IACA;IACA;IACD,CAAC;AAEF;;EAGF,MAAM,QAAQ,KAAK,gBAAgB,EAAE,cAAc,KAAK,YAAY,CAAC;EACrE,MAAM,UAAU,IAAI,KAAK,KAAK,KAAK,GAAG,MAAM;AAE5C,QAAM,KAAK,UAAU,MAAM,KAAK,IAAI,QAAQ;AAC5C,OAAK,KAAK,gBAAgB,sBAAsB;GAC9C;GACA;GACA,SAAS;GACT,kBAAkB;GACnB,CAAC;;;;;;ACvMN,SAAgB,gBAId,OAAwG;CACxG,MAAM,kBAAkB,uBAAuB,MAAM,uBAAuB;CAE5E,MAAM,gBAAgB,MAAM,eAAe,QAAQ;AAEnD,KAAI,kBAAkB,SACpB,QAAO,IAAI,gBACT,MAAM,WACN,MAAM,MACN,MAAM,SACN,iBACA,MAAM,cACP;AAKH,OAAM,IAAI,MAAM,2BAA2B,gBAAgB;;;;;;;;;;ACF7D,IAAa,SAAb,cAAmF,aAA8B;CAC/G,AAAiB;CACjB,AAAiB,6BAAgF,IAAI,KAAK;CAE1G,AAAS,gBAAgB;CAEzB,YAAY,WAAqD;AAC/D,SAAO;AAEP,OAAK,YAAY;;CAGnB,MAAa,QAAuB;AAClC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,OAAM,UAAU,OAAO;AAGzB,OAAK,KAAK,aAAa,SAAS,EAAE,2BAAW,IAAI,MAAM,EAAE,CAAC;;CAG5D,MAAa,OAAsB;EACjC,MAAM,eAAe,MAAM,KAAK,KAAK,WAAW,QAAQ,CAAC,CAAC,KAAK,cAAc,UAAU,MAAM,CAAC;AAE9F,MAAI;AACF,SAAM,mBAAmB,QAAQ,IAAI,aAAa,EAAE,KAAK,cAAc;WAChE,OAAO;AACd,QAAK,KAAK,aAAa,cAAc;IAAE;IAAO,2BAAW,IAAI,MAAM;IAAE,CAAC;;;CAI1E,MAAa,aACX,OACgD;AAQhD,SAPa,MAAM,KAAK,UAAU,SAAS;GACzC,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,kBAAkB,MAAM;GACzB,CAAC;;CAKJ,MAAa,WACX,QAC4D;AAG5D,SAFa,MAAM,KAAK,UAAU,OAAiB,OAAO;;CAK5D,AAAO,oBACL,OACoD;AACpD,MAAI,KAAK,WAAW,IAAI,MAAM,KAAK,CACjC,OAAM,IAAI,MAAM,uCAAuC;EAGzD,MAAM,YAAY,gBAAgB;GAChC,MAAM,MAAM;GACZ,WAAW,KAAK;GAChB,SAAS,MAAM;GACf,wBAAwB,MAAM;GAC9B,eAAe,MAAM;GACtB,CAAC;AAEF,OAAK,WAAW,IAAI,MAAM,MAAM,UAAU;AAE1C,SAAO;;;;;;AC7GX,MAAa,aAAa;CACxB,SAAS;CACT,SAAS;CACT,WAAW;CACX,QAAQ;CACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neofinancial/chrono",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Core package for Chrono task scheduling system",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/neofinancial/chrono.git"
|
|
13
13
|
},
|
|
14
|
-
"main": "./build/index.
|
|
14
|
+
"main": "./build/index.cjs",
|
|
15
15
|
"module": "./build/index.mjs",
|
|
16
|
-
"types": "./build/index.d.
|
|
16
|
+
"types": "./build/index.d.cts",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
19
|
"import": {
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"default": "./build/index.mjs"
|
|
22
22
|
},
|
|
23
23
|
"require": {
|
|
24
|
-
"types": "./build/index.d.
|
|
25
|
-
"default": "./build/index.
|
|
24
|
+
"types": "./build/index.d.cts",
|
|
25
|
+
"default": "./build/index.cjs"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
},
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"author": "Neo Financial Engineering <engineering@neofinancial.com>",
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"scripts": {
|
|
37
|
-
"clean": "rimraf ./build",
|
|
37
|
+
"clean": "rimraf ./build ./node_modules",
|
|
38
38
|
"build": "tsdown",
|
|
39
|
-
"typecheck": "tsc -p ./tsconfig.json --noEmit",
|
|
39
|
+
"typecheck": "tsc -p ./tsconfig.json --noEmit & tsc -p ./test/tsconfig.json --noEmit",
|
|
40
40
|
"test": "NODE_ENV=test TZ=UTC vitest run"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/build/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DEFAULT_BACKOFF_STRATEGY: BackoffStrategyOptions","DEFAULT_CONFIG: SimpleProcessorConfig","EventEmitter","datastore: Datastore<TaskMapping, DatastoreOptions>","taskKind: TaskKind","handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>","backOffStrategy: BackoffStrategy","EventEmitter"],"sources":["../src/events.ts","../src/backoff-strategy.ts","../src/utils/promise-utils.ts","../src/processors/events.ts","../src/processors/simple-processor.ts","../src/processors/create-processor.ts","../src/chrono.ts","../src/datastore.ts"],"sourcesContent":["export const ChronoEvents = {\n /** Chrono instance has started processors and begun polling tasks */\n STARTED: 'started',\n /** Chrono instance has failed to gracefully stop so shutdown has been aborted */\n STOP_ABORTED: 'stopAborted',\n} as const;\n\nexport type ChronoEvents = (typeof ChronoEvents)[keyof typeof ChronoEvents];\n\nexport type ChronoEventsMap = {\n [ChronoEvents.STARTED]: [{ startedAt: Date }];\n [ChronoEvents.STOP_ABORTED]: [{ timestamp: Date; error: unknown }];\n};\n","/** Input for calculating the next backoff delay. */\nexport type BackoffStrategyInput = {\n /** The number of retries already attempted (0 for the first retry). */\n retryAttempt: number;\n};\n\nexport type DelayMs = number;\n\n/**\n * A function that calculates the backoff delay in milliseconds.\n * @param input - Contains information like the current retry number.\n * @returns The delay duration in milliseconds.\n */\nexport type BackoffStrategy = (input: BackoffStrategyInput) => DelayMs;\n\n/**\n * Creates a strategy that provides no delay (immediate retry).\n */\nexport function createNoBackoffStrategy(): BackoffStrategy {\n return (_input: BackoffStrategyInput) => 0;\n}\n\nexport interface FixedBackoffStrategyConfig {\n /** The constant delay in milliseconds. */\n readonly delayMs: number;\n}\n/**\n * Creates a strategy that waits a fixed amount of time.\n */\nexport function createFixedBackoffStrategy(config: FixedBackoffStrategyConfig): BackoffStrategy {\n return (_input: BackoffStrategyInput) => config.delayMs;\n}\n\nexport interface LinearBackoffStrategyConfig {\n /** The base delay for all retires that will be incremented off of */\n readonly baseDelayMs?: number;\n /** The amount to increase the delay by for each subsequent retry in milliseconds. */\n readonly incrementMs: number;\n}\n\n/**\n * Creates a strategy where the delay increases linearly.\n * Delay = (incrementMs * retryAttempt)\n */\nexport function createLinearBackoffStrategy(config: LinearBackoffStrategyConfig): BackoffStrategy {\n const { incrementMs, baseDelayMs = 0 } = config;\n return (input: BackoffStrategyInput) => {\n return baseDelayMs + input.retryAttempt * incrementMs;\n };\n}\n\nexport interface ExponentialBackoffStrategyConfig {\n /** The base delay for the first retry (retryAttempt = 0) in milliseconds. */\n readonly baseDelayMs: number;\n /** The maximum delay in milliseconds. Defaults to Infinity. */\n readonly maxDelayMs?: number;\n /** Type of jitter to apply. Defaults to 'none'. */\n readonly jitter?: 'none' | 'full' | 'equal';\n}\n/**\n * Creates a strategy where the delay increases exponentially, potentially with jitter.\n * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)\n */\nexport function createExponentialBackoffStrategy(config: ExponentialBackoffStrategyConfig): BackoffStrategy {\n const { baseDelayMs, maxDelayMs = Number.POSITIVE_INFINITY, jitter = 'none' } = config;\n\n return (input: BackoffStrategyInput) => {\n const exponentialDelay = baseDelayMs * 2 ** input.retryAttempt;\n const cappedDelay = Math.min(exponentialDelay, maxDelayMs);\n\n switch (jitter) {\n case 'full':\n // Full Jitter: random_between(0, cappedDelay)\n return Math.floor(Math.random() * cappedDelay);\n case 'equal': {\n // Equal Jitter: (cappedDelay / 2) + random_between(0, cappedDelay / 2)\n const halfDelay = cappedDelay / 2;\n return halfDelay + Math.random() * halfDelay;\n }\n case 'none':\n // No Jitter\n return cappedDelay;\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = jitter;\n throw new Error('Unknown jitter type for exponential backoff strategy');\n }\n }\n };\n}\n\nexport type BackoffStrategyType = 'none' | 'fixed' | 'linear' | 'exponential';\n\nexport type BackoffStrategyOptions =\n | { type: 'none' }\n | ({ type: 'fixed' } & FixedBackoffStrategyConfig)\n | ({ type: 'linear' } & LinearBackoffStrategyConfig)\n | ({ type: 'exponential' } & ExponentialBackoffStrategyConfig);\n\nconst DEFAULT_BACKOFF_STRATEGY: BackoffStrategyOptions = {\n type: 'linear',\n incrementMs: 2000,\n} as const;\n\n/**\n * Factory function to create a backoff strategy based on type and configuration.\n * @param options - Configuration object including the strategy type.\n * @returns A BackoffStrategy function.\n */\nexport function backoffStrategyFactory(options: BackoffStrategyOptions = DEFAULT_BACKOFF_STRATEGY): BackoffStrategy {\n switch (options.type) {\n case 'none':\n return createNoBackoffStrategy();\n case 'fixed':\n // No need to destructure 'type', pass the rest\n return createFixedBackoffStrategy(options);\n case 'linear':\n return createLinearBackoffStrategy(options);\n case 'exponential':\n return createExponentialBackoffStrategy(options);\n default: {\n // This should be caught by TypeScript if options type is correct\n const _exhaustiveCheck: never = options;\n throw new Error('Unknown backoff strategy type');\n }\n }\n}\n","export function promiseWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('Promise timed out'));\n }, timeout);\n\n promise\n .then((result) => {\n clearTimeout(timer);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n","import type { Task, TaskMappingBase } from '..';\n\nexport const ProcessorEvents = {\n /** A task has been claimed by the running processor for handling */\n TASK_CLAIMED: 'taskClaimed',\n /** A task has completed processing and successfully marked as completed */\n TASK_COMPLETED: 'taskCompleted',\n /** A task has failed during processing and being scheduled for retry */\n TASK_RETRY_SCHEDULED: 'taskRetryScheduled',\n /** A task has been marked as FAILED due to process failures exceeding max retries */\n TASK_FAILED: 'taskFailed',\n /** A task has been successfully processed but underlying data store failed to mark task as completed. Duplicate processing expected */\n TASK_COMPLETION_FAILURE: 'taskCompletionFailure',\n /** An unknown and uncaught exception occurred in processor. Processing paused for processLoopRetryIntervalMs before continuing */\n UNKNOWN_PROCESSING_ERROR: 'unknownProcessingError',\n} as const;\n\nexport type ProcessorEvents = (typeof ProcessorEvents)[keyof typeof ProcessorEvents];\n\nexport type ProcessorEventsMap<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = {\n [ProcessorEvents.TASK_CLAIMED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; claimedAt: Date }];\n [ProcessorEvents.TASK_COMPLETED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; completedAt: Date }];\n [ProcessorEvents.TASK_RETRY_SCHEDULED]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; retryScheduledAt: Date; errorAt: Date },\n ];\n [ProcessorEvents.TASK_FAILED]: [{ task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date }];\n [ProcessorEvents.TASK_COMPLETION_FAILURE]: [\n { task: Task<TaskKind, TaskMapping[TaskKind]>; error: unknown; failedAt: Date },\n ];\n [ProcessorEvents.UNKNOWN_PROCESSING_ERROR]: [{ error: unknown; timestamp: Date }];\n};\n\nProcessorEvents.TASK_CLAIMED;\n","import { EventEmitter } from 'node:events';\nimport { setTimeout } from 'node:timers/promises';\nimport type { BackoffStrategy } from '../backoff-strategy';\nimport type { TaskMappingBase } from '../chrono';\nimport type { Datastore, Task } from '../datastore';\nimport { promiseWithTimeout } from '../utils/promise-utils';\nimport { ProcessorEvents, type ProcessorEventsMap } from './events';\nimport type { Processor } from './processor';\n\nconst DEFAULT_CONFIG: SimpleProcessorConfig = {\n maxConcurrency: 1,\n claimIntervalMs: 50,\n idleIntervalMs: 5_000,\n claimStaleTimeoutMs: 10_000,\n taskHandlerTimeoutMs: 5_000,\n taskHandlerMaxRetries: 5,\n processLoopRetryIntervalMs: 20_000,\n};\n\ntype SimpleProcessorConfig = {\n /** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */\n maxConcurrency: number;\n /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */\n claimIntervalMs: number;\n /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */\n claimStaleTimeoutMs: number;\n /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */\n idleIntervalMs: number;\n /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */\n taskHandlerTimeoutMs: number;\n /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */\n taskHandlerMaxRetries: number;\n /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */\n processLoopRetryIntervalMs: number;\n};\n\nconst InternalProcessorEvents = { PROCESSOR_LOOP_EXIT: 'processorLoopExit' } as const;\n\ntype InternalProcessorEventsMap = {\n [InternalProcessorEvents.PROCESSOR_LOOP_EXIT]: [];\n};\n\nexport class SimpleProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n >\n extends EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>\n implements Processor<TaskKind, TaskMapping>\n{\n private config: SimpleProcessorConfig;\n\n private exitChannels: EventEmitter<InternalProcessorEventsMap>[] = [];\n private stopRequested = false;\n\n constructor(\n private datastore: Datastore<TaskMapping, DatastoreOptions>,\n private taskKind: TaskKind,\n private handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>,\n private backOffStrategy: BackoffStrategy,\n config?: Partial<SimpleProcessorConfig>,\n ) {\n super();\n\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n this.validatedHandlerTimeout();\n }\n\n /**\n * Validates that the task handler timeout is less than the claim stale timeout.\n * Throws an error if the validation fails.\n * This ensures that the task handler has enough time to complete before the task is considered stale.\n * This is important to prevent tasks from being claimed again while they are still being processed.\n *\n * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.\n */\n private validatedHandlerTimeout() {\n if (this.config.taskHandlerTimeoutMs >= this.config.claimStaleTimeoutMs) {\n throw new Error(\n `Task handler timeout (${this.config.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.config.claimStaleTimeoutMs}ms)`,\n );\n }\n }\n\n /**\n * Starts multiple concurrent process loops that claim and process tasks.\n * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.\n */\n async start(): Promise<void> {\n if (this.stopRequested || this.exitChannels.length > 0) {\n return;\n }\n\n for (let i = 0; i < this.config.maxConcurrency; i++) {\n const exitChannel = new EventEmitter<InternalProcessorEventsMap>();\n\n this.exitChannels.push(exitChannel);\n this.runProcessLoop(exitChannel);\n }\n }\n\n /**\n * Stops the processor by signaling all process loops to exit,\n * then waits for all process loops to finish before resolving.\n */\n async stop(): Promise<void> {\n const exitPromises = this.exitChannels.map(\n (channel) =>\n new Promise((resolve) => channel.once(InternalProcessorEvents.PROCESSOR_LOOP_EXIT, () => resolve(null))),\n );\n\n this.stopRequested = true;\n\n await Promise.all(exitPromises);\n }\n\n /**\n * The main loop that processes tasks.\n *\n * @param exitChannel The channel to signal when the loop exits.\n */\n private async runProcessLoop(exitChannel: EventEmitter<InternalProcessorEventsMap>): Promise<void> {\n while (!this.stopRequested) {\n try {\n const task = await this.datastore.claim({\n kind: this.taskKind,\n claimStaleTimeoutMs: this.config.claimStaleTimeoutMs,\n });\n\n // If no tasks are available, wait before trying again\n if (!task) {\n await setTimeout(this.config.idleIntervalMs);\n\n continue;\n }\n\n this.emit(ProcessorEvents.TASK_CLAIMED, { task, claimedAt: task.claimedAt || new Date() });\n\n // Process the task using the handler\n await this.handleTask(task);\n\n // Wait a bit before claiming the next task\n await setTimeout(this.config.claimIntervalMs);\n } catch (error) {\n this.emit(ProcessorEvents.UNKNOWN_PROCESSING_ERROR, { error, timestamp: new Date() });\n\n await setTimeout(this.config.processLoopRetryIntervalMs);\n }\n }\n\n exitChannel.emit(InternalProcessorEvents.PROCESSOR_LOOP_EXIT);\n }\n\n /**\n * Handles a task by calling the handler and marking it as complete or failed.\n *\n * Emits:\n * - `task.completed` when the task is successfully completed.\n * - `task.failed` when the task fails.\n * - `task.complete.failed` when the task fails to mark as completed.\n *\n * @param task The task to handle.\n */\n private async handleTask(task: Task<TaskKind, TaskMapping[TaskKind]>) {\n try {\n await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);\n } catch (error) {\n await this.handleTaskError(task, error as Error);\n\n return;\n }\n\n try {\n const completedTask = await this.datastore.complete<TaskKind>(task.id);\n\n this.emit(ProcessorEvents.TASK_COMPLETED, {\n task: completedTask,\n completedAt: completedTask.completedAt || new Date(),\n });\n } catch (error) {\n this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {\n error: error,\n failedAt: new Date(),\n task,\n });\n }\n }\n\n private async handleTaskError(task: Task<TaskKind, TaskMapping[TaskKind]>, error: Error): Promise<void> {\n const failedAt = new Date();\n if (task.retryCount >= this.config.taskHandlerMaxRetries) {\n // Mark the task as failed\n await this.datastore.fail(task.id);\n this.emit(ProcessorEvents.TASK_FAILED, {\n task,\n error,\n failedAt,\n });\n\n return;\n }\n\n const delay = this.backOffStrategy({ retryAttempt: task.retryCount });\n const retryAt = new Date(Date.now() + delay);\n\n await this.datastore.retry(task.id, retryAt);\n this.emit(ProcessorEvents.TASK_RETRY_SCHEDULED, {\n task,\n error,\n errorAt: failedAt,\n retryScheduledAt: retryAt,\n });\n }\n}\n","import type { Datastore, Task } from 'datastore';\nimport type { TaskMappingBase } from '..';\nimport { type BackoffStrategyOptions, backoffStrategyFactory } from '../backoff-strategy';\nimport type { Processor } from './processor';\nimport { SimpleProcessor } from './simple-processor';\n\n/**\n * Configuration for the processor.\n */\nexport type ProcessorConfiguration = {\n /** The maximum number of concurrent tasks that the processor will use when processing. @default 1 */\n maxConcurrency?: number;\n /** The interval at which the processor will wait before next poll when the previous poll returned a task @default 50ms */\n claimIntervalMs?: number;\n /** The interval at which the processor will wait before next poll when no tasks are available for processing @default 5000ms */\n idleIntervalMs?: number;\n /** The maximum time a task can be claimed for processing before it will be considered stale and claimed again @default 10000ms */\n claimStaleTimeoutMs?: number;\n /** The maximum time a task handler can take to complete before it will be considered timed out @default 5000ms */\n taskHandlerTimeoutMs?: number;\n /** The maximum number of retries for a task handler, before task is marked as failed. @default 5 */\n taskHandlerMaxRetries?: number;\n /** The interval at which the processor will wait before next poll when an unexpected error occurs @default 20000ms */\n processLoopRetryIntervalMs?: number;\n};\n\nexport type CreateProcessorInput<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n> = {\n kind: TaskKind;\n datastore: Datastore<TaskMapping, DatastoreOptions>;\n handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;\n configuration?: ProcessorConfiguration;\n backoffStrategyOptions?: BackoffStrategyOptions;\n};\n\nexport function createProcessor<\n TaskKind extends Extract<keyof TaskMapping, string>,\n TaskMapping extends TaskMappingBase,\n DatastoreOptions,\n>(input: CreateProcessorInput<TaskKind, TaskMapping, DatastoreOptions>): Processor<TaskKind, TaskMapping> {\n const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);\n // add more processors here\n return new SimpleProcessor<TaskKind, TaskMapping, DatastoreOptions>(\n input.datastore,\n input.kind,\n input.handler,\n backoffStrategy,\n input.configuration,\n );\n}\n","import { EventEmitter } from 'node:events';\n\nimport type { BackoffStrategyOptions } from './backoff-strategy';\nimport type { Datastore, ScheduleInput, Task } from './datastore';\nimport { ChronoEvents, type ChronoEventsMap } from './events';\nimport { createProcessor, type Processor } from './processors';\nimport type { ProcessorConfiguration } from './processors/create-processor';\nimport type { ProcessorEventsMap } from './processors/events';\nimport { promiseWithTimeout } from './utils/promise-utils';\n\nexport type TaskMappingBase = Record<string, unknown>;\n\nexport type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<\n TaskKind,\n TaskData,\n DatastoreOptions\n>;\n\nexport type RegisterTaskHandlerInput<TaskKind, TaskData> = {\n /** The type of task */\n kind: TaskKind;\n /** The handler function to process the task */\n handler: (task: Task<TaskKind, TaskData>) => Promise<void>;\n /** The options for the backoff strategy to use when the task handler fails */\n backoffStrategyOptions?: BackoffStrategyOptions;\n /** The configuration for the processor to use when processing the task */\n processorConfiguration?: ProcessorConfiguration;\n};\n\n/**\n * Response from registering a task handler.\n * @returns The processor instance that can be used to start and stop the processor.\n */\nexport type RegisterTaskHandlerResponse<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;\n\n/**\n * The main class for scheduling and processing tasks.\n * @param datastore - The datastore instance to use for storing and retrieving tasks.\n * @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.\n */\nexport class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {\n private readonly datastore: Datastore<TaskMapping, DatastoreOptions>;\n private readonly processors: Map<keyof TaskMapping, Processor<keyof TaskMapping, TaskMapping>> = new Map();\n\n readonly exitTimeoutMs = 60_000;\n\n constructor(datastore: Datastore<TaskMapping, DatastoreOptions>) {\n super();\n\n this.datastore = datastore;\n }\n\n public async start(): Promise<void> {\n for (const processor of this.processors.values()) {\n await processor.start();\n }\n\n this.emit(ChronoEvents.STARTED, { startedAt: new Date() });\n }\n\n public async stop(): Promise<void> {\n const stopPromises = Array.from(this.processors.values()).map((processor) => processor.stop());\n\n try {\n await promiseWithTimeout(Promise.all(stopPromises), this.exitTimeoutMs);\n } catch (error) {\n this.emit(ChronoEvents.STOP_ABORTED, { error, timestamp: new Date() });\n }\n }\n\n public async scheduleTask<TaskKind extends keyof TaskMapping>(\n input: ScheduleTaskInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>> {\n const task = await this.datastore.schedule({\n when: input.when,\n kind: input.kind,\n data: input.data,\n datastoreOptions: input.datastoreOptions,\n });\n\n return task;\n }\n\n public async deleteTask<TaskKind extends keyof TaskMapping>(\n taskId: string,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined> {\n const task = await this.datastore.delete<TaskKind>(taskId);\n\n return task;\n }\n\n public registerTaskHandler<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: RegisterTaskHandlerInput<TaskKind, TaskMapping[TaskKind]>,\n ): RegisterTaskHandlerResponse<TaskKind, TaskMapping> {\n if (this.processors.has(input.kind)) {\n throw new Error('Handler for task kind already exists');\n }\n\n const processor = createProcessor({\n kind: input.kind,\n datastore: this.datastore,\n handler: input.handler,\n backoffStrategyOptions: input.backoffStrategyOptions,\n configuration: input.processorConfiguration,\n });\n\n this.processors.set(input.kind, processor);\n\n return processor;\n }\n}\n","import type { TaskMappingBase } from './chrono';\n\nexport const TaskStatus = {\n PENDING: 'PENDING',\n CLAIMED: 'CLAIMED',\n COMPLETED: 'COMPLETED',\n FAILED: 'FAILED',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport type Task<TaskKind, TaskData> = {\n /** A unique identifier for the task */\n id: string;\n /** A human-readable name or type for the task */\n kind: TaskKind;\n /** The current status of the task */\n status: TaskStatus;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** The original scheduled date when the task was first intended to run */\n originalScheduleDate: Date;\n /** The current scheduled execution date, which may change if rescheduled */\n scheduledAt: Date;\n /** The date the task is mark 'claimed */\n claimedAt?: Date;\n /** The date the task is mark 'completed' */\n completedAt?: Date;\n /** The date when the task was last executed (if any) */\n lastExecutedAt?: Date;\n /** A counter to track the number of times the task has been retried */\n retryCount: number;\n};\n\nexport type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {\n /** The date and time when the task is scheduled to run */\n when: Date;\n /** The type of task */\n kind: TaskKind;\n /** The payload or data associated with the task */\n data: TaskData;\n /** The priority level of the task (lower numbers can indicate higher priority) */\n priority?: number;\n /** A key used for idempotency to prevent duplicate processing */\n idempotencyKey?: string;\n /** Additional options for the datastore to use when scheduling the task in the datastore. Can include things like a session for database transactions. Unique per datastore implementation.*/\n datastoreOptions?: DatastoreOptions;\n};\n\nexport type ClaimTaskInput<TaskKind> = {\n kind: TaskKind;\n claimStaleTimeoutMs: number;\n};\n\nexport type DeleteByIdempotencyKeyInput<TaskKind> = {\n kind: TaskKind;\n idempotencyKey: string;\n};\n\nexport type DeleteOptions = {\n force?: boolean;\n};\n\nexport type DeleteInput<TaskKind> = DeleteByIdempotencyKeyInput<TaskKind> | string;\n\nexport interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {\n schedule<TaskKind extends keyof TaskMapping>(\n input: ScheduleInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n delete<TaskKind extends keyof TaskMapping>(\n taskId: string,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n delete<TaskKind extends keyof TaskMapping>(\n key: DeleteByIdempotencyKeyInput<TaskKind>,\n options?: DeleteOptions,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n claim<TaskKind extends Extract<keyof TaskMapping, string>>(\n input: ClaimTaskInput<TaskKind>,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;\n retry<TaskKind extends keyof TaskMapping>(\n taskId: string,\n retryAt: Date,\n ): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;\n}\n"],"mappings":"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"}
|