@monque/core 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/dist/CHANGELOG.md +89 -0
- package/dist/LICENSE +15 -0
- package/dist/README.md +150 -0
- package/dist/index.cjs +6 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -14
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +6 -14
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -7
- package/src/events/index.ts +1 -0
- package/src/events/types.ts +113 -0
- package/src/index.ts +51 -0
- package/src/jobs/guards.ts +220 -0
- package/src/jobs/index.ts +29 -0
- package/src/jobs/types.ts +335 -0
- package/src/scheduler/helpers.ts +107 -0
- package/src/scheduler/index.ts +5 -0
- package/src/scheduler/monque.ts +1309 -0
- package/src/scheduler/services/change-stream-handler.ts +239 -0
- package/src/scheduler/services/index.ts +8 -0
- package/src/scheduler/services/job-manager.ts +455 -0
- package/src/scheduler/services/job-processor.ts +301 -0
- package/src/scheduler/services/job-query.ts +411 -0
- package/src/scheduler/services/job-scheduler.ts +267 -0
- package/src/scheduler/services/types.ts +48 -0
- package/src/scheduler/types.ts +123 -0
- package/src/shared/errors.ts +225 -0
- package/src/shared/index.ts +18 -0
- package/src/shared/utils/backoff.ts +77 -0
- package/src/shared/utils/cron.ts +67 -0
- package/src/shared/utils/index.ts +7 -0
- package/src/workers/index.ts +1 -0
- package/src/workers/types.ts +39 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default base interval for exponential backoff in milliseconds.
|
|
3
|
+
* @default 1000
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_BASE_INTERVAL = 1000;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default maximum delay cap for exponential backoff in milliseconds.
|
|
9
|
+
*
|
|
10
|
+
* This prevents unbounded delays (e.g. failCount=20 is >11 days at 1s base)
|
|
11
|
+
* and avoids precision/overflow issues for very large fail counts.
|
|
12
|
+
* @default 86400000 (24 hours)
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_MAX_BACKOFF_DELAY = 24 * 60 * 60 * 1_000;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Calculate the next run time using exponential backoff.
|
|
18
|
+
*
|
|
19
|
+
* Formula: nextRunAt = now + (2^failCount × baseInterval)
|
|
20
|
+
*
|
|
21
|
+
* @param failCount - Number of previous failed attempts
|
|
22
|
+
* @param baseInterval - Base interval in milliseconds (default: 1000ms)
|
|
23
|
+
* @param maxDelay - Maximum delay in milliseconds (optional)
|
|
24
|
+
* @returns The next run date
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // First retry (failCount=1): 2^1 * 1000 = 2000ms delay
|
|
29
|
+
* const nextRun = calculateBackoff(1);
|
|
30
|
+
*
|
|
31
|
+
* // Second retry (failCount=2): 2^2 * 1000 = 4000ms delay
|
|
32
|
+
* const nextRun = calculateBackoff(2);
|
|
33
|
+
*
|
|
34
|
+
* // With custom base interval
|
|
35
|
+
* const nextRun = calculateBackoff(3, 500); // 2^3 * 500 = 4000ms delay
|
|
36
|
+
*
|
|
37
|
+
* // With max delay
|
|
38
|
+
* const nextRun = calculateBackoff(10, 1000, 60000); // capped at 60000ms
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function calculateBackoff(
|
|
42
|
+
failCount: number,
|
|
43
|
+
baseInterval: number = DEFAULT_BASE_INTERVAL,
|
|
44
|
+
maxDelay?: number,
|
|
45
|
+
): Date {
|
|
46
|
+
const effectiveMaxDelay = maxDelay ?? DEFAULT_MAX_BACKOFF_DELAY;
|
|
47
|
+
let delay = 2 ** failCount * baseInterval;
|
|
48
|
+
|
|
49
|
+
if (delay > effectiveMaxDelay) {
|
|
50
|
+
delay = effectiveMaxDelay;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Date(Date.now() + delay);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calculate just the delay in milliseconds for a given fail count.
|
|
58
|
+
*
|
|
59
|
+
* @param failCount - Number of previous failed attempts
|
|
60
|
+
* @param baseInterval - Base interval in milliseconds (default: 1000ms)
|
|
61
|
+
* @param maxDelay - Maximum delay in milliseconds (optional)
|
|
62
|
+
* @returns The delay in milliseconds
|
|
63
|
+
*/
|
|
64
|
+
export function calculateBackoffDelay(
|
|
65
|
+
failCount: number,
|
|
66
|
+
baseInterval: number = DEFAULT_BASE_INTERVAL,
|
|
67
|
+
maxDelay?: number,
|
|
68
|
+
): number {
|
|
69
|
+
const effectiveMaxDelay = maxDelay ?? DEFAULT_MAX_BACKOFF_DELAY;
|
|
70
|
+
let delay = 2 ** failCount * baseInterval;
|
|
71
|
+
|
|
72
|
+
if (delay > effectiveMaxDelay) {
|
|
73
|
+
delay = effectiveMaxDelay;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return delay;
|
|
77
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
2
|
+
|
|
3
|
+
import { InvalidCronError } from '../errors.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a cron expression and return the next scheduled run date.
|
|
7
|
+
*
|
|
8
|
+
* @param expression - A 5-field cron expression (minute hour day-of-month month day-of-week) or a predefined expression
|
|
9
|
+
* @param currentDate - The reference date for calculating next run (default: now)
|
|
10
|
+
* @returns The next scheduled run date
|
|
11
|
+
* @throws {InvalidCronError} If the cron expression is invalid
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Every minute
|
|
16
|
+
* const nextRun = getNextCronDate('* * * * *');
|
|
17
|
+
*
|
|
18
|
+
* // Every day at midnight
|
|
19
|
+
* const nextRun = getNextCronDate('0 0 * * *');
|
|
20
|
+
*
|
|
21
|
+
* // Using predefined expression
|
|
22
|
+
* const nextRun = getNextCronDate('@daily');
|
|
23
|
+
*
|
|
24
|
+
* // Every Monday at 9am
|
|
25
|
+
* const nextRun = getNextCronDate('0 9 * * 1');
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function getNextCronDate(expression: string, currentDate?: Date): Date {
|
|
29
|
+
try {
|
|
30
|
+
const interval = CronExpressionParser.parse(expression, {
|
|
31
|
+
currentDate: currentDate ?? new Date(),
|
|
32
|
+
});
|
|
33
|
+
return interval.next().toDate();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
handleCronParseError(expression, error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate a cron expression without calculating the next run date.
|
|
41
|
+
*
|
|
42
|
+
* @param expression - A 5-field cron expression
|
|
43
|
+
* @throws {InvalidCronError} If the cron expression is invalid
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* validateCronExpression('0 9 * * 1'); // Throws if invalid
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function validateCronExpression(expression: string): void {
|
|
51
|
+
try {
|
|
52
|
+
CronExpressionParser.parse(expression);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
handleCronParseError(expression, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleCronParseError(expression: string, error: unknown): never {
|
|
59
|
+
/* istanbul ignore next -- @preserve cron-parser always throws Error objects */
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
|
|
61
|
+
throw new InvalidCronError(
|
|
62
|
+
expression,
|
|
63
|
+
`Invalid cron expression "${expression}": ${errorMessage}. ` +
|
|
64
|
+
'Expected 5-field format: "minute hour day-of-month month day-of-week" or predefined expression (e.g. @daily). ' +
|
|
65
|
+
'Example: "0 9 * * 1" (every Monday at 9am)',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { WorkerOptions, WorkerRegistration } from './types.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { JobHandler, PersistedJob } from '@/jobs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for registering a worker.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* monque.register('send-email', emailHandler, {
|
|
9
|
+
* concurrency: 3,
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export interface WorkerOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Number of concurrent jobs this worker can process.
|
|
16
|
+
* @default 5 (uses defaultConcurrency from MonqueOptions)
|
|
17
|
+
*/
|
|
18
|
+
concurrency?: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Allow replacing an existing worker for the same job name.
|
|
22
|
+
* If false (default) and a worker already exists, throws WorkerRegistrationError.
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
replace?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Internal worker registration with handler and options.
|
|
30
|
+
* Tracks the handler, concurrency limit, and currently active jobs.
|
|
31
|
+
*/
|
|
32
|
+
export interface WorkerRegistration<T = unknown> {
|
|
33
|
+
/** The job handler function */
|
|
34
|
+
handler: JobHandler<T>;
|
|
35
|
+
/** Maximum concurrent jobs for this worker */
|
|
36
|
+
concurrency: number;
|
|
37
|
+
/** Map of active job IDs to their job data */
|
|
38
|
+
activeJobs: Map<string, PersistedJob<T>>;
|
|
39
|
+
}
|