@neofinancial/chrono 0.4.1-next.1 → 0.5.0

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.
Files changed (44) hide show
  1. package/README.md +10 -0
  2. package/build/index.d.mts +217 -0
  3. package/build/index.d.ts +217 -4
  4. package/build/index.js +352 -11
  5. package/build/index.js.map +1 -1
  6. package/build/index.mjs +325 -0
  7. package/build/index.mjs.map +1 -0
  8. package/package.json +17 -4
  9. package/build/backoff-strategy.d.ts +0 -64
  10. package/build/backoff-strategy.js +0 -86
  11. package/build/backoff-strategy.js.map +0 -1
  12. package/build/chrono.d.ts +0 -37
  13. package/build/chrono.js +0 -71
  14. package/build/chrono.js.map +0 -1
  15. package/build/datastore.d.ts +0 -63
  16. package/build/datastore.js +0 -10
  17. package/build/datastore.js.map +0 -1
  18. package/build/events.d.ts +0 -16
  19. package/build/events.js +0 -10
  20. package/build/events.js.map +0 -1
  21. package/build/processors/create-processor.d.ts +0 -31
  22. package/build/processors/create-processor.js +0 -11
  23. package/build/processors/create-processor.js.map +0 -1
  24. package/build/processors/events.d.ts +0 -50
  25. package/build/processors/events.js +0 -19
  26. package/build/processors/events.js.map +0 -1
  27. package/build/processors/index.d.ts +0 -3
  28. package/build/processors/index.js +0 -8
  29. package/build/processors/index.js.map +0 -1
  30. package/build/processors/processor.d.ts +0 -7
  31. package/build/processors/processor.js +0 -3
  32. package/build/processors/processor.js.map +0 -1
  33. package/build/processors/simple-processor.d.ts +0 -63
  34. package/build/processors/simple-processor.js +0 -161
  35. package/build/processors/simple-processor.js.map +0 -1
  36. package/build/scheduler.d.ts +0 -9
  37. package/build/scheduler.js +0 -21
  38. package/build/scheduler.js.map +0 -1
  39. package/build/task.d.ts +0 -33
  40. package/build/task.js +0 -10
  41. package/build/task.js.map +0 -1
  42. package/build/utils/promise-utils.d.ts +0 -1
  43. package/build/utils/promise-utils.js +0 -20
  44. package/build/utils/promise-utils.js.map +0 -1
package/build/index.js CHANGED
@@ -1,12 +1,353 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProcessorEvents = exports.ChronoEvents = exports.TaskStatus = exports.Chrono = void 0;
4
- var chrono_1 = require("./chrono");
5
- Object.defineProperty(exports, "Chrono", { enumerable: true, get: function () { return chrono_1.Chrono; } });
6
- var datastore_1 = require("./datastore");
7
- Object.defineProperty(exports, "TaskStatus", { enumerable: true, get: function () { return datastore_1.TaskStatus; } });
8
- var events_1 = require("./events");
9
- Object.defineProperty(exports, "ChronoEvents", { enumerable: true, get: function () { return events_1.ChronoEvents; } });
10
- var processors_1 = require("./processors");
11
- Object.defineProperty(exports, "ProcessorEvents", { enumerable: true, get: function () { return processors_1.ProcessorEvents; } });
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);
26
+ let node_timers_promises = require("node:timers/promises");
27
+ node_timers_promises = __toESM(node_timers_promises);
28
+
29
+ //#region src/events.ts
30
+ const ChronoEvents = {
31
+ STARTED: "started",
32
+ STOP_ABORTED: "stopAborted"
33
+ };
34
+
35
+ //#endregion
36
+ //#region src/backoff-strategy.ts
37
+ /**
38
+ * Creates a strategy that provides no delay (immediate retry).
39
+ */
40
+ function createNoBackoffStrategy() {
41
+ return (_input) => 0;
42
+ }
43
+ /**
44
+ * Creates a strategy that waits a fixed amount of time.
45
+ */
46
+ function createFixedBackoffStrategy(config) {
47
+ return (_input) => config.delayMs;
48
+ }
49
+ /**
50
+ * Creates a strategy where the delay increases linearly.
51
+ * Delay = (incrementMs * retryAttempt)
52
+ */
53
+ function createLinearBackoffStrategy(config) {
54
+ const { incrementMs, baseDelayMs = 0 } = config;
55
+ return (input) => {
56
+ return baseDelayMs + input.retryAttempt * incrementMs;
57
+ };
58
+ }
59
+ /**
60
+ * Creates a strategy where the delay increases exponentially, potentially with jitter.
61
+ * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)
62
+ */
63
+ function createExponentialBackoffStrategy(config) {
64
+ const { baseDelayMs, maxDelayMs = Number.POSITIVE_INFINITY, jitter = "none" } = config;
65
+ return (input) => {
66
+ const exponentialDelay = baseDelayMs * 2 ** input.retryAttempt;
67
+ const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
68
+ switch (jitter) {
69
+ case "full": return Math.floor(Math.random() * cappedDelay);
70
+ case "equal": {
71
+ const halfDelay = cappedDelay / 2;
72
+ return halfDelay + Math.random() * halfDelay;
73
+ }
74
+ case "none": return cappedDelay;
75
+ default: throw new Error("Unknown jitter type for exponential backoff strategy");
76
+ }
77
+ };
78
+ }
79
+ const DEFAULT_BACKOFF_STRATEGY = {
80
+ type: "linear",
81
+ incrementMs: 2e3
82
+ };
83
+ /**
84
+ * Factory function to create a backoff strategy based on type and configuration.
85
+ * @param options - Configuration object including the strategy type.
86
+ * @returns A BackoffStrategy function.
87
+ */
88
+ function backoffStrategyFactory(options = DEFAULT_BACKOFF_STRATEGY) {
89
+ switch (options.type) {
90
+ case "none": return createNoBackoffStrategy();
91
+ case "fixed": return createFixedBackoffStrategy(options);
92
+ case "linear": return createLinearBackoffStrategy(options);
93
+ case "exponential": return createExponentialBackoffStrategy(options);
94
+ default: throw new Error("Unknown backoff strategy type");
95
+ }
96
+ }
97
+
98
+ //#endregion
99
+ //#region src/utils/promise-utils.ts
100
+ function promiseWithTimeout(promise, timeout) {
101
+ return new Promise((resolve, reject) => {
102
+ const timer = setTimeout(() => {
103
+ reject(/* @__PURE__ */ new Error("Promise timed out"));
104
+ }, timeout);
105
+ promise.then((result) => {
106
+ clearTimeout(timer);
107
+ resolve(result);
108
+ }).catch((error) => {
109
+ clearTimeout(timer);
110
+ reject(error);
111
+ });
112
+ });
113
+ }
114
+
115
+ //#endregion
116
+ //#region src/processors/events.ts
117
+ const ProcessorEvents = {
118
+ TASK_CLAIMED: "taskClaimed",
119
+ TASK_COMPLETED: "taskCompleted",
120
+ TASK_RETRY_SCHEDULED: "taskRetryScheduled",
121
+ TASK_FAILED: "taskFailed",
122
+ TASK_COMPLETION_FAILURE: "taskCompletionFailure",
123
+ UNKNOWN_PROCESSING_ERROR: "unknownProcessingError"
124
+ };
125
+ ProcessorEvents.TASK_CLAIMED;
126
+
127
+ //#endregion
128
+ //#region src/processors/simple-processor.ts
129
+ const DEFAULT_CONFIG = {
130
+ maxConcurrency: 1,
131
+ claimIntervalMs: 50,
132
+ idleIntervalMs: 5e3,
133
+ claimStaleTimeoutMs: 1e4,
134
+ taskHandlerTimeoutMs: 5e3,
135
+ taskHandlerMaxRetries: 5,
136
+ processLoopRetryIntervalMs: 2e4
137
+ };
138
+ const InternalProcessorEvents = { PROCESSOR_LOOP_EXIT: "processorLoopExit" };
139
+ var SimpleProcessor = class extends node_stream.EventEmitter {
140
+ config;
141
+ exitChannels = [];
142
+ stopRequested = false;
143
+ constructor(datastore, taskKind, handler, backOffStrategy, config) {
144
+ super();
145
+ this.datastore = datastore;
146
+ this.taskKind = taskKind;
147
+ this.handler = handler;
148
+ this.backOffStrategy = backOffStrategy;
149
+ this.config = {
150
+ ...DEFAULT_CONFIG,
151
+ ...config
152
+ };
153
+ this.validatedHandlerTimeout();
154
+ }
155
+ /**
156
+ * Validates that the task handler timeout is less than the claim stale timeout.
157
+ * Throws an error if the validation fails.
158
+ * This ensures that the task handler has enough time to complete before the task is considered stale.
159
+ * This is important to prevent tasks from being claimed again while they are still being processed.
160
+ *
161
+ * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.
162
+ */
163
+ validatedHandlerTimeout() {
164
+ 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)`);
165
+ }
166
+ /**
167
+ * Starts multiple concurrent process loops that claim and process tasks.
168
+ * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.
169
+ */
170
+ async start() {
171
+ if (this.stopRequested || this.exitChannels.length > 0) return;
172
+ for (let i = 0; i < this.config.maxConcurrency; i++) {
173
+ const exitChannel = new node_stream.EventEmitter();
174
+ this.exitChannels.push(exitChannel);
175
+ this.runProcessLoop(exitChannel);
176
+ }
177
+ }
178
+ /**
179
+ * Stops the processor by signaling all process loops to exit,
180
+ * then waits for all process loops to finish before resolving.
181
+ */
182
+ async stop() {
183
+ const exitPromises = this.exitChannels.map((channel) => new Promise((resolve) => channel.once(InternalProcessorEvents.PROCESSOR_LOOP_EXIT, () => resolve(null))));
184
+ this.stopRequested = true;
185
+ await Promise.all(exitPromises);
186
+ }
187
+ /**
188
+ * The main loop that processes tasks.
189
+ *
190
+ * @param exitChannel The channel to signal when the loop exits.
191
+ */
192
+ async runProcessLoop(exitChannel) {
193
+ while (!this.stopRequested) try {
194
+ const task = await this.datastore.claim({
195
+ kind: this.taskKind,
196
+ claimStaleTimeoutMs: this.config.claimStaleTimeoutMs
197
+ });
198
+ if (!task) {
199
+ await (0, node_timers_promises.setTimeout)(this.config.idleIntervalMs);
200
+ continue;
201
+ }
202
+ this.emit(ProcessorEvents.TASK_CLAIMED, {
203
+ task,
204
+ claimedAt: task.claimedAt || /* @__PURE__ */ new Date()
205
+ });
206
+ await this.handleTask(task);
207
+ await (0, node_timers_promises.setTimeout)(this.config.claimIntervalMs);
208
+ } catch (error) {
209
+ this.emit(ProcessorEvents.UNKNOWN_PROCESSING_ERROR, {
210
+ error,
211
+ timestamp: /* @__PURE__ */ new Date()
212
+ });
213
+ await (0, node_timers_promises.setTimeout)(this.config.processLoopRetryIntervalMs);
214
+ }
215
+ exitChannel.emit(InternalProcessorEvents.PROCESSOR_LOOP_EXIT);
216
+ }
217
+ /**
218
+ * Handles a task by calling the handler and marking it as complete or failed.
219
+ *
220
+ * Emits:
221
+ * - `task.completed` when the task is successfully completed.
222
+ * - `task.failed` when the task fails.
223
+ * - `task.complete.failed` when the task fails to mark as completed.
224
+ *
225
+ * @param task The task to handle.
226
+ */
227
+ async handleTask(task) {
228
+ try {
229
+ await promiseWithTimeout(this.handler(task), this.config.taskHandlerTimeoutMs);
230
+ } catch (error) {
231
+ await this.handleTaskError(task, error);
232
+ return;
233
+ }
234
+ try {
235
+ const completedTask = await this.datastore.complete(task.id);
236
+ this.emit(ProcessorEvents.TASK_COMPLETED, {
237
+ task: completedTask,
238
+ completedAt: completedTask.completedAt || /* @__PURE__ */ new Date()
239
+ });
240
+ } catch (error) {
241
+ this.emit(ProcessorEvents.TASK_COMPLETION_FAILURE, {
242
+ error,
243
+ failedAt: /* @__PURE__ */ new Date(),
244
+ task
245
+ });
246
+ }
247
+ }
248
+ async handleTaskError(task, error) {
249
+ const failedAt = /* @__PURE__ */ new Date();
250
+ if (task.retryCount >= this.config.taskHandlerMaxRetries) {
251
+ await this.datastore.fail(task.id);
252
+ this.emit(ProcessorEvents.TASK_FAILED, {
253
+ task,
254
+ error,
255
+ failedAt
256
+ });
257
+ return;
258
+ }
259
+ const delay = this.backOffStrategy({ retryAttempt: task.retryCount });
260
+ const retryAt = new Date(Date.now() + delay);
261
+ await this.datastore.retry(task.id, retryAt);
262
+ this.emit(ProcessorEvents.TASK_RETRY_SCHEDULED, {
263
+ task,
264
+ error,
265
+ errorAt: failedAt,
266
+ retryScheduledAt: retryAt
267
+ });
268
+ }
269
+ };
270
+
271
+ //#endregion
272
+ //#region src/processors/create-processor.ts
273
+ function createProcessor(input) {
274
+ const backoffStrategy = backoffStrategyFactory(input.backoffStrategyOptions);
275
+ return new SimpleProcessor(input.datastore, input.kind, input.handler, backoffStrategy, input.configuration);
276
+ }
277
+
278
+ //#endregion
279
+ //#region src/chrono.ts
280
+ /**
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
+ *
290
+ */
291
+ var Chrono = class extends node_stream.EventEmitter {
292
+ datastore;
293
+ processors = /* @__PURE__ */ new Map();
294
+ exitTimeoutMs = 6e4;
295
+ constructor(datastore) {
296
+ super();
297
+ this.datastore = datastore;
298
+ }
299
+ async start() {
300
+ for (const processor of this.processors.values()) await processor.start();
301
+ this.emit(ChronoEvents.STARTED, { startedAt: /* @__PURE__ */ new Date() });
302
+ }
303
+ async stop() {
304
+ const stopPromises = Array.from(this.processors.values()).map((processor) => processor.stop());
305
+ try {
306
+ await promiseWithTimeout(Promise.all(stopPromises), this.exitTimeoutMs);
307
+ } catch (error) {
308
+ this.emit(ChronoEvents.STOP_ABORTED, {
309
+ error,
310
+ timestamp: /* @__PURE__ */ new Date()
311
+ });
312
+ }
313
+ }
314
+ async scheduleTask(input) {
315
+ return await this.datastore.schedule({
316
+ when: input.when,
317
+ kind: input.kind,
318
+ data: input.data,
319
+ datastoreOptions: input.datastoreOptions
320
+ });
321
+ }
322
+ async deleteTask(taskId) {
323
+ return await this.datastore.delete(taskId);
324
+ }
325
+ registerTaskHandler(input) {
326
+ if (this.processors.has(input.kind)) throw new Error("Handler for task kind already exists");
327
+ const processor = createProcessor({
328
+ kind: input.kind,
329
+ datastore: this.datastore,
330
+ handler: input.handler,
331
+ backoffStrategyOptions: input.backoffStrategyOptions,
332
+ configuration: input.processorConfiguration
333
+ });
334
+ this.processors.set(input.kind, processor);
335
+ return processor;
336
+ }
337
+ };
338
+
339
+ //#endregion
340
+ //#region src/datastore.ts
341
+ const TaskStatus = {
342
+ PENDING: "PENDING",
343
+ CLAIMED: "CLAIMED",
344
+ COMPLETED: "COMPLETED",
345
+ FAILED: "FAILED"
346
+ };
347
+
348
+ //#endregion
349
+ exports.Chrono = Chrono;
350
+ exports.ChronoEvents = ChronoEvents;
351
+ exports.ProcessorEvents = ProcessorEvents;
352
+ exports.TaskStatus = TaskStatus;
12
353
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAMkB;AALhB,gGAAA,MAAM,OAAA;AAMR,yCASqB;AADnB,uGAAA,UAAU,OAAA;AAEZ,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AACrB,2CAAwE;AAA/D,6GAAA,eAAe,OAAA"}
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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"}