@neofinancial/chrono 0.5.0 → 0.5.1
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.d.mts +18 -10
- package/build/index.d.ts +18 -10
- package/build/index.js +8 -14
- package/build/index.js.map +1 -1
- package/build/index.mjs +4 -10
- package/build/index.mjs.map +1 -1
- package/package.json +1 -1
package/build/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventEmitter } from "node:
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
2
|
|
|
3
3
|
//#region src/backoff-strategy.d.ts
|
|
4
4
|
|
|
@@ -65,11 +65,17 @@ type Task<TaskKind, TaskData> = {
|
|
|
65
65
|
retryCount: number;
|
|
66
66
|
};
|
|
67
67
|
type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {
|
|
68
|
+
/** The date and time when the task is scheduled to run */
|
|
68
69
|
when: Date;
|
|
70
|
+
/** The type of task */
|
|
69
71
|
kind: TaskKind;
|
|
72
|
+
/** The payload or data associated with the task */
|
|
70
73
|
data: TaskData;
|
|
74
|
+
/** The priority level of the task (lower numbers can indicate higher priority) */
|
|
71
75
|
priority?: number;
|
|
76
|
+
/** A key used for idempotency to prevent duplicate processing */
|
|
72
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.*/
|
|
73
79
|
datastoreOptions?: DatastoreOptions;
|
|
74
80
|
};
|
|
75
81
|
type ClaimTaskInput<TaskKind> = {
|
|
@@ -184,22 +190,24 @@ type ProcessorConfiguration = {
|
|
|
184
190
|
type TaskMappingBase = Record<string, unknown>;
|
|
185
191
|
type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<TaskKind, TaskData, DatastoreOptions>;
|
|
186
192
|
type RegisterTaskHandlerInput<TaskKind, TaskData> = {
|
|
193
|
+
/** The type of task */
|
|
187
194
|
kind: TaskKind;
|
|
195
|
+
/** The handler function to process the task */
|
|
188
196
|
handler: (task: Task<TaskKind, TaskData>) => Promise<void>;
|
|
197
|
+
/** The options for the backoff strategy to use when the task handler fails */
|
|
189
198
|
backoffStrategyOptions?: BackoffStrategyOptions;
|
|
199
|
+
/** The configuration for the processor to use when processing the task */
|
|
190
200
|
processorConfiguration?: ProcessorConfiguration;
|
|
191
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Response from registering a task handler.
|
|
204
|
+
* @returns The processor instance that can be used to start and stop the processor.
|
|
205
|
+
*/
|
|
192
206
|
type RegisterTaskHandlerResponse<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;
|
|
193
207
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* type TaskMapping = {
|
|
199
|
-
* "async-messaging": { someField: number };
|
|
200
|
-
* "send-email": { url: string };
|
|
201
|
-
* };
|
|
202
|
-
*
|
|
208
|
+
* The main class for scheduling and processing tasks.
|
|
209
|
+
* @param datastore - The datastore instance to use for storing and retrieving tasks.
|
|
210
|
+
* @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.
|
|
203
211
|
*/
|
|
204
212
|
declare class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {
|
|
205
213
|
private readonly datastore;
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventEmitter } from "node:
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
2
|
|
|
3
3
|
//#region src/backoff-strategy.d.ts
|
|
4
4
|
|
|
@@ -65,11 +65,17 @@ type Task<TaskKind, TaskData> = {
|
|
|
65
65
|
retryCount: number;
|
|
66
66
|
};
|
|
67
67
|
type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {
|
|
68
|
+
/** The date and time when the task is scheduled to run */
|
|
68
69
|
when: Date;
|
|
70
|
+
/** The type of task */
|
|
69
71
|
kind: TaskKind;
|
|
72
|
+
/** The payload or data associated with the task */
|
|
70
73
|
data: TaskData;
|
|
74
|
+
/** The priority level of the task (lower numbers can indicate higher priority) */
|
|
71
75
|
priority?: number;
|
|
76
|
+
/** A key used for idempotency to prevent duplicate processing */
|
|
72
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.*/
|
|
73
79
|
datastoreOptions?: DatastoreOptions;
|
|
74
80
|
};
|
|
75
81
|
type ClaimTaskInput<TaskKind> = {
|
|
@@ -184,22 +190,24 @@ type ProcessorConfiguration = {
|
|
|
184
190
|
type TaskMappingBase = Record<string, unknown>;
|
|
185
191
|
type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<TaskKind, TaskData, DatastoreOptions>;
|
|
186
192
|
type RegisterTaskHandlerInput<TaskKind, TaskData> = {
|
|
193
|
+
/** The type of task */
|
|
187
194
|
kind: TaskKind;
|
|
195
|
+
/** The handler function to process the task */
|
|
188
196
|
handler: (task: Task<TaskKind, TaskData>) => Promise<void>;
|
|
197
|
+
/** The options for the backoff strategy to use when the task handler fails */
|
|
189
198
|
backoffStrategyOptions?: BackoffStrategyOptions;
|
|
199
|
+
/** The configuration for the processor to use when processing the task */
|
|
190
200
|
processorConfiguration?: ProcessorConfiguration;
|
|
191
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Response from registering a task handler.
|
|
204
|
+
* @returns The processor instance that can be used to start and stop the processor.
|
|
205
|
+
*/
|
|
192
206
|
type RegisterTaskHandlerResponse<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;
|
|
193
207
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* type TaskMapping = {
|
|
199
|
-
* "async-messaging": { someField: number };
|
|
200
|
-
* "send-email": { url: string };
|
|
201
|
-
* };
|
|
202
|
-
*
|
|
208
|
+
* The main class for scheduling and processing tasks.
|
|
209
|
+
* @param datastore - The datastore instance to use for storing and retrieving tasks.
|
|
210
|
+
* @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.
|
|
203
211
|
*/
|
|
204
212
|
declare class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ChronoEventsMap> {
|
|
205
213
|
private readonly datastore;
|
package/build/index.js
CHANGED
|
@@ -21,8 +21,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
}) : target, mod));
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
-
let
|
|
25
|
-
|
|
24
|
+
let node_events = require("node:events");
|
|
25
|
+
node_events = __toESM(node_events);
|
|
26
26
|
let node_timers_promises = require("node:timers/promises");
|
|
27
27
|
node_timers_promises = __toESM(node_timers_promises);
|
|
28
28
|
|
|
@@ -136,7 +136,7 @@ const DEFAULT_CONFIG = {
|
|
|
136
136
|
processLoopRetryIntervalMs: 2e4
|
|
137
137
|
};
|
|
138
138
|
const InternalProcessorEvents = { PROCESSOR_LOOP_EXIT: "processorLoopExit" };
|
|
139
|
-
var SimpleProcessor = class extends
|
|
139
|
+
var SimpleProcessor = class extends node_events.EventEmitter {
|
|
140
140
|
config;
|
|
141
141
|
exitChannels = [];
|
|
142
142
|
stopRequested = false;
|
|
@@ -170,7 +170,7 @@ var SimpleProcessor = class extends node_stream.EventEmitter {
|
|
|
170
170
|
async start() {
|
|
171
171
|
if (this.stopRequested || this.exitChannels.length > 0) return;
|
|
172
172
|
for (let i = 0; i < this.config.maxConcurrency; i++) {
|
|
173
|
-
const exitChannel = new
|
|
173
|
+
const exitChannel = new node_events.EventEmitter();
|
|
174
174
|
this.exitChannels.push(exitChannel);
|
|
175
175
|
this.runProcessLoop(exitChannel);
|
|
176
176
|
}
|
|
@@ -278,17 +278,11 @@ function createProcessor(input) {
|
|
|
278
278
|
//#endregion
|
|
279
279
|
//#region src/chrono.ts
|
|
280
280
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* type TaskMapping = {
|
|
286
|
-
* "async-messaging": { someField: number };
|
|
287
|
-
* "send-email": { url: string };
|
|
288
|
-
* };
|
|
289
|
-
*
|
|
281
|
+
* The main class for scheduling and processing tasks.
|
|
282
|
+
* @param datastore - The datastore instance to use for storing and retrieving tasks.
|
|
283
|
+
* @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.
|
|
290
284
|
*/
|
|
291
|
-
var Chrono = class extends
|
|
285
|
+
var Chrono = class extends node_events.EventEmitter {
|
|
292
286
|
datastore;
|
|
293
287
|
processors = /* @__PURE__ */ new Map();
|
|
294
288
|
exitTimeoutMs = 6e4;
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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:stream';\nimport { setTimeout } from 'node:timers/promises';\n\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 maxConcurrency: number;\n claimIntervalMs: number;\n claimStaleTimeoutMs: number;\n idleIntervalMs: number;\n taskHandlerTimeoutMs: number;\n taskHandlerMaxRetries: number;\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:stream';\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 kind: TaskKind;\n handler: (task: Task<TaskKind, TaskData>) => Promise<void>;\n backoffStrategyOptions?: BackoffStrategyOptions;\n processorConfiguration?: ProcessorConfiguration;\n};\n\nexport type RegisterTaskHandlerResponse<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;\n\n/**\n * This is a type that represents the mapping of task kinds to their respective data types.\n *\n * Eg. shape of the TaskMapping type:\n *\n * type TaskMapping = {\n * \"async-messaging\": { someField: number };\n * \"send-email\": { url: string };\n * };\n *\n */\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 when: Date;\n kind: TaskKind;\n data: TaskData;\n priority?: number;\n idempotencyKey?: string;\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.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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"}
|
package/build/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventEmitter } from "node:
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
2
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
3
3
|
|
|
4
4
|
//#region src/events.ts
|
|
@@ -253,15 +253,9 @@ function createProcessor(input) {
|
|
|
253
253
|
//#endregion
|
|
254
254
|
//#region src/chrono.ts
|
|
255
255
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* type TaskMapping = {
|
|
261
|
-
* "async-messaging": { someField: number };
|
|
262
|
-
* "send-email": { url: string };
|
|
263
|
-
* };
|
|
264
|
-
*
|
|
256
|
+
* The main class for scheduling and processing tasks.
|
|
257
|
+
* @param datastore - The datastore instance to use for storing and retrieving tasks.
|
|
258
|
+
* @returns The Chrono instance that can be used to start and stop the processors as well as receive chrono instance events.
|
|
265
259
|
*/
|
|
266
260
|
var Chrono = class extends EventEmitter {
|
|
267
261
|
datastore;
|
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:stream';\nimport { setTimeout } from 'node:timers/promises';\n\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 maxConcurrency: number;\n claimIntervalMs: number;\n claimStaleTimeoutMs: number;\n idleIntervalMs: number;\n taskHandlerTimeoutMs: number;\n taskHandlerMaxRetries: number;\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:stream';\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 kind: TaskKind;\n handler: (task: Task<TaskKind, TaskData>) => Promise<void>;\n backoffStrategyOptions?: BackoffStrategyOptions;\n processorConfiguration?: ProcessorConfiguration;\n};\n\nexport type RegisterTaskHandlerResponse<\n TaskKind extends keyof TaskMapping,\n TaskMapping extends TaskMappingBase,\n> = EventEmitter<ProcessorEventsMap<TaskKind, TaskMapping>>;\n\n/**\n * This is a type that represents the mapping of task kinds to their respective data types.\n *\n * Eg. shape of the TaskMapping type:\n *\n * type TaskMapping = {\n * \"async-messaging\": { someField: number };\n * \"send-email\": { url: string };\n * };\n *\n */\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 when: Date;\n kind: TaskKind;\n data: TaskData;\n priority?: number;\n idempotencyKey?: string;\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":["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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"}
|