@neofinancial/chrono 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Neo Financial
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,64 @@
1
+ /** Input for calculating the next backoff delay. */
2
+ export type BackoffStrategyInput = {
3
+ /** The number of retries already attempted (0 for the first retry). */
4
+ retryAttempt: number;
5
+ };
6
+ export type DelayMs = number;
7
+ /**
8
+ * A function that calculates the backoff delay in milliseconds.
9
+ * @param input - Contains information like the current retry number.
10
+ * @returns The delay duration in milliseconds.
11
+ */
12
+ export type BackoffStrategy = (input: BackoffStrategyInput) => DelayMs;
13
+ /**
14
+ * Creates a strategy that provides no delay (immediate retry).
15
+ */
16
+ export declare function createNoBackoffStrategy(): BackoffStrategy;
17
+ export interface FixedBackoffStrategyConfig {
18
+ /** The constant delay in milliseconds. */
19
+ readonly delayMs: number;
20
+ }
21
+ /**
22
+ * Creates a strategy that waits a fixed amount of time.
23
+ */
24
+ export declare function createFixedBackoffStrategy(config: FixedBackoffStrategyConfig): BackoffStrategy;
25
+ export interface LinearBackoffStrategyConfig {
26
+ /** The base delay for all retires that will be incremented off of */
27
+ readonly baseDelayMs?: number;
28
+ /** The amount to increase the delay by for each subsequent retry in milliseconds. */
29
+ readonly incrementMs: number;
30
+ }
31
+ /**
32
+ * Creates a strategy where the delay increases linearly.
33
+ * Delay = (incrementMs * retryAttempt)
34
+ */
35
+ export declare function createLinearBackoffStrategy(config: LinearBackoffStrategyConfig): BackoffStrategy;
36
+ export interface ExponentialBackoffStrategyConfig {
37
+ /** The base delay for the first retry (retryAttempt = 0) in milliseconds. */
38
+ readonly baseDelayMs: number;
39
+ /** The maximum delay in milliseconds. Defaults to Infinity. */
40
+ readonly maxDelayMs?: number;
41
+ /** Type of jitter to apply. Defaults to 'none'. */
42
+ readonly jitter?: 'none' | 'full' | 'equal';
43
+ }
44
+ /**
45
+ * Creates a strategy where the delay increases exponentially, potentially with jitter.
46
+ * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)
47
+ */
48
+ export declare function createExponentialBackoffStrategy(config: ExponentialBackoffStrategyConfig): BackoffStrategy;
49
+ export type BackoffStrategyType = 'none' | 'fixed' | 'linear' | 'exponential';
50
+ export type BackoffStrategyOptions = {
51
+ type: 'none';
52
+ } | ({
53
+ type: 'fixed';
54
+ } & FixedBackoffStrategyConfig) | ({
55
+ type: 'linear';
56
+ } & LinearBackoffStrategyConfig) | ({
57
+ type: 'exponential';
58
+ } & ExponentialBackoffStrategyConfig);
59
+ /**
60
+ * Factory function to create a backoff strategy based on type and configuration.
61
+ * @param options - Configuration object including the strategy type.
62
+ * @returns A BackoffStrategy function.
63
+ */
64
+ export declare function backoffStrategyFactory(options?: BackoffStrategyOptions): BackoffStrategy;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNoBackoffStrategy = createNoBackoffStrategy;
4
+ exports.createFixedBackoffStrategy = createFixedBackoffStrategy;
5
+ exports.createLinearBackoffStrategy = createLinearBackoffStrategy;
6
+ exports.createExponentialBackoffStrategy = createExponentialBackoffStrategy;
7
+ exports.backoffStrategyFactory = backoffStrategyFactory;
8
+ /**
9
+ * Creates a strategy that provides no delay (immediate retry).
10
+ */
11
+ function createNoBackoffStrategy() {
12
+ return (_input) => 0;
13
+ }
14
+ /**
15
+ * Creates a strategy that waits a fixed amount of time.
16
+ */
17
+ function createFixedBackoffStrategy(config) {
18
+ return (_input) => config.delayMs;
19
+ }
20
+ /**
21
+ * Creates a strategy where the delay increases linearly.
22
+ * Delay = (incrementMs * retryAttempt)
23
+ */
24
+ function createLinearBackoffStrategy(config) {
25
+ const { incrementMs, baseDelayMs = 0 } = config;
26
+ return (input) => {
27
+ return baseDelayMs + input.retryAttempt * incrementMs;
28
+ };
29
+ }
30
+ /**
31
+ * Creates a strategy where the delay increases exponentially, potentially with jitter.
32
+ * Base Delay Formula = baseDelayMs * (2 ** retryAttempt)
33
+ */
34
+ function createExponentialBackoffStrategy(config) {
35
+ const { baseDelayMs, maxDelayMs = Number.POSITIVE_INFINITY, jitter = 'none' } = config;
36
+ return (input) => {
37
+ const exponentialDelay = baseDelayMs * 2 ** input.retryAttempt;
38
+ const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
39
+ switch (jitter) {
40
+ case 'full':
41
+ // Full Jitter: random_between(0, cappedDelay)
42
+ return Math.floor(Math.random() * cappedDelay);
43
+ case 'equal': {
44
+ // Equal Jitter: (cappedDelay / 2) + random_between(0, cappedDelay / 2)
45
+ const halfDelay = cappedDelay / 2;
46
+ return halfDelay + Math.random() * halfDelay;
47
+ }
48
+ case 'none':
49
+ // No Jitter
50
+ return cappedDelay;
51
+ default: {
52
+ // This should be caught by TypeScript if options type is correct
53
+ const _exhaustiveCheck = jitter;
54
+ throw new Error('Unknown jitter type for exponential backoff strategy');
55
+ }
56
+ }
57
+ };
58
+ }
59
+ const DEFAULT_BACKOFF_STRATEGY = {
60
+ type: 'linear',
61
+ incrementMs: 2000,
62
+ };
63
+ /**
64
+ * Factory function to create a backoff strategy based on type and configuration.
65
+ * @param options - Configuration object including the strategy type.
66
+ * @returns A BackoffStrategy function.
67
+ */
68
+ function backoffStrategyFactory(options = DEFAULT_BACKOFF_STRATEGY) {
69
+ switch (options.type) {
70
+ case 'none':
71
+ return createNoBackoffStrategy();
72
+ case 'fixed':
73
+ // No need to destructure 'type', pass the rest
74
+ return createFixedBackoffStrategy(options);
75
+ case 'linear':
76
+ return createLinearBackoffStrategy(options);
77
+ case 'exponential':
78
+ return createExponentialBackoffStrategy(options);
79
+ default: {
80
+ // This should be caught by TypeScript if options type is correct
81
+ const _exhaustiveCheck = options;
82
+ throw new Error('Unknown backoff strategy type');
83
+ }
84
+ }
85
+ }
86
+ //# sourceMappingURL=backoff-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backoff-strategy.js","sourceRoot":"","sources":["../src/backoff-strategy.ts"],"names":[],"mappings":";;AAkBA,0DAEC;AASD,gEAEC;AAaD,kEAKC;AAcD,4EA0BC;AAoBD,wDAiBC;AA/GD;;GAEG;AACH,SAAgB,uBAAuB;IACrC,OAAO,CAAC,MAA4B,EAAE,EAAE,CAAC,CAAC,CAAC;AAC7C,CAAC;AAMD;;GAEG;AACH,SAAgB,0BAA0B,CAAC,MAAkC;IAC3E,OAAO,CAAC,MAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;AAC1D,CAAC;AASD;;;GAGG;AACH,SAAgB,2BAA2B,CAAC,MAAmC;IAC7E,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC;IAChD,OAAO,CAAC,KAA2B,EAAE,EAAE;QACrC,OAAO,WAAW,GAAG,KAAK,CAAC,YAAY,GAAG,WAAW,CAAC;IACxD,CAAC,CAAC;AACJ,CAAC;AAUD;;;GAGG;AACH,SAAgB,gCAAgC,CAAC,MAAwC;IACvF,MAAM,EAAE,WAAW,EAAE,UAAU,GAAG,MAAM,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC;IAEvF,OAAO,CAAC,KAA2B,EAAE,EAAE;QACrC,MAAM,gBAAgB,GAAG,WAAW,GAAG,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC;QAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;QAE3D,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,MAAM;gBACT,8CAA8C;gBAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC;YACjD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,uEAAuE;gBACvE,MAAM,SAAS,GAAG,WAAW,GAAG,CAAC,CAAC;gBAClC,OAAO,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC;YAC/C,CAAC;YACD,KAAK,MAAM;gBACT,YAAY;gBACZ,OAAO,WAAW,CAAC;YACrB,OAAO,CAAC,CAAC,CAAC;gBACR,iEAAiE;gBACjE,MAAM,gBAAgB,GAAU,MAAM,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAUD,MAAM,wBAAwB,GAA2B;IACvD,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,IAAI;CACT,CAAC;AAEX;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,UAAkC,wBAAwB;IAC/F,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,uBAAuB,EAAE,CAAC;QACnC,KAAK,OAAO;YACV,+CAA+C;YAC/C,OAAO,0BAA0B,CAAC,OAAO,CAAC,CAAC;QAC7C,KAAK,QAAQ;YACX,OAAO,2BAA2B,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,aAAa;YAChB,OAAO,gCAAgC,CAAC,OAAO,CAAC,CAAC;QACnD,OAAO,CAAC,CAAC,CAAC;YACR,iEAAiE;YACjE,MAAM,gBAAgB,GAAU,OAAO,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { EventEmitter } from 'node:stream';
2
+ import type { BackoffStrategyOptions } from './backoff-strategy';
3
+ import type { Datastore, ScheduleInput, Task } from './datastore';
4
+ import { type Processor } from './processors';
5
+ import type { ProcessorConfiguration } from './processors/create-processor';
6
+ export type TaskMappingBase = Record<string, unknown>;
7
+ export type ScheduleTaskInput<TaskKind, TaskData, DatastoreOptions> = ScheduleInput<TaskKind, TaskData, DatastoreOptions>;
8
+ export type RegisterTaskHandlerInput<TaskKind, TaskData> = {
9
+ kind: TaskKind;
10
+ handler: (task: Task<TaskKind, TaskData>) => Promise<void>;
11
+ backoffStrategyOptions?: BackoffStrategyOptions;
12
+ processorConfiguration?: ProcessorConfiguration;
13
+ };
14
+ /**
15
+ * This is a type that represents the mapping of task kinds to their respective data types.
16
+ *
17
+ * Eg. shape of the TaskMapping type:
18
+ *
19
+ * type TaskMapping = {
20
+ * "async-messaging": { someField: number };
21
+ * "send-email": { url: string };
22
+ * };
23
+ *
24
+ */
25
+ export declare class Chrono<TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter {
26
+ private datastore;
27
+ private processors;
28
+ readonly exitTimeoutMs = 60000;
29
+ constructor(datastore: Datastore<TaskMapping, DatastoreOptions>);
30
+ start(): Promise<void>;
31
+ stop(): Promise<void>;
32
+ scheduleTask<TaskKind extends keyof TaskMapping>(input: ScheduleTaskInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
33
+ deleteTask<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
34
+ registerTaskHandler<TaskKind extends Extract<keyof TaskMapping, string>>(input: RegisterTaskHandlerInput<TaskKind, TaskMapping[TaskKind]>): Processor<TaskKind, TaskMapping>;
35
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Chrono = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const processors_1 = require("./processors");
6
+ const promise_utils_1 = require("./utils/promise-utils");
7
+ /**
8
+ * This is a type that represents the mapping of task kinds to their respective data types.
9
+ *
10
+ * Eg. shape of the TaskMapping type:
11
+ *
12
+ * type TaskMapping = {
13
+ * "async-messaging": { someField: number };
14
+ * "send-email": { url: string };
15
+ * };
16
+ *
17
+ */
18
+ class Chrono extends node_stream_1.EventEmitter {
19
+ datastore;
20
+ processors = new Map();
21
+ exitTimeoutMs = 60_000;
22
+ constructor(datastore) {
23
+ super();
24
+ this.datastore = datastore;
25
+ }
26
+ async start() {
27
+ for (const processor of this.processors.values()) {
28
+ await processor.start();
29
+ }
30
+ this.emit('ready', { timestamp: new Date() });
31
+ }
32
+ async stop() {
33
+ const stopPromises = Array.from(this.processors.values()).map((processor) => processor.stop());
34
+ try {
35
+ await (0, promise_utils_1.promiseWithTimeout)(Promise.all(stopPromises), this.exitTimeoutMs);
36
+ this.emit('stopped', { timestamp: new Date() });
37
+ }
38
+ catch (error) {
39
+ this.emit('stop.failed', { error, timestamp: new Date() });
40
+ }
41
+ finally {
42
+ this.emit('close', { timestamp: new Date() });
43
+ }
44
+ }
45
+ async scheduleTask(input) {
46
+ const task = await this.datastore.schedule({
47
+ when: input.when,
48
+ kind: input.kind,
49
+ data: input.data,
50
+ datastoreOptions: input.datastoreOptions,
51
+ });
52
+ return task;
53
+ }
54
+ async deleteTask(taskId) {
55
+ const task = await this.datastore.delete(taskId);
56
+ return task;
57
+ }
58
+ registerTaskHandler(input) {
59
+ if (this.processors.has(input.kind)) {
60
+ throw new Error('Handler for task kind already exists');
61
+ }
62
+ const processor = (0, processors_1.createProcessor)({
63
+ kind: input.kind,
64
+ datastore: this.datastore,
65
+ handler: input.handler,
66
+ configuration: input.processorConfiguration,
67
+ });
68
+ this.processors.set(input.kind, processor);
69
+ return processor;
70
+ }
71
+ }
72
+ exports.Chrono = Chrono;
73
+ //# sourceMappingURL=chrono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrono.js","sourceRoot":"","sources":["../src/chrono.ts"],"names":[],"mappings":";;;AAAA,6CAA2C;AAI3C,6CAA+D;AAE/D,yDAA2D;AAiB3D;;;;;;;;;;GAUG;AAEH,MAAa,MAA8D,SAAQ,0BAAY;IACrF,SAAS,CAA2C;IACpD,UAAU,GAAsE,IAAI,GAAG,EAAE,CAAC;IAEzF,aAAa,GAAG,MAAM,CAAC;IAEhC,YAAY,SAAmD;QAC7D,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAEM,KAAK,CAAC,KAAK;QAChB,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YACjD,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QAE/F,IAAI,CAAC;YACH,MAAM,IAAA,kCAAkB,EAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YAExE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAC7D,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,YAAY,CACvB,KAA2E;QAE3E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YACzC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;SACzC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK,CAAC,UAAU,CACrB,MAAc;QAEd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAW,MAAM,CAAC,CAAC;QAE3D,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,mBAAmB,CACxB,KAAgE;QAEhE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,SAAS,GAAG,IAAA,4BAAe,EAAC;YAChC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,aAAa,EAAE,KAAK,CAAC,sBAAsB;SAC5C,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAE3C,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAzED,wBAyEC"}
@@ -0,0 +1,63 @@
1
+ import type { TaskMappingBase } from './chrono';
2
+ export declare const TaskStatus: {
3
+ readonly PENDING: "PENDING";
4
+ readonly CLAIMED: "CLAIMED";
5
+ readonly COMPLETED: "COMPLETED";
6
+ readonly FAILED: "FAILED";
7
+ };
8
+ export type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];
9
+ export type Task<TaskKind, TaskData> = {
10
+ /** A unique identifier for the task */
11
+ id: string;
12
+ /** A human-readable name or type for the task */
13
+ kind: TaskKind;
14
+ /** The current status of the task */
15
+ status: TaskStatus;
16
+ /** The payload or data associated with the task */
17
+ data: TaskData;
18
+ /** The priority level of the task (lower numbers can indicate higher priority) */
19
+ priority?: number;
20
+ /** A key used for idempotency to prevent duplicate processing */
21
+ idempotencyKey?: string;
22
+ /** The original scheduled date when the task was first intended to run */
23
+ originalScheduleDate: Date;
24
+ /** The current scheduled execution date, which may change if rescheduled */
25
+ scheduledAt: Date;
26
+ /** The date the task is mark 'claimed */
27
+ claimedAt?: Date;
28
+ /** The date the task is mark 'completed' */
29
+ completedAt?: Date;
30
+ /** The date when the task was last executed (if any) */
31
+ lastExecutedAt?: Date;
32
+ /** A counter to track the number of times the task has been retried */
33
+ retryCount: number;
34
+ };
35
+ export type ScheduleInput<TaskKind, TaskData, DatastoreOptions> = {
36
+ when: Date;
37
+ kind: TaskKind;
38
+ data: TaskData;
39
+ priority?: number;
40
+ idempotencyKey?: string;
41
+ datastoreOptions?: DatastoreOptions;
42
+ };
43
+ export type ClaimTaskInput<TaskKind> = {
44
+ kind: TaskKind;
45
+ claimStaleTimeoutMs: number;
46
+ };
47
+ export type DeleteByIdempotencyKeyInput<TaskKind> = {
48
+ kind: TaskKind;
49
+ idempotencyKey: string;
50
+ };
51
+ export type DeleteOptions = {
52
+ force?: boolean;
53
+ };
54
+ export type DeleteInput<TaskKind> = DeleteByIdempotencyKeyInput<TaskKind> | string;
55
+ export interface Datastore<TaskMapping extends TaskMappingBase, DatastoreOptions> {
56
+ schedule<TaskKind extends keyof TaskMapping>(input: ScheduleInput<TaskKind, TaskMapping[TaskKind], DatastoreOptions>): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
57
+ delete<TaskKind extends keyof TaskMapping>(taskId: string, options?: DeleteOptions): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
58
+ delete<TaskKind extends keyof TaskMapping>(key: DeleteByIdempotencyKeyInput<TaskKind>, options?: DeleteOptions): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
59
+ claim<TaskKind extends Extract<keyof TaskMapping, string>>(input: ClaimTaskInput<TaskKind>): Promise<Task<TaskKind, TaskMapping[TaskKind]> | undefined>;
60
+ unclaim<TaskKind extends keyof TaskMapping>(taskId: string, nextScheduledAt: Date): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
61
+ complete<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
62
+ fail<TaskKind extends keyof TaskMapping>(taskId: string): Promise<Task<TaskKind, TaskMapping[TaskKind]>>;
63
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskStatus = void 0;
4
+ exports.TaskStatus = {
5
+ PENDING: 'PENDING',
6
+ CLAIMED: 'CLAIMED',
7
+ COMPLETED: 'COMPLETED',
8
+ FAILED: 'FAILED',
9
+ };
10
+ //# sourceMappingURL=datastore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datastore.js","sourceRoot":"","sources":["../src/datastore.ts"],"names":[],"mappings":";;;AAEa,QAAA,UAAU,GAAG;IACxB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;CACR,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Chrono, type ScheduleTaskInput, type TaskMappingBase } from './chrono';
2
+ export { TaskStatus, type ClaimTaskInput, type Datastore, type ScheduleInput, type Task, type DeleteInput, type DeleteOptions, type DeleteByIdempotencyKeyInput } from './datastore';
package/build/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ 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
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAgF;AAAvE,gGAAA,MAAM,OAAA;AACf,yCAAsL;AAA7K,uGAAA,UAAU,OAAA"}
@@ -0,0 +1,20 @@
1
+ import type { Datastore, Task } from 'datastore';
2
+ import type { TaskMappingBase } from '..';
3
+ import { type BackoffStrategyOptions } from '../backoff-strategy';
4
+ import type { Processor } from './processor';
5
+ export type ProcessorConfiguration = {
6
+ maxConcurrency?: number;
7
+ claimIntervalMs?: number;
8
+ claimStaleTimeoutMs?: number;
9
+ idleIntervalMs?: number;
10
+ taskHandlerTimeoutMs?: number;
11
+ taskHandlerMaxRetries?: number;
12
+ };
13
+ export type CreateProcessorInput<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase, DatastoreOptions> = {
14
+ kind: TaskKind;
15
+ datastore: Datastore<TaskMapping, DatastoreOptions>;
16
+ handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;
17
+ configuration?: ProcessorConfiguration;
18
+ backoffStrategyOptions?: BackoffStrategyOptions;
19
+ };
20
+ export declare function createProcessor<TaskKind extends Extract<keyof TaskMapping, string>, TaskMapping extends TaskMappingBase, DatastoreOptions>(input: CreateProcessorInput<TaskKind, TaskMapping, DatastoreOptions>): Processor<TaskKind, TaskMapping>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createProcessor = createProcessor;
4
+ const backoff_strategy_1 = require("../backoff-strategy");
5
+ const simple_processor_1 = require("./simple-processor");
6
+ function createProcessor(input) {
7
+ const backoffStrategy = (0, backoff_strategy_1.backoffStrategyFactory)(input.backoffStrategyOptions);
8
+ // add more processors here
9
+ return new simple_processor_1.SimpleProcessor({
10
+ datastore: input.datastore,
11
+ kind: input.kind,
12
+ handler: input.handler,
13
+ maxConcurrency: input.configuration?.maxConcurrency,
14
+ backoffStrategy,
15
+ claimIntervalMs: input.configuration?.claimIntervalMs,
16
+ idleIntervalMs: input.configuration?.idleIntervalMs,
17
+ taskHandlerTimeoutMs: input.configuration?.taskHandlerTimeoutMs,
18
+ claimStaleTimeoutMs: input.configuration?.claimStaleTimeoutMs,
19
+ taskHandlerMaxRetries: input.configuration?.taskHandlerMaxRetries,
20
+ });
21
+ }
22
+ //# sourceMappingURL=create-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-processor.js","sourceRoot":"","sources":["../../src/processors/create-processor.ts"],"names":[],"mappings":";;AA2BA,0CAmBC;AA5CD,0DAA0F;AAE1F,yDAAqD;AAuBrD,SAAgB,eAAe,CAI7B,KAAoE;IACpE,MAAM,eAAe,GAAG,IAAA,yCAAsB,EAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC7E,2BAA2B;IAC3B,OAAO,IAAI,kCAAe,CAA0C;QAClE,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,cAAc,EAAE,KAAK,CAAC,aAAa,EAAE,cAAc;QACnD,eAAe;QACf,eAAe,EAAE,KAAK,CAAC,aAAa,EAAE,eAAe;QACrD,cAAc,EAAE,KAAK,CAAC,aAAa,EAAE,cAAc;QACnD,oBAAoB,EAAE,KAAK,CAAC,aAAa,EAAE,oBAAoB;QAC/D,mBAAmB,EAAE,KAAK,CAAC,aAAa,EAAE,mBAAmB;QAC7D,qBAAqB,EAAE,KAAK,CAAC,aAAa,EAAE,qBAAqB;KAClE,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createProcessor } from './create-processor';
2
+ export type { Processor } from './processor';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createProcessor = void 0;
4
+ var create_processor_1 = require("./create-processor");
5
+ Object.defineProperty(exports, "createProcessor", { enumerable: true, get: function () { return create_processor_1.createProcessor; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/processors/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAA5C,mHAAA,eAAe,OAAA"}
@@ -0,0 +1,35 @@
1
+ import type { EventEmitter } from 'node:stream';
2
+ import type { Task, TaskMappingBase } from '..';
3
+ export type ProcessorEvents<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> = {
4
+ 'task:claimed': [{
5
+ task: Task<TaskKind, TaskMapping[TaskKind]>;
6
+ timestamp: Date;
7
+ }];
8
+ 'task:completed': [{
9
+ task: Task<TaskKind, TaskMapping[TaskKind]>;
10
+ timestamp: Date;
11
+ }];
12
+ 'task:failed': [{
13
+ task: Task<TaskKind, TaskMapping[TaskKind]>;
14
+ error: Error;
15
+ timestamp: Date;
16
+ }];
17
+ 'task:unclaimed': [{
18
+ task: Task<TaskKind, TaskMapping[TaskKind]>;
19
+ error: Error;
20
+ timestamp: Date;
21
+ }];
22
+ 'task:completion:failed': [{
23
+ task: Task<TaskKind, TaskMapping[TaskKind]>;
24
+ error: Error;
25
+ timestamp: Date;
26
+ }];
27
+ 'processloop:error': [{
28
+ error: Error;
29
+ timestamp: Date;
30
+ }];
31
+ };
32
+ export interface Processor<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase> extends EventEmitter<ProcessorEvents<TaskKind, TaskMapping>> {
33
+ start(): Promise<void>;
34
+ stop(): Promise<void>;
35
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processor.js","sourceRoot":"","sources":["../../src/processors/processor.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { EventEmitter } from 'node:stream';
2
+ import type { BackoffStrategy } from '../backoff-strategy';
3
+ import type { TaskMappingBase } from '../chrono';
4
+ import type { Datastore, Task } from '../datastore';
5
+ import type { Processor, ProcessorEvents } from './processor';
6
+ type SimpleProcessorConfig<TaskKind extends keyof TaskMapping, TaskMapping extends TaskMappingBase, DatastoreOptions> = {
7
+ datastore: Datastore<TaskMapping, DatastoreOptions>;
8
+ kind: TaskKind;
9
+ handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;
10
+ maxConcurrency?: number;
11
+ backoffStrategy: BackoffStrategy;
12
+ claimIntervalMs?: number;
13
+ claimStaleTimeoutMs?: number;
14
+ idleIntervalMs?: number;
15
+ taskHandlerTimeoutMs?: number;
16
+ taskHandlerMaxRetries?: number;
17
+ };
18
+ export declare class SimpleProcessor<TaskKind extends Extract<keyof TaskMapping, string>, TaskMapping extends TaskMappingBase, DatastoreOptions> extends EventEmitter<ProcessorEvents<TaskKind, TaskMapping>> implements Processor<TaskKind, TaskMapping> {
19
+ readonly taskKind: TaskKind;
20
+ readonly datastore: Datastore<TaskMapping, DatastoreOptions>;
21
+ readonly handler: (task: Task<TaskKind, TaskMapping[TaskKind]>) => Promise<void>;
22
+ private maxConcurrency;
23
+ private backOffStrategy;
24
+ readonly claimIntervalMs: number;
25
+ readonly claimStaleTimeoutMs: number;
26
+ readonly idleIntervalMs: number;
27
+ readonly taskHandlerTimeoutMs: number;
28
+ readonly taskHandlerMaxRetries: number;
29
+ private exitChannels;
30
+ private stopRequested;
31
+ constructor(config: SimpleProcessorConfig<TaskKind, TaskMapping, DatastoreOptions>);
32
+ /**
33
+ * Validates that the task handler timeout is less than the claim stale timeout.
34
+ * Throws an error if the validation fails.
35
+ * This ensures that the task handler has enough time to complete before the task is considered stale.
36
+ * This is important to prevent tasks from being claimed again while they are still being processed.
37
+ *
38
+ * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.
39
+ */
40
+ private validatedHandlerTimeout;
41
+ /**
42
+ * Starts multiple concurrent process loops that claim and process tasks.
43
+ * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.
44
+ */
45
+ start(): Promise<void>;
46
+ /**
47
+ * Stops the processor by signaling all process loops to exit,
48
+ * then waits for all process loops to finish before resolving.
49
+ */
50
+ stop(): Promise<void>;
51
+ /**
52
+ * The main loop that processes tasks.
53
+ *
54
+ * @param exitChannel The channel to signal when the loop exits.
55
+ */
56
+ private runProcessLoop;
57
+ /**
58
+ * Handles a task by calling the handler and marking it as complete or failed.
59
+ *
60
+ * Emits:
61
+ * - `task.completed` when the task is successfully completed.
62
+ * - `task.failed` when the task fails.
63
+ * - `task.complete.failed` when the task fails to mark as completed.
64
+ *
65
+ * @param task The task to handle.
66
+ */
67
+ private handleTask;
68
+ private handleTaskError;
69
+ }
70
+ export {};
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SimpleProcessor = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const promises_1 = require("node:timers/promises");
6
+ const promise_utils_1 = require("../utils/promise-utils");
7
+ const DEFAULT_MAX_CONCURRENCY = 1;
8
+ const DEFAULT_CLAIM_INTERVAL_MS = 50;
9
+ const DEFAULT_CLAIM_STALE_TIMEOUT_MS = 10_000;
10
+ const DEFAULT_IDLE_INTERVAL_MS = 5_000;
11
+ const DEFAULT_TASK_HANDLER_TIMEOUT_MS = 5_000;
12
+ const DEFAULT_TASK_HANDLER_MAX_RETRIES = 10;
13
+ class SimpleProcessor extends node_stream_1.EventEmitter {
14
+ taskKind;
15
+ datastore;
16
+ handler;
17
+ maxConcurrency;
18
+ backOffStrategy;
19
+ claimIntervalMs;
20
+ claimStaleTimeoutMs;
21
+ idleIntervalMs;
22
+ taskHandlerTimeoutMs;
23
+ taskHandlerMaxRetries;
24
+ exitChannels = [];
25
+ stopRequested = false;
26
+ constructor(config) {
27
+ super();
28
+ this.datastore = config.datastore;
29
+ this.handler = config.handler;
30
+ this.taskKind = config.kind;
31
+ this.backOffStrategy = config.backoffStrategy;
32
+ this.maxConcurrency = config.maxConcurrency || DEFAULT_MAX_CONCURRENCY;
33
+ this.claimIntervalMs = config.claimIntervalMs || DEFAULT_CLAIM_INTERVAL_MS;
34
+ this.claimStaleTimeoutMs = config.claimStaleTimeoutMs || DEFAULT_CLAIM_STALE_TIMEOUT_MS;
35
+ this.idleIntervalMs = config.idleIntervalMs || DEFAULT_IDLE_INTERVAL_MS;
36
+ this.taskHandlerTimeoutMs = config.taskHandlerTimeoutMs || DEFAULT_TASK_HANDLER_TIMEOUT_MS;
37
+ this.taskHandlerMaxRetries = config.taskHandlerMaxRetries || DEFAULT_TASK_HANDLER_MAX_RETRIES;
38
+ this.validatedHandlerTimeout();
39
+ }
40
+ /**
41
+ * Validates that the task handler timeout is less than the claim stale timeout.
42
+ * Throws an error if the validation fails.
43
+ * This ensures that the task handler has enough time to complete before the task is considered stale.
44
+ * This is important to prevent tasks from being claimed again while they are still being processed.
45
+ *
46
+ * @throws {Error} If the task handler timeout is greater than or equal to the claim stale timeout.
47
+ */
48
+ validatedHandlerTimeout() {
49
+ if (this.taskHandlerTimeoutMs >= this.claimStaleTimeoutMs) {
50
+ throw new Error(`Task handler timeout (${this.taskHandlerTimeoutMs}ms) must be less than the claim stale timeout (${this.claimStaleTimeoutMs}ms)`);
51
+ }
52
+ }
53
+ /**
54
+ * Starts multiple concurrent process loops that claim and process tasks.
55
+ * Max concurrent processes is defined by the `maxConcurrency` property set in the constructor.
56
+ */
57
+ async start() {
58
+ if (this.stopRequested || this.exitChannels.length > 0) {
59
+ return;
60
+ }
61
+ for (let i = 0; i < this.maxConcurrency; i++) {
62
+ const exitChannel = new node_stream_1.EventEmitter();
63
+ this.exitChannels.push(exitChannel);
64
+ this.runProcessLoop(exitChannel);
65
+ }
66
+ }
67
+ /**
68
+ * Stops the processor by signaling all process loops to exit,
69
+ * then waits for all process loops to finish before resolving.
70
+ */
71
+ async stop() {
72
+ const exitPromises = this.exitChannels.map((channel) => new Promise((resolve) => channel.once('processloop.exit', resolve)));
73
+ this.stopRequested = true;
74
+ await Promise.all(exitPromises);
75
+ }
76
+ /**
77
+ * The main loop that processes tasks.
78
+ *
79
+ * @param exitChannel The channel to signal when the loop exits.
80
+ */
81
+ async runProcessLoop(exitChannel) {
82
+ while (!this.stopRequested) {
83
+ try {
84
+ const task = await this.datastore.claim({
85
+ kind: this.taskKind,
86
+ claimStaleTimeoutMs: this.claimStaleTimeoutMs,
87
+ });
88
+ // If no tasks are available, wait before trying again
89
+ if (!task) {
90
+ await (0, promises_1.setTimeout)(this.idleIntervalMs);
91
+ continue;
92
+ }
93
+ this.emit('task:claimed', { task, timestamp: new Date() });
94
+ // Process the task using the handler
95
+ await this.handleTask(task);
96
+ // Wait a bit before claiming the next task
97
+ await (0, promises_1.setTimeout)(this.claimIntervalMs);
98
+ }
99
+ catch (error) {
100
+ this.emit('processloop:error', { error: error, timestamp: new Date() });
101
+ }
102
+ }
103
+ exitChannel.emit('processloop:exit');
104
+ }
105
+ /**
106
+ * Handles a task by calling the handler and marking it as complete or failed.
107
+ *
108
+ * Emits:
109
+ * - `task.completed` when the task is successfully completed.
110
+ * - `task.failed` when the task fails.
111
+ * - `task.complete.failed` when the task fails to mark as completed.
112
+ *
113
+ * @param task The task to handle.
114
+ */
115
+ async handleTask(task) {
116
+ try {
117
+ await (0, promise_utils_1.promiseWithTimeout)(this.handler(task), this.taskHandlerTimeoutMs);
118
+ }
119
+ catch (error) {
120
+ await this.handleTaskError(task, error);
121
+ return;
122
+ }
123
+ try {
124
+ const completedTask = await this.datastore.complete(task.id);
125
+ this.emit('task:completed', {
126
+ task: completedTask,
127
+ timestamp: completedTask.completedAt || new Date(),
128
+ });
129
+ }
130
+ catch (error) {
131
+ this.emit('task:completion:failed', {
132
+ error: error,
133
+ task,
134
+ timestamp: new Date(),
135
+ });
136
+ }
137
+ }
138
+ async handleTaskError(task, error) {
139
+ if (task.retryCount >= this.taskHandlerMaxRetries) {
140
+ // Mark the task as failed
141
+ await this.datastore.fail(task.id);
142
+ this.emit('task:failed', {
143
+ task,
144
+ error,
145
+ timestamp: new Date(),
146
+ });
147
+ return;
148
+ }
149
+ const delay = this.backOffStrategy({ retryAttempt: task.retryCount });
150
+ const nextScheduledAt = new Date(Date.now() + delay);
151
+ await this.datastore.unclaim(task.id, nextScheduledAt);
152
+ this.emit('task:unclaimed', {
153
+ task,
154
+ error,
155
+ timestamp: new Date(),
156
+ });
157
+ }
158
+ }
159
+ exports.SimpleProcessor = SimpleProcessor;
160
+ //# sourceMappingURL=simple-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simple-processor.js","sourceRoot":"","sources":["../../src/processors/simple-processor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface Task<T> {
2
+ run(): Promise<T>;
3
+ }
4
+ export declare class Scheduler<T> {
5
+ #private;
6
+ constructor();
7
+ schedule(task: Task<T>): Promise<boolean>;
8
+ run(): Promise<boolean>;
9
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scheduler = void 0;
4
+ class Scheduler {
5
+ #tasks = [];
6
+ constructor() {
7
+ this.#tasks = [];
8
+ }
9
+ async schedule(task) {
10
+ this.#tasks.push(task);
11
+ return true;
12
+ }
13
+ async run() {
14
+ for (const task of this.#tasks) {
15
+ task.run();
16
+ }
17
+ return true;
18
+ }
19
+ }
20
+ exports.Scheduler = Scheduler;
21
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":";;;AAIA,MAAa,SAAS;IACpB,MAAM,GAAc,EAAE,CAAC;IAEvB;QACE,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,IAAa;QACjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEvB,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK,CAAC,GAAG;QACd,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AApBD,8BAoBC"}
@@ -0,0 +1,33 @@
1
+ export declare const TaskStatus: {
2
+ readonly PENDING: "PENDING";
3
+ readonly CLAIMED: "CLAIMED";
4
+ readonly COMPLETED: "COMPLETED";
5
+ readonly FAILED: "FAILED";
6
+ };
7
+ export type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];
8
+ export type Task<TaskKind, TaskData> = {
9
+ /** A unique identifier for the task */
10
+ id: string;
11
+ /** A human-readable name or type for the task */
12
+ kind: TaskKind;
13
+ /** The current status of the task */
14
+ status: TaskStatus;
15
+ /** The payload or data associated with the task */
16
+ data: TaskData;
17
+ /** The priority level of the task (lower numbers can indicate higher priority) */
18
+ priority?: number;
19
+ /** A key used for idempotency to prevent duplicate processing */
20
+ idempotencyKey?: string;
21
+ /** The original scheduled date when the task was first intended to run */
22
+ originalScheduleDate: Date;
23
+ /** The current scheduled execution date, which may change if rescheduled */
24
+ scheduledAt: Date;
25
+ /** The date the task is mark 'claimed */
26
+ claimedAt?: Date;
27
+ /** The date the task is mark 'completed' */
28
+ completedAt?: Date;
29
+ /** The date when the task was last executed (if any) */
30
+ lastExecutedAt?: Date;
31
+ /** A counter to track the number of times the task has been retried */
32
+ retryCount: number;
33
+ };
package/build/task.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskStatus = void 0;
4
+ exports.TaskStatus = {
5
+ PENDING: "PENDING",
6
+ CLAIMED: "CLAIMED",
7
+ COMPLETED: "COMPLETED",
8
+ FAILED: "FAILED",
9
+ };
10
+ //# sourceMappingURL=task.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.js","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":";;;AAAa,QAAA,UAAU,GAAG;IACxB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;CACR,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function promiseWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promiseWithTimeout = promiseWithTimeout;
4
+ function promiseWithTimeout(promise, timeout) {
5
+ return new Promise((resolve, reject) => {
6
+ const timer = setTimeout(() => {
7
+ reject(new Error('Promise timed out'));
8
+ }, timeout);
9
+ promise
10
+ .then((result) => {
11
+ clearTimeout(timer);
12
+ resolve(result);
13
+ })
14
+ .catch((error) => {
15
+ clearTimeout(timer);
16
+ reject(error);
17
+ });
18
+ });
19
+ }
20
+ //# sourceMappingURL=promise-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promise-utils.js","sourceRoot":"","sources":["../../src/utils/promise-utils.ts"],"names":[],"mappings":";;AAAA,gDAgBC;AAhBD,SAAgB,kBAAkB,CAAI,OAAmB,EAAE,OAAe;IACxE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;QACzC,CAAC,EAAE,OAAO,CAAC,CAAC;QAEZ,OAAO;aACJ,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@neofinancial/chrono",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "build/index.js",
10
+ "types": "build/index.d.ts",
11
+ "files": [
12
+ "build/**"
13
+ ],
14
+ "keywords": [],
15
+ "author": "Neo Financial Engineering <engineering@neofinancial.com>",
16
+ "license": "MIT",
17
+ "scripts": {
18
+ "clean": "rimraf ./build",
19
+ "build": "tsc",
20
+ "typecheck": "tsc -p ./tsconfig.json --noEmit",
21
+ "test": "NODE_ENV=test TZ=UTC vitest run"
22
+ }
23
+ }