@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monque/core",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "MongoDB-backed job scheduler with atomic locking, exponential backoff, and cron scheduling",
5
5
  "author": "Maurice de Bruyn <debruyn.maurice@gmail.com>",
6
6
  "repository": {
@@ -8,6 +8,7 @@
8
8
  "url": "git+https://github.com/ueberBrot/monque.git",
9
9
  "directory": "packages/core"
10
10
  },
11
+ "license": "ISC",
11
12
  "bugs": {
12
13
  "url": "https://github.com/ueberBrot/monque/issues"
13
14
  },
@@ -36,7 +37,8 @@
36
37
  }
37
38
  },
38
39
  "files": [
39
- "dist"
40
+ "dist",
41
+ "src"
40
42
  ],
41
43
  "scripts": {
42
44
  "build": "tsdown",
@@ -66,7 +68,6 @@
66
68
  "atomic",
67
69
  "persistence"
68
70
  ],
69
- "license": "ISC",
70
71
  "dependencies": {
71
72
  "cron-parser": "^5.5.0"
72
73
  },
@@ -79,12 +80,13 @@
79
80
  "@testcontainers/mongodb": "^11.11.0",
80
81
  "@total-typescript/ts-reset": "^0.6.1",
81
82
  "@types/bun": "^1.3.6",
82
- "@vitest/coverage-v8": "^4.0.17",
83
+ "@vitest/coverage-v8": "^4.0.18",
83
84
  "fishery": "^2.4.0",
84
85
  "mongodb": "^7.0.0",
85
- "publint": "^0.3.16",
86
- "tsdown": "^0.19.0",
86
+ "publint": "^0.3.17",
87
+ "tsdown": "^0.20.1",
87
88
  "typescript": "^5.9.3",
88
- "vitest": "^4.0.17"
89
+ "unplugin-unused": "^0.5.7",
90
+ "vitest": "^4.0.18"
89
91
  }
90
92
  }
@@ -0,0 +1 @@
1
+ export type { MonqueEventMap } from './types.js';
@@ -0,0 +1,113 @@
1
+ import type { Job } from '@/jobs';
2
+
3
+ /**
4
+ * Event payloads for Monque lifecycle events.
5
+ */
6
+ export interface MonqueEventMap {
7
+ /**
8
+ * Emitted when a job begins processing.
9
+ */
10
+ 'job:start': Job;
11
+
12
+ /**
13
+ * Emitted when a job finishes successfully.
14
+ */
15
+ 'job:complete': {
16
+ job: Job;
17
+ /** Processing duration in milliseconds */
18
+ duration: number;
19
+ };
20
+
21
+ /**
22
+ * Emitted when a job fails (may retry).
23
+ */
24
+ 'job:fail': {
25
+ job: Job;
26
+ error: Error;
27
+ /** Whether the job will be retried */
28
+ willRetry: boolean;
29
+ };
30
+
31
+ /**
32
+ * Emitted for unexpected errors during processing.
33
+ */
34
+ 'job:error': {
35
+ error: Error;
36
+ job?: Job;
37
+ };
38
+
39
+ /**
40
+ * Emitted when stale jobs are recovered on startup.
41
+ */
42
+ 'stale:recovered': {
43
+ count: number;
44
+ };
45
+
46
+ /**
47
+ * Emitted when the change stream is successfully connected.
48
+ */
49
+ 'changestream:connected': undefined;
50
+
51
+ /**
52
+ * Emitted when a change stream error occurs.
53
+ */
54
+ 'changestream:error': {
55
+ error: Error;
56
+ };
57
+
58
+ /**
59
+ * Emitted when the change stream is closed.
60
+ */
61
+ 'changestream:closed': undefined;
62
+
63
+ /**
64
+ * Emitted when falling back from change streams to polling-only mode.
65
+ */
66
+ 'changestream:fallback': {
67
+ reason: string;
68
+ };
69
+ /**
70
+ * Emitted when a job is manually cancelled.
71
+ */
72
+ 'job:cancelled': {
73
+ job: Job;
74
+ };
75
+
76
+ /**
77
+ * Emitted when a job is manually retried.
78
+ */
79
+ 'job:retried': {
80
+ job: Job;
81
+ previousStatus: 'failed' | 'cancelled';
82
+ };
83
+
84
+ /**
85
+ * Emitted when a job is manually deleted.
86
+ */
87
+ 'job:deleted': {
88
+ jobId: string;
89
+ };
90
+
91
+ /**
92
+ * Emitted when multiple jobs are cancelled in bulk.
93
+ */
94
+ 'jobs:cancelled': {
95
+ jobIds: string[];
96
+ count: number;
97
+ };
98
+
99
+ /**
100
+ * Emitted when multiple jobs are retried in bulk.
101
+ */
102
+ 'jobs:retried': {
103
+ jobIds: string[];
104
+ count: number;
105
+ };
106
+
107
+ /**
108
+ * Emitted when multiple jobs are deleted in bulk.
109
+ */
110
+ 'jobs:deleted': {
111
+ count: number;
112
+ };
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ // Types - Events
2
+ export type { MonqueEventMap } from '@/events';
3
+ // Types - Jobs
4
+ export {
5
+ type BulkOperationResult,
6
+ CursorDirection,
7
+ type CursorOptions,
8
+ type CursorPage,
9
+ type EnqueueOptions,
10
+ type GetJobsFilter,
11
+ isCancelledJob,
12
+ isCompletedJob,
13
+ isFailedJob,
14
+ isPendingJob,
15
+ isPersistedJob,
16
+ isProcessingJob,
17
+ isRecurringJob,
18
+ isValidJobStatus,
19
+ type Job,
20
+ type JobHandler,
21
+ type JobSelector,
22
+ JobStatus,
23
+ type JobStatusType,
24
+ type PersistedJob,
25
+ type QueueStats,
26
+ type ScheduleOptions,
27
+ } from '@/jobs';
28
+ // Types - Scheduler
29
+ export type { MonqueOptions } from '@/scheduler';
30
+ // Main class
31
+ export { Monque } from '@/scheduler';
32
+ // Errors
33
+ // Utilities (for advanced use cases)
34
+ export {
35
+ AggregationTimeoutError,
36
+ ConnectionError,
37
+ calculateBackoff,
38
+ calculateBackoffDelay,
39
+ DEFAULT_BASE_INTERVAL,
40
+ DEFAULT_MAX_BACKOFF_DELAY,
41
+ getNextCronDate,
42
+ InvalidCronError,
43
+ InvalidCursorError,
44
+ JobStateError,
45
+ MonqueError,
46
+ ShutdownTimeoutError,
47
+ validateCronExpression,
48
+ WorkerRegistrationError,
49
+ } from '@/shared';
50
+ // Types - Workers
51
+ export type { WorkerOptions } from '@/workers';
@@ -0,0 +1,220 @@
1
+ import type { Job, JobStatusType, PersistedJob } from './types.js';
2
+ import { JobStatus } from './types.js';
3
+
4
+ /**
5
+ * Type guard to check if a job has been persisted to MongoDB.
6
+ *
7
+ * A persisted job is guaranteed to have an `_id` field, which means it has been
8
+ * successfully inserted into the database. This is useful when you need to ensure
9
+ * a job can be updated or referenced by its ID.
10
+ *
11
+ * @template T - The type of the job's data payload
12
+ * @param job - The job to check
13
+ * @returns `true` if the job has a valid `_id`, narrowing the type to `PersistedJob<T>`
14
+ *
15
+ * @example Basic usage
16
+ * ```typescript
17
+ * const job: Job<EmailData> = await monque.enqueue('send-email', emailData);
18
+ *
19
+ * if (isPersistedJob(job)) {
20
+ * // TypeScript knows job._id exists
21
+ * console.log(`Job ID: ${job._id.toString()}`);
22
+ * }
23
+ * ```
24
+ *
25
+ * @example In a conditional
26
+ * ```typescript
27
+ * function logJobId(job: Job) {
28
+ * if (!isPersistedJob(job)) {
29
+ * console.log('Job not yet persisted');
30
+ * return;
31
+ * }
32
+ * // TypeScript knows job is PersistedJob here
33
+ * console.log(`Processing job ${job._id.toString()}`);
34
+ * }
35
+ * ```
36
+ */
37
+ export function isPersistedJob<T>(job: Job<T>): job is PersistedJob<T> {
38
+ return '_id' in job && job._id !== undefined && job._id !== null;
39
+ }
40
+
41
+ /**
42
+ * Type guard to check if a value is a valid job status.
43
+ *
44
+ * Validates that a value is one of the five valid job statuses: `'pending'`,
45
+ * `'processing'`, `'completed'`, `'failed'`, or `'cancelled'`. Useful for runtime validation
46
+ * of user input or external data.
47
+ *
48
+ * @param value - The value to check
49
+ * @returns `true` if the value is a valid `JobStatusType`, narrowing the type
50
+ *
51
+ * @example Validating user input
52
+ * ```typescript
53
+ * function filterByStatus(status: string) {
54
+ * if (!isValidJobStatus(status)) {
55
+ * throw new Error(`Invalid status: ${status}`);
56
+ * }
57
+ * // TypeScript knows status is JobStatusType here
58
+ * return db.jobs.find({ status });
59
+ * }
60
+ * ```
61
+ *
62
+ * @example Runtime validation
63
+ * ```typescript
64
+ * const statusFromApi = externalData.status;
65
+ *
66
+ * if (isValidJobStatus(statusFromApi)) {
67
+ * job.status = statusFromApi;
68
+ * } else {
69
+ * job.status = JobStatus.PENDING;
70
+ * }
71
+ * ```
72
+ */
73
+ export function isValidJobStatus(value: unknown): value is JobStatusType {
74
+ return typeof value === 'string' && Object.values(JobStatus).includes(value as JobStatusType);
75
+ }
76
+
77
+ /**
78
+ * Type guard to check if a job is in pending status.
79
+ *
80
+ * A convenience helper for checking if a job is waiting to be processed.
81
+ * Equivalent to `job.status === JobStatus.PENDING` but with better semantics.
82
+ *
83
+ * @template T - The type of the job's data payload
84
+ * @param job - The job to check
85
+ * @returns `true` if the job status is `'pending'`
86
+ *
87
+ * @example Filter pending jobs
88
+ * ```typescript
89
+ * const jobs = await monque.getJobs();
90
+ * const pendingJobs = jobs.filter(isPendingJob);
91
+ * console.log(`${pendingJobs.length} jobs waiting to be processed`);
92
+ * ```
93
+ *
94
+ * @example Conditional logic
95
+ * ```typescript
96
+ * if (isPendingJob(job)) {
97
+ * await monque.now(job.name, job.data);
98
+ * }
99
+ * ```
100
+ */
101
+ export function isPendingJob<T>(job: Job<T>): boolean {
102
+ return job.status === JobStatus.PENDING;
103
+ }
104
+
105
+ /**
106
+ * Type guard to check if a job is currently being processed.
107
+ *
108
+ * A convenience helper for checking if a job is actively running.
109
+ * Equivalent to `job.status === JobStatus.PROCESSING` but with better semantics.
110
+ *
111
+ * @template T - The type of the job's data payload
112
+ * @param job - The job to check
113
+ * @returns `true` if the job status is `'processing'`
114
+ *
115
+ * @example Monitor active jobs
116
+ * ```typescript
117
+ * const jobs = await monque.getJobs();
118
+ * const activeJobs = jobs.filter(isProcessingJob);
119
+ * console.log(`${activeJobs.length} jobs currently running`);
120
+ * ```
121
+ */
122
+ export function isProcessingJob<T>(job: Job<T>): boolean {
123
+ return job.status === JobStatus.PROCESSING;
124
+ }
125
+
126
+ /**
127
+ * Type guard to check if a job has completed successfully.
128
+ *
129
+ * A convenience helper for checking if a job finished without errors.
130
+ * Equivalent to `job.status === JobStatus.COMPLETED` but with better semantics.
131
+ *
132
+ * @template T - The type of the job's data payload
133
+ * @param job - The job to check
134
+ * @returns `true` if the job status is `'completed'`
135
+ *
136
+ * @example Find completed jobs
137
+ * ```typescript
138
+ * const jobs = await monque.getJobs();
139
+ * const completedJobs = jobs.filter(isCompletedJob);
140
+ * console.log(`${completedJobs.length} jobs completed successfully`);
141
+ * ```
142
+ */
143
+ export function isCompletedJob<T>(job: Job<T>): boolean {
144
+ return job.status === JobStatus.COMPLETED;
145
+ }
146
+
147
+ /**
148
+ * Type guard to check if a job has permanently failed.
149
+ *
150
+ * A convenience helper for checking if a job exhausted all retries.
151
+ * Equivalent to `job.status === JobStatus.FAILED` but with better semantics.
152
+ *
153
+ * @template T - The type of the job's data payload
154
+ * @param job - The job to check
155
+ * @returns `true` if the job status is `'failed'`
156
+ *
157
+ * @example Handle failed jobs
158
+ * ```typescript
159
+ * const jobs = await monque.getJobs();
160
+ * const failedJobs = jobs.filter(isFailedJob);
161
+ *
162
+ * for (const job of failedJobs) {
163
+ * console.error(`Job ${job.name} failed: ${job.failReason}`);
164
+ * await sendAlert(job);
165
+ * }
166
+ * ```
167
+ */
168
+ export function isFailedJob<T>(job: Job<T>): boolean {
169
+ return job.status === JobStatus.FAILED;
170
+ }
171
+
172
+ /**
173
+ * Type guard to check if a job has been manually cancelled.
174
+ *
175
+ * A convenience helper for checking if a job was cancelled by an operator.
176
+ * Equivalent to `job.status === JobStatus.CANCELLED` but with better semantics.
177
+ *
178
+ * @template T - The type of the job's data payload
179
+ * @param job - The job to check
180
+ * @returns `true` if the job status is `'cancelled'`
181
+ *
182
+ * @example Filter cancelled jobs
183
+ * ```typescript
184
+ * const jobs = await monque.getJobs();
185
+ * const cancelledJobs = jobs.filter(isCancelledJob);
186
+ * console.log(`${cancelledJobs.length} jobs were cancelled`);
187
+ * ```
188
+ */
189
+ export function isCancelledJob<T>(job: Job<T>): boolean {
190
+ return job.status === JobStatus.CANCELLED;
191
+ }
192
+
193
+ /**
194
+ * Type guard to check if a job is a recurring scheduled job.
195
+ *
196
+ * A recurring job has a `repeatInterval` cron expression and will be automatically
197
+ * rescheduled after each successful completion.
198
+ *
199
+ * @template T - The type of the job's data payload
200
+ * @param job - The job to check
201
+ * @returns `true` if the job has a `repeatInterval` defined
202
+ *
203
+ * @example Filter recurring jobs
204
+ * ```typescript
205
+ * const jobs = await monque.getJobs();
206
+ * const recurringJobs = jobs.filter(isRecurringJob);
207
+ * console.log(`${recurringJobs.length} jobs will repeat automatically`);
208
+ * ```
209
+ *
210
+ * @example Conditional cleanup
211
+ * ```typescript
212
+ * if (!isRecurringJob(job) && isCompletedJob(job)) {
213
+ * // Safe to delete one-time completed jobs
214
+ * await deleteJob(job._id);
215
+ * }
216
+ * ```
217
+ */
218
+ export function isRecurringJob<T>(job: Job<T>): boolean {
219
+ return job.repeatInterval !== undefined && job.repeatInterval !== null;
220
+ }
@@ -0,0 +1,29 @@
1
+ // Guards
2
+ export {
3
+ isCancelledJob,
4
+ isCompletedJob,
5
+ isFailedJob,
6
+ isPendingJob,
7
+ isPersistedJob,
8
+ isProcessingJob,
9
+ isRecurringJob,
10
+ isValidJobStatus,
11
+ } from './guards.js';
12
+ // Types
13
+ export {
14
+ type BulkOperationResult,
15
+ CursorDirection,
16
+ type CursorDirectionType,
17
+ type CursorOptions,
18
+ type CursorPage,
19
+ type EnqueueOptions,
20
+ type GetJobsFilter,
21
+ type Job,
22
+ type JobHandler,
23
+ type JobSelector,
24
+ JobStatus,
25
+ type JobStatusType,
26
+ type PersistedJob,
27
+ type QueueStats,
28
+ type ScheduleOptions,
29
+ } from './types.js';