@neofinancial/chrono 0.5.0 → 0.5.2

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