@agendajs/mongo-backend 1.0.0-alpha.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.md +25 -0
- package/README.md +174 -0
- package/dist/MongoBackend.d.ts +48 -0
- package/dist/MongoBackend.js +69 -0
- package/dist/MongoBackend.js.map +1 -0
- package/dist/MongoJobRepository.d.ts +65 -0
- package/dist/MongoJobRepository.js +492 -0
- package/dist/MongoJobRepository.js.map +1 -0
- package/dist/hasMongoProtocol.d.ts +1 -0
- package/dist/hasMongoProtocol.js +2 -0
- package/dist/hasMongoProtocol.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +25 -0
- package/dist/testing.js +45 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## License
|
|
2
|
+
(The MIT License)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2013 Ryan Schmukler <ryan@slingingcode.com>
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2022-2026 Simon Tretter <s.tretter@gmail.com>
|
|
7
|
+
|
|
8
|
+
Contributors: Mikael Korpela, Alexis Tyler, Vasyl Boroviak, Haris Sulaiman, Loris Guignard
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
11
|
+
this software and associated documentation files (the 'Software'), to deal in
|
|
12
|
+
the Software without restriction, including without limitation the rights to
|
|
13
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
14
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
15
|
+
subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
22
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
23
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
24
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
25
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# @agendajs/mongo-backend
|
|
2
|
+
|
|
3
|
+
MongoDB backend for [Agenda](https://www.npmjs.com/package/agenda) job scheduler.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install agenda @agendajs/mongo-backend
|
|
9
|
+
# or
|
|
10
|
+
pnpm add agenda @agendajs/mongo-backend
|
|
11
|
+
# or
|
|
12
|
+
yarn add agenda @agendajs/mongo-backend
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Requirements:**
|
|
16
|
+
- Node.js 18+
|
|
17
|
+
- MongoDB 4+
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Agenda } from 'agenda';
|
|
23
|
+
import { MongoBackend } from '@agendajs/mongo-backend';
|
|
24
|
+
|
|
25
|
+
// Via connection string
|
|
26
|
+
const agenda = new Agenda({
|
|
27
|
+
backend: new MongoBackend({
|
|
28
|
+
address: 'mongodb://localhost/agenda'
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Or via existing MongoDB connection
|
|
33
|
+
import { MongoClient } from 'mongodb';
|
|
34
|
+
|
|
35
|
+
const client = await MongoClient.connect('mongodb://localhost');
|
|
36
|
+
const db = client.db('agenda');
|
|
37
|
+
|
|
38
|
+
const agenda = new Agenda({
|
|
39
|
+
backend: new MongoBackend({ mongo: db })
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Define jobs
|
|
43
|
+
agenda.define('send-email', async (job) => {
|
|
44
|
+
const { to, subject } = job.attrs.data;
|
|
45
|
+
await sendEmail(to, subject);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Start processing
|
|
49
|
+
await agenda.start();
|
|
50
|
+
|
|
51
|
+
// Schedule jobs
|
|
52
|
+
await agenda.every('1 hour', 'send-email', { to: 'user@example.com', subject: 'Hello' });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration Options
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
interface IMongoBackendConfig {
|
|
59
|
+
/** MongoDB connection string */
|
|
60
|
+
address?: string;
|
|
61
|
+
|
|
62
|
+
/** Existing MongoDB database instance */
|
|
63
|
+
mongo?: Db;
|
|
64
|
+
|
|
65
|
+
/** Collection name for jobs (default: 'agendaJobs') */
|
|
66
|
+
collection?: string;
|
|
67
|
+
|
|
68
|
+
/** MongoDB client options */
|
|
69
|
+
options?: MongoClientOptions;
|
|
70
|
+
|
|
71
|
+
/** Name to identify this Agenda instance (stored as lastModifiedBy) */
|
|
72
|
+
name?: string;
|
|
73
|
+
|
|
74
|
+
/** Whether to create indexes on connect (default: true) */
|
|
75
|
+
ensureIndex?: boolean;
|
|
76
|
+
|
|
77
|
+
/** Sort order for job queries */
|
|
78
|
+
sort?: { [key: string]: SortDirection };
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Full Example with Options
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const agenda = new Agenda({
|
|
86
|
+
backend: new MongoBackend({
|
|
87
|
+
address: 'mongodb://localhost/agenda',
|
|
88
|
+
collection: 'jobs', // Custom collection name
|
|
89
|
+
sort: { nextRunAt: 'asc', priority: 'desc' }
|
|
90
|
+
}),
|
|
91
|
+
processEvery: '30 seconds', // Job polling interval
|
|
92
|
+
maxConcurrency: 20, // Max concurrent jobs
|
|
93
|
+
defaultConcurrency: 5 // Default per job type
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Real-Time Notifications
|
|
98
|
+
|
|
99
|
+
MongoBackend provides storage only and uses polling to check for new jobs. For real-time job processing across distributed systems, combine it with a notification channel:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { Agenda, InMemoryNotificationChannel } from 'agenda';
|
|
103
|
+
import { MongoBackend } from '@agendajs/mongo-backend';
|
|
104
|
+
|
|
105
|
+
// For single-process apps (testing/development)
|
|
106
|
+
const agenda = new Agenda({
|
|
107
|
+
backend: new MongoBackend({ mongo: db }),
|
|
108
|
+
notificationChannel: new InMemoryNotificationChannel()
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// For production: use Redis for notifications
|
|
112
|
+
import { RedisBackend } from '@agendajs/redis-backend';
|
|
113
|
+
|
|
114
|
+
const redisBackend = new RedisBackend({ connectionString: 'redis://localhost:6379' });
|
|
115
|
+
const agenda = new Agenda({
|
|
116
|
+
backend: new MongoBackend({ mongo: db }),
|
|
117
|
+
notificationChannel: redisBackend.notificationChannel
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Database Index
|
|
122
|
+
|
|
123
|
+
By default, MongoBackend automatically creates the `findAndLockNextJobIndex` index on connect for optimal job query performance. The index includes:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
{
|
|
127
|
+
"name": 1,
|
|
128
|
+
"nextRunAt": 1,
|
|
129
|
+
"priority": -1,
|
|
130
|
+
"lockedAt": 1,
|
|
131
|
+
"disabled": 1
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
To disable automatic index creation (e.g., if you manage indexes separately):
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const backend = new MongoBackend({
|
|
139
|
+
mongo: db,
|
|
140
|
+
ensureIndex: false // Skip automatic index creation
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Additional Indexes
|
|
145
|
+
|
|
146
|
+
If you use **Agendash**, add this index for better dashboard performance:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
db.agendaJobs.createIndex({
|
|
150
|
+
"nextRunAt": -1,
|
|
151
|
+
"lastRunAt": -1,
|
|
152
|
+
"lastFinishedAt": -1
|
|
153
|
+
}, { name: "agendash" })
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If you have job definitions with thousands of instances, this index can improve lock queries:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
db.agendaJobs.createIndex({
|
|
160
|
+
"name": 1,
|
|
161
|
+
"disabled": 1,
|
|
162
|
+
"lockedAt": 1
|
|
163
|
+
}, { name: "findAndLockDeadJobs" })
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Related Packages
|
|
167
|
+
|
|
168
|
+
- [agenda](https://www.npmjs.com/package/agenda) - Core scheduler
|
|
169
|
+
- [@agendajs/postgres-backend](https://www.npmjs.com/package/@agendajs/postgres-backend) - PostgreSQL backend with LISTEN/NOTIFY
|
|
170
|
+
- [@agendajs/redis-backend](https://www.npmjs.com/package/@agendajs/redis-backend) - Redis backend with Pub/Sub
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AgendaBackend, JobRepository, NotificationChannel } from 'agenda';
|
|
2
|
+
import type { MongoBackendConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* MongoDB backend for Agenda.
|
|
5
|
+
* Provides storage via MongoDB. Does not provide notifications (uses polling).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Agenda } from 'agenda';
|
|
10
|
+
* import { MongoBackend } from '@agendajs/mongo-backend';
|
|
11
|
+
*
|
|
12
|
+
* // Via connection string
|
|
13
|
+
* const backend = new MongoBackend({ address: 'mongodb://localhost/agenda' });
|
|
14
|
+
*
|
|
15
|
+
* // Via existing connection
|
|
16
|
+
* const backend = new MongoBackend({ mongo: existingDb });
|
|
17
|
+
*
|
|
18
|
+
* const agenda = new Agenda({ backend });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class MongoBackend implements AgendaBackend {
|
|
22
|
+
private config;
|
|
23
|
+
private _repository;
|
|
24
|
+
private _ownsConnection;
|
|
25
|
+
/**
|
|
26
|
+
* MongoDB does not provide a notification channel.
|
|
27
|
+
* For real-time notifications with MongoDB, use a separate notification
|
|
28
|
+
* channel like Redis pub/sub.
|
|
29
|
+
*/
|
|
30
|
+
readonly notificationChannel: NotificationChannel | undefined;
|
|
31
|
+
constructor(config: MongoBackendConfig);
|
|
32
|
+
get repository(): JobRepository;
|
|
33
|
+
/**
|
|
34
|
+
* Whether this backend owns its database connection.
|
|
35
|
+
* True if created from address (connection string), false if mongo Db was passed in.
|
|
36
|
+
*/
|
|
37
|
+
get ownsConnection(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Connect to MongoDB and initialize the collection.
|
|
40
|
+
*/
|
|
41
|
+
connect(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Disconnect from MongoDB.
|
|
44
|
+
* Only closes the connection if we created it (i.e., from connection string).
|
|
45
|
+
* If an existing Db instance was passed in, this is a no-op.
|
|
46
|
+
*/
|
|
47
|
+
disconnect(): Promise<void>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MongoJobRepository } from './MongoJobRepository.js';
|
|
2
|
+
/**
|
|
3
|
+
* MongoDB backend for Agenda.
|
|
4
|
+
* Provides storage via MongoDB. Does not provide notifications (uses polling).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Agenda } from 'agenda';
|
|
9
|
+
* import { MongoBackend } from '@agendajs/mongo-backend';
|
|
10
|
+
*
|
|
11
|
+
* // Via connection string
|
|
12
|
+
* const backend = new MongoBackend({ address: 'mongodb://localhost/agenda' });
|
|
13
|
+
*
|
|
14
|
+
* // Via existing connection
|
|
15
|
+
* const backend = new MongoBackend({ mongo: existingDb });
|
|
16
|
+
*
|
|
17
|
+
* const agenda = new Agenda({ backend });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class MongoBackend {
|
|
21
|
+
config;
|
|
22
|
+
_repository;
|
|
23
|
+
_ownsConnection;
|
|
24
|
+
/**
|
|
25
|
+
* MongoDB does not provide a notification channel.
|
|
26
|
+
* For real-time notifications with MongoDB, use a separate notification
|
|
27
|
+
* channel like Redis pub/sub.
|
|
28
|
+
*/
|
|
29
|
+
notificationChannel = undefined;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
// Determine if we own the connection (not passed in by user)
|
|
33
|
+
this._ownsConnection = !('mongo' in config);
|
|
34
|
+
this._repository = new MongoJobRepository({
|
|
35
|
+
...('mongo' in config
|
|
36
|
+
? { mongo: config.mongo, db: { collection: config.collection } }
|
|
37
|
+
: {
|
|
38
|
+
db: { address: config.address, collection: config.collection, options: config.options }
|
|
39
|
+
}),
|
|
40
|
+
ensureIndex: config.ensureIndex,
|
|
41
|
+
sort: config.sort
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
get repository() {
|
|
45
|
+
return this._repository;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether this backend owns its database connection.
|
|
49
|
+
* True if created from address (connection string), false if mongo Db was passed in.
|
|
50
|
+
*/
|
|
51
|
+
get ownsConnection() {
|
|
52
|
+
return this._ownsConnection;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Connect to MongoDB and initialize the collection.
|
|
56
|
+
*/
|
|
57
|
+
async connect() {
|
|
58
|
+
await this._repository.connect();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Disconnect from MongoDB.
|
|
62
|
+
* Only closes the connection if we created it (i.e., from connection string).
|
|
63
|
+
* If an existing Db instance was passed in, this is a no-op.
|
|
64
|
+
*/
|
|
65
|
+
async disconnect() {
|
|
66
|
+
await this._repository.disconnect();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=MongoBackend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoBackend.js","sourceRoot":"","sources":["../src/MongoBackend.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,YAAY;IAWJ;IAVZ,WAAW,CAAqB;IAChC,eAAe,CAAU;IAEjC;;;;OAIG;IACM,mBAAmB,GAAoC,SAAS,CAAC;IAE1E,YAAoB,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;QAC7C,6DAA6D;QAC7D,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,WAAW,GAAG,IAAI,kBAAkB,CAAC;YACzC,GAAG,CAAC,OAAO,IAAI,MAAM;gBACpB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE;gBAChE,CAAC,CAAC;oBACA,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE;iBACvF,CAAC;YACJ,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,IAAI,EAAE,MAAM,CAAC,IAAI;SACjB,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU;QACb,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,IAAI,cAAc;QACjB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACZ,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,UAAU;QACf,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;IACrC,CAAC;CACD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Collection, ObjectId } from 'mongodb';
|
|
2
|
+
import type { JobParameters, JobId, JobsQueryOptions, JobsResult, JobsOverview, JobRepository, JobRepositoryOptions, RemoveJobsOptions } from 'agenda';
|
|
3
|
+
import type { MongoJobRepositoryConfig } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Internal MongoDB document type with ObjectId for _id
|
|
6
|
+
* This is what's actually stored in MongoDB, separate from the public JobParameters interface
|
|
7
|
+
*/
|
|
8
|
+
type MongoJobDocument = Omit<JobParameters, '_id'> & {
|
|
9
|
+
_id?: ObjectId;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* @class
|
|
13
|
+
* MongoDB implementation of JobRepository
|
|
14
|
+
*/
|
|
15
|
+
export declare class MongoJobRepository implements JobRepository {
|
|
16
|
+
private connectOptions;
|
|
17
|
+
collection: Collection<MongoJobDocument>;
|
|
18
|
+
private mongoClient?;
|
|
19
|
+
private ownsConnection;
|
|
20
|
+
constructor(connectOptions: MongoJobRepositoryConfig);
|
|
21
|
+
private createConnection;
|
|
22
|
+
getJobById(id: string): Promise<JobParameters | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Convert a SortDirection value to MongoDB's numeric sort direction
|
|
25
|
+
*/
|
|
26
|
+
private toMongoSortDirection;
|
|
27
|
+
/**
|
|
28
|
+
* Convert generic sort options to MongoDB sort
|
|
29
|
+
*/
|
|
30
|
+
private toMongoSort;
|
|
31
|
+
/**
|
|
32
|
+
* Query jobs with database-agnostic options.
|
|
33
|
+
* Handles state computation and filtering internally.
|
|
34
|
+
*/
|
|
35
|
+
queryJobs(options?: JobsQueryOptions): Promise<JobsResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Get overview statistics for jobs grouped by name.
|
|
38
|
+
* Returns counts of jobs in each state for each job name.
|
|
39
|
+
*/
|
|
40
|
+
getJobsOverview(): Promise<JobsOverview[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Get all distinct job names
|
|
43
|
+
*/
|
|
44
|
+
getDistinctJobNames(): Promise<string[]>;
|
|
45
|
+
getQueueSize(): Promise<number>;
|
|
46
|
+
removeJobs(options: RemoveJobsOptions): Promise<number>;
|
|
47
|
+
unlockJob(job: JobParameters): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Internal method to unlock jobs so that they can be re-run
|
|
50
|
+
*/
|
|
51
|
+
unlockJobs(jobIds: (JobId | string)[]): Promise<void>;
|
|
52
|
+
lockJob(job: JobParameters, options: JobRepositoryOptions | undefined): Promise<JobParameters | undefined>;
|
|
53
|
+
getNextJobToRun(jobName: string, nextScanAt: Date, lockDeadline: Date, now: Date | undefined, options: JobRepositoryOptions | undefined): Promise<JobParameters | undefined>;
|
|
54
|
+
connect(): Promise<void>;
|
|
55
|
+
private database;
|
|
56
|
+
disconnect(): Promise<void>;
|
|
57
|
+
saveJobState(job: JobParameters, options: JobRepositoryOptions | undefined): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Save a job to the database (insert or update)
|
|
60
|
+
* @param job Job parameters to save
|
|
61
|
+
* @returns The saved job parameters with ID
|
|
62
|
+
*/
|
|
63
|
+
saveJob<DATA = unknown>(job: JobParameters<DATA>, options: JobRepositoryOptions | undefined): Promise<JobParameters<DATA>>;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
import { MongoClient, ObjectId } from 'mongodb';
|
|
4
|
+
import { toJobId, computeJobState } from 'agenda';
|
|
5
|
+
import { hasMongoProtocol } from './hasMongoProtocol.js';
|
|
6
|
+
const log = debug('agenda:mongo:repository');
|
|
7
|
+
/**
|
|
8
|
+
* Escape special regex characters in a string to treat it as literal text
|
|
9
|
+
*/
|
|
10
|
+
function escapeRegex(str) {
|
|
11
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
+
}
|
|
13
|
+
function computeJobObj(job) {
|
|
14
|
+
return {
|
|
15
|
+
_id: toJobId(job._id.toHexString()),
|
|
16
|
+
name: job.name,
|
|
17
|
+
priority: job.priority,
|
|
18
|
+
nextRunAt: job.nextRunAt,
|
|
19
|
+
type: job.type,
|
|
20
|
+
data: job.data,
|
|
21
|
+
lockedAt: job.lockedAt,
|
|
22
|
+
lastFinishedAt: job.lastFinishedAt,
|
|
23
|
+
failedAt: job.failedAt || undefined,
|
|
24
|
+
failCount: job.failCount || undefined,
|
|
25
|
+
failReason: job.failReason || undefined,
|
|
26
|
+
repeatTimezone: job.repeatTimezone,
|
|
27
|
+
lastRunAt: job.lastRunAt,
|
|
28
|
+
repeatInterval: job.repeatInterval,
|
|
29
|
+
repeatAt: job.repeatAt,
|
|
30
|
+
disabled: job.disabled,
|
|
31
|
+
progress: job.progress,
|
|
32
|
+
unique: job.unique,
|
|
33
|
+
uniqueOpts: job.uniqueOpts,
|
|
34
|
+
lastModifiedBy: job.lastModifiedBy,
|
|
35
|
+
fork: job.fork
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @class
|
|
40
|
+
* MongoDB implementation of JobRepository
|
|
41
|
+
*/
|
|
42
|
+
export class MongoJobRepository {
|
|
43
|
+
connectOptions;
|
|
44
|
+
collection;
|
|
45
|
+
mongoClient;
|
|
46
|
+
ownsConnection = false;
|
|
47
|
+
constructor(connectOptions) {
|
|
48
|
+
this.connectOptions = connectOptions;
|
|
49
|
+
this.connectOptions.sort = this.connectOptions.sort || { nextRunAt: 'asc', priority: 'desc' };
|
|
50
|
+
// Track if we own the connection (i.e., we created it from a connection string)
|
|
51
|
+
this.ownsConnection = !('mongo' in connectOptions);
|
|
52
|
+
}
|
|
53
|
+
async createConnection() {
|
|
54
|
+
const { connectOptions } = this;
|
|
55
|
+
if ('mongo' in connectOptions) {
|
|
56
|
+
log('using passed in mongo connection');
|
|
57
|
+
return connectOptions.mongo;
|
|
58
|
+
}
|
|
59
|
+
if ('db' in connectOptions && connectOptions.db) {
|
|
60
|
+
log('using database config', connectOptions);
|
|
61
|
+
return this.database(connectOptions.db.address, connectOptions.db.options);
|
|
62
|
+
}
|
|
63
|
+
throw new Error('invalid db config, or db config not found');
|
|
64
|
+
}
|
|
65
|
+
async getJobById(id) {
|
|
66
|
+
const doc = await this.collection.findOne({ _id: new ObjectId(id) });
|
|
67
|
+
if (!doc)
|
|
68
|
+
return null;
|
|
69
|
+
// Convert MongoDB ObjectId to JobId
|
|
70
|
+
return computeJobObj(doc);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Convert a SortDirection value to MongoDB's numeric sort direction
|
|
74
|
+
*/
|
|
75
|
+
toMongoSortDirection(dir) {
|
|
76
|
+
return dir === 'asc' ? 1 : -1;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Convert generic sort options to MongoDB sort
|
|
80
|
+
*/
|
|
81
|
+
toMongoSort(sort) {
|
|
82
|
+
if (!sort) {
|
|
83
|
+
return { nextRunAt: -1, lastRunAt: -1 };
|
|
84
|
+
}
|
|
85
|
+
const mongoSort = {};
|
|
86
|
+
if (sort.nextRunAt !== undefined)
|
|
87
|
+
mongoSort.nextRunAt = this.toMongoSortDirection(sort.nextRunAt);
|
|
88
|
+
if (sort.lastRunAt !== undefined)
|
|
89
|
+
mongoSort.lastRunAt = this.toMongoSortDirection(sort.lastRunAt);
|
|
90
|
+
if (sort.lastFinishedAt !== undefined)
|
|
91
|
+
mongoSort.lastFinishedAt = this.toMongoSortDirection(sort.lastFinishedAt);
|
|
92
|
+
if (sort.priority !== undefined)
|
|
93
|
+
mongoSort.priority = this.toMongoSortDirection(sort.priority);
|
|
94
|
+
if (sort.name !== undefined)
|
|
95
|
+
mongoSort.name = this.toMongoSortDirection(sort.name);
|
|
96
|
+
if (sort.data !== undefined)
|
|
97
|
+
mongoSort.data = this.toMongoSortDirection(sort.data);
|
|
98
|
+
return Object.keys(mongoSort).length > 0 ? mongoSort : { nextRunAt: -1, lastRunAt: -1 };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Query jobs with database-agnostic options.
|
|
102
|
+
* Handles state computation and filtering internally.
|
|
103
|
+
*/
|
|
104
|
+
async queryJobs(options = {}) {
|
|
105
|
+
const { name, names, state, id, ids, search, data, includeDisabled = true, sort, skip = 0, limit = 0 } = options;
|
|
106
|
+
const now = new Date();
|
|
107
|
+
// Build MongoDB query from options
|
|
108
|
+
const query = {};
|
|
109
|
+
// Validate name is a string to prevent query operator injection
|
|
110
|
+
if (name && typeof name === 'string') {
|
|
111
|
+
query.name = name;
|
|
112
|
+
}
|
|
113
|
+
else if (names && Array.isArray(names) && names.length > 0) {
|
|
114
|
+
// Filter to only valid strings
|
|
115
|
+
const validNames = names.filter((n) => typeof n === 'string');
|
|
116
|
+
if (validNames.length > 0) {
|
|
117
|
+
query.name = { $in: validNames };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (id && typeof id === 'string') {
|
|
121
|
+
try {
|
|
122
|
+
query._id = new ObjectId(id);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return { jobs: [], total: 0 };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (ids && Array.isArray(ids) && ids.length > 0) {
|
|
129
|
+
try {
|
|
130
|
+
const validIds = ids.filter((i) => typeof i === 'string');
|
|
131
|
+
if (validIds.length > 0) {
|
|
132
|
+
query._id = { $in: validIds.map(i => new ObjectId(i)) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { jobs: [], total: 0 };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Validate search is a string and escape regex metacharacters
|
|
140
|
+
if (search && typeof search === 'string' && search.length > 0) {
|
|
141
|
+
query.name = { $regex: escapeRegex(search), $options: 'i' };
|
|
142
|
+
}
|
|
143
|
+
if (data !== undefined) {
|
|
144
|
+
query.data = data;
|
|
145
|
+
}
|
|
146
|
+
if (!includeDisabled) {
|
|
147
|
+
query.disabled = { $ne: true };
|
|
148
|
+
}
|
|
149
|
+
// Fetch jobs
|
|
150
|
+
const allJobs = await this.collection.find(query).sort(this.toMongoSort(sort)).toArray();
|
|
151
|
+
// Compute states and filter by state if specified
|
|
152
|
+
let jobsWithState = allJobs
|
|
153
|
+
.map(job => {
|
|
154
|
+
const jobOb = computeJobObj(job);
|
|
155
|
+
return {
|
|
156
|
+
...jobOb,
|
|
157
|
+
state: computeJobState(jobOb, now)
|
|
158
|
+
};
|
|
159
|
+
})
|
|
160
|
+
.filter(job => !state || job.state === state);
|
|
161
|
+
// Apply pagination after state filtering
|
|
162
|
+
const total = jobsWithState.length;
|
|
163
|
+
if (limit > 0) {
|
|
164
|
+
jobsWithState = jobsWithState.slice(skip, skip + limit);
|
|
165
|
+
}
|
|
166
|
+
else if (skip > 0) {
|
|
167
|
+
jobsWithState = jobsWithState.slice(skip);
|
|
168
|
+
}
|
|
169
|
+
return { jobs: jobsWithState, total };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get overview statistics for jobs grouped by name.
|
|
173
|
+
* Returns counts of jobs in each state for each job name.
|
|
174
|
+
*/
|
|
175
|
+
async getJobsOverview() {
|
|
176
|
+
const now = new Date();
|
|
177
|
+
const names = await this.getDistinctJobNames();
|
|
178
|
+
const overviews = await Promise.all(names.map(async (name) => {
|
|
179
|
+
const jobs = await this.collection.find({ name }).toArray();
|
|
180
|
+
const overview = {
|
|
181
|
+
name,
|
|
182
|
+
total: jobs.length,
|
|
183
|
+
running: 0,
|
|
184
|
+
scheduled: 0,
|
|
185
|
+
queued: 0,
|
|
186
|
+
completed: 0,
|
|
187
|
+
failed: 0,
|
|
188
|
+
repeating: 0
|
|
189
|
+
};
|
|
190
|
+
for (const job of jobs) {
|
|
191
|
+
const state = computeJobState(job, now);
|
|
192
|
+
overview[state]++;
|
|
193
|
+
}
|
|
194
|
+
return overview;
|
|
195
|
+
}));
|
|
196
|
+
return overviews;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get all distinct job names
|
|
200
|
+
*/
|
|
201
|
+
async getDistinctJobNames() {
|
|
202
|
+
return this.collection.distinct('name');
|
|
203
|
+
}
|
|
204
|
+
async getQueueSize() {
|
|
205
|
+
return this.collection.countDocuments({ nextRunAt: { $lt: new Date() } });
|
|
206
|
+
}
|
|
207
|
+
async removeJobs(options) {
|
|
208
|
+
const query = {};
|
|
209
|
+
if (options.id) {
|
|
210
|
+
const idStr = String(options.id);
|
|
211
|
+
try {
|
|
212
|
+
query._id = new ObjectId(idStr);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (options.ids && Array.isArray(options.ids) && options.ids.length > 0) {
|
|
219
|
+
try {
|
|
220
|
+
query._id = { $in: options.ids.map(id => new ObjectId(String(id))) };
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Validate name is a string to prevent query operator injection
|
|
227
|
+
if (options.name && typeof options.name === 'string') {
|
|
228
|
+
query.name = options.name;
|
|
229
|
+
}
|
|
230
|
+
else if (options.names && Array.isArray(options.names) && options.names.length > 0) {
|
|
231
|
+
const validNames = options.names.filter((n) => typeof n === 'string');
|
|
232
|
+
if (validNames.length > 0) {
|
|
233
|
+
query.name = { $in: validNames };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (options.notNames && Array.isArray(options.notNames) && options.notNames.length > 0) {
|
|
237
|
+
const validNotNames = options.notNames.filter((n) => typeof n === 'string');
|
|
238
|
+
if (validNotNames.length > 0) {
|
|
239
|
+
query.name = { $nin: validNotNames };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (options.data !== undefined) {
|
|
243
|
+
query.data = options.data;
|
|
244
|
+
}
|
|
245
|
+
// If no criteria provided, don't delete anything
|
|
246
|
+
if (Object.keys(query).length === 0) {
|
|
247
|
+
log('removeJobs: skipping deleteMany without query', query);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
const result = await this.collection.deleteMany(query);
|
|
251
|
+
return result.deletedCount;
|
|
252
|
+
}
|
|
253
|
+
async unlockJob(job) {
|
|
254
|
+
if (!job._id)
|
|
255
|
+
return;
|
|
256
|
+
// only unlock jobs which are not currently processed (nextRunAt is not null)
|
|
257
|
+
await this.collection.updateOne({ _id: new ObjectId(job._id.toString()), nextRunAt: { $ne: null } }, { $unset: { lockedAt: true } });
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Internal method to unlock jobs so that they can be re-run
|
|
261
|
+
*/
|
|
262
|
+
async unlockJobs(jobIds) {
|
|
263
|
+
if (jobIds.length === 0)
|
|
264
|
+
return;
|
|
265
|
+
const objectIds = jobIds.map(id => new ObjectId(id.toString()));
|
|
266
|
+
// Unlock jobs by ID regardless of nextRunAt status.
|
|
267
|
+
// When a one-time job starts running, nextRunAt becomes null,
|
|
268
|
+
// but we still want to unlock it when stop() is called.
|
|
269
|
+
await this.collection.updateMany({ _id: { $in: objectIds } }, { $unset: { lockedAt: true } });
|
|
270
|
+
}
|
|
271
|
+
async lockJob(job, options) {
|
|
272
|
+
if (!job._id)
|
|
273
|
+
return undefined;
|
|
274
|
+
// Query to run against collection to see if we need to lock it
|
|
275
|
+
const criteria = {
|
|
276
|
+
_id: new ObjectId(job._id.toString()),
|
|
277
|
+
name: job.name,
|
|
278
|
+
lockedAt: null,
|
|
279
|
+
nextRunAt: job.nextRunAt,
|
|
280
|
+
disabled: { $ne: true }
|
|
281
|
+
};
|
|
282
|
+
// Update / options for the MongoDB query
|
|
283
|
+
const update = {
|
|
284
|
+
$set: { lockedAt: new Date(), lastModifiedBy: options?.lastModifiedBy }
|
|
285
|
+
};
|
|
286
|
+
const findOptions = {
|
|
287
|
+
returnDocument: 'after',
|
|
288
|
+
sort: this.connectOptions.sort
|
|
289
|
+
};
|
|
290
|
+
// Lock the job in MongoDB!
|
|
291
|
+
const result = await this.collection.findOneAndUpdate(criteria, update, findOptions);
|
|
292
|
+
if (!result)
|
|
293
|
+
return undefined;
|
|
294
|
+
// Convert MongoDB ObjectId to JobId
|
|
295
|
+
return computeJobObj(result);
|
|
296
|
+
}
|
|
297
|
+
async getNextJobToRun(jobName, nextScanAt, lockDeadline, now, options) {
|
|
298
|
+
const lockTime = now ?? new Date();
|
|
299
|
+
/**
|
|
300
|
+
* Query used to find job to run
|
|
301
|
+
*/
|
|
302
|
+
const JOB_PROCESS_WHERE_QUERY = {
|
|
303
|
+
name: jobName,
|
|
304
|
+
disabled: { $ne: true },
|
|
305
|
+
$or: [
|
|
306
|
+
{
|
|
307
|
+
lockedAt: { $eq: null },
|
|
308
|
+
nextRunAt: { $lte: nextScanAt }
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
lockedAt: { $lte: lockDeadline }
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Query used to set a job as locked
|
|
317
|
+
*/
|
|
318
|
+
const JOB_PROCESS_SET_QUERY = {
|
|
319
|
+
$set: { lockedAt: lockTime, lastModifiedBy: options?.lastModifiedBy }
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Query used to affect what gets returned
|
|
323
|
+
*/
|
|
324
|
+
const JOB_RETURN_QUERY = {
|
|
325
|
+
returnDocument: 'after',
|
|
326
|
+
sort: this.connectOptions.sort
|
|
327
|
+
};
|
|
328
|
+
// Find ONE and ONLY ONE job and set the 'lockedAt' time so that job begins to be processed
|
|
329
|
+
const result = await this.collection.findOneAndUpdate(JOB_PROCESS_WHERE_QUERY, JOB_PROCESS_SET_QUERY, JOB_RETURN_QUERY);
|
|
330
|
+
if (!result)
|
|
331
|
+
return undefined;
|
|
332
|
+
// Convert MongoDB ObjectId to JobId
|
|
333
|
+
return computeJobObj(result);
|
|
334
|
+
}
|
|
335
|
+
async connect() {
|
|
336
|
+
const db = await this.createConnection();
|
|
337
|
+
log('successful connection to MongoDB', db.options);
|
|
338
|
+
const collection = this.connectOptions.db?.collection || 'agendaJobs';
|
|
339
|
+
this.collection = db.collection(collection);
|
|
340
|
+
if (log.enabled) {
|
|
341
|
+
log(`connected with collection: ${collection}, collection size: ${typeof this.collection.estimatedDocumentCount === 'function'
|
|
342
|
+
? await this.collection.estimatedDocumentCount()
|
|
343
|
+
: '?'}`);
|
|
344
|
+
}
|
|
345
|
+
if (this.connectOptions.ensureIndex ?? true) {
|
|
346
|
+
log('attempting index creation');
|
|
347
|
+
try {
|
|
348
|
+
const result = await this.collection.createIndex({
|
|
349
|
+
name: 1,
|
|
350
|
+
...this.connectOptions.sort,
|
|
351
|
+
priority: -1,
|
|
352
|
+
lockedAt: 1,
|
|
353
|
+
nextRunAt: 1,
|
|
354
|
+
disabled: 1
|
|
355
|
+
}, { name: 'findAndLockNextJobIndex' });
|
|
356
|
+
log('index succesfully created', result);
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
log('db index creation failed', error);
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async database(url, options) {
|
|
365
|
+
let connectionString = url;
|
|
366
|
+
if (!hasMongoProtocol(connectionString)) {
|
|
367
|
+
connectionString = `mongodb://${connectionString}`;
|
|
368
|
+
}
|
|
369
|
+
this.mongoClient = await MongoClient.connect(connectionString, {
|
|
370
|
+
...options
|
|
371
|
+
});
|
|
372
|
+
return this.mongoClient.db();
|
|
373
|
+
}
|
|
374
|
+
async disconnect() {
|
|
375
|
+
if (this.ownsConnection && this.mongoClient) {
|
|
376
|
+
log('closing owned MongoDB connection');
|
|
377
|
+
await this.mongoClient.close();
|
|
378
|
+
this.mongoClient = undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async saveJobState(job, options) {
|
|
382
|
+
if (!job._id) {
|
|
383
|
+
throw new Error('Cannot save job state without job ID');
|
|
384
|
+
}
|
|
385
|
+
const id = new ObjectId(job._id.toString());
|
|
386
|
+
const $set = {
|
|
387
|
+
lockedAt: (job.lockedAt && new Date(job.lockedAt)) || undefined,
|
|
388
|
+
nextRunAt: (job.nextRunAt && new Date(job.nextRunAt)) || undefined,
|
|
389
|
+
lastRunAt: (job.lastRunAt && new Date(job.lastRunAt)) || undefined,
|
|
390
|
+
progress: job.progress,
|
|
391
|
+
failReason: job.failReason,
|
|
392
|
+
failCount: job.failCount,
|
|
393
|
+
failedAt: job.failedAt && new Date(job.failedAt),
|
|
394
|
+
lastFinishedAt: (job.lastFinishedAt && new Date(job.lastFinishedAt)) || undefined,
|
|
395
|
+
lastModifiedBy: options?.lastModifiedBy
|
|
396
|
+
};
|
|
397
|
+
log('[job %s] save job state: \n%O', job._id, $set);
|
|
398
|
+
const result = await this.collection.updateOne({ _id: id, name: job.name }, {
|
|
399
|
+
$set
|
|
400
|
+
});
|
|
401
|
+
if (!result.acknowledged || result.matchedCount !== 1) {
|
|
402
|
+
throw new Error(`job ${job._id} (name: ${job.name}) cannot be updated in the database, maybe it does not exist anymore?`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Save a job to the database (insert or update)
|
|
407
|
+
* @param job Job parameters to save
|
|
408
|
+
* @returns The saved job parameters with ID
|
|
409
|
+
*/
|
|
410
|
+
async saveJob(job, options) {
|
|
411
|
+
try {
|
|
412
|
+
log('attempting to save a job');
|
|
413
|
+
// Extract the ID and unique fields (not persisted directly)
|
|
414
|
+
const { _id, unique, uniqueOpts, ...props } = job;
|
|
415
|
+
// Add lastModifiedBy
|
|
416
|
+
const propsWithModifier = {
|
|
417
|
+
...props,
|
|
418
|
+
lastModifiedBy: options?.lastModifiedBy
|
|
419
|
+
};
|
|
420
|
+
log('[job %s] set job props: \n%O', _id, propsWithModifier);
|
|
421
|
+
// Grab current time and set default query options for MongoDB
|
|
422
|
+
const now = new Date();
|
|
423
|
+
const protect = {};
|
|
424
|
+
let update = { $set: propsWithModifier };
|
|
425
|
+
log('current time stored as %s', now.toISOString());
|
|
426
|
+
// If the job already had an ID, then update the properties of the job
|
|
427
|
+
if (_id) {
|
|
428
|
+
log('job already has _id, calling findOneAndUpdate() using _id as query');
|
|
429
|
+
const result = await this.collection.findOneAndUpdate({ _id: new ObjectId(_id.toString()), name: props.name }, update, { returnDocument: 'after' });
|
|
430
|
+
if (!result) {
|
|
431
|
+
// Job was removed, return original data unchanged
|
|
432
|
+
log('job %s was not found for update, returning original data', _id);
|
|
433
|
+
return job;
|
|
434
|
+
}
|
|
435
|
+
return computeJobObj(result);
|
|
436
|
+
}
|
|
437
|
+
if (props.type === 'single') {
|
|
438
|
+
// Job type set to 'single' so...
|
|
439
|
+
log('job with type of "single" found');
|
|
440
|
+
// If the nextRunAt time is older than the current time, "protect" that property
|
|
441
|
+
if (props.nextRunAt && props.nextRunAt <= now) {
|
|
442
|
+
log('job has a scheduled nextRunAt time, protecting that field from upsert');
|
|
443
|
+
protect.nextRunAt = props.nextRunAt;
|
|
444
|
+
delete propsWithModifier.nextRunAt;
|
|
445
|
+
}
|
|
446
|
+
// If we have things to protect, set them in MongoDB using $setOnInsert
|
|
447
|
+
if (Object.keys(protect).length > 0) {
|
|
448
|
+
update.$setOnInsert = protect;
|
|
449
|
+
}
|
|
450
|
+
// Try an upsert - ensures only one job of this name exists
|
|
451
|
+
log(`calling findOneAndUpdate(${props.name}) with job name and type of "single" as query`);
|
|
452
|
+
const result = await this.collection.findOneAndUpdate({
|
|
453
|
+
name: props.name,
|
|
454
|
+
type: 'single'
|
|
455
|
+
}, update, {
|
|
456
|
+
upsert: true,
|
|
457
|
+
returnDocument: 'after'
|
|
458
|
+
});
|
|
459
|
+
log(`findOneAndUpdate(${props.name}) with type "single" completed`);
|
|
460
|
+
assert(result, 'findOneAndUpdate with upsert should always return a document');
|
|
461
|
+
return computeJobObj(result);
|
|
462
|
+
}
|
|
463
|
+
if (unique) {
|
|
464
|
+
// Upsert based on the 'unique' query object
|
|
465
|
+
const query = { ...unique };
|
|
466
|
+
query.name = props.name;
|
|
467
|
+
if (uniqueOpts?.insertOnly) {
|
|
468
|
+
update = { $setOnInsert: propsWithModifier };
|
|
469
|
+
}
|
|
470
|
+
log('calling findOneAndUpdate() with unique object as query: \n%O', query);
|
|
471
|
+
const result = await this.collection.findOneAndUpdate(query, update, {
|
|
472
|
+
upsert: true,
|
|
473
|
+
returnDocument: 'after'
|
|
474
|
+
});
|
|
475
|
+
assert(result, 'findOneAndUpdate with upsert should always return a document');
|
|
476
|
+
return computeJobObj(result);
|
|
477
|
+
}
|
|
478
|
+
// Insert new job
|
|
479
|
+
log('using default behavior, inserting new job via insertOne() with props that were set: \n%O', propsWithModifier);
|
|
480
|
+
const result = await this.collection.insertOne(propsWithModifier);
|
|
481
|
+
return computeJobObj({
|
|
482
|
+
_id: result.insertedId,
|
|
483
|
+
...propsWithModifier
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
log('saveJob() received an error, job was not updated/created');
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
//# sourceMappingURL=MongoJobRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoJobRepository.js","sourceRoot":"","sources":["../src/MongoJobRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const hasMongoProtocol: (url: string) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hasMongoProtocol.js","sourceRoot":"","sources":["../src/hasMongoProtocol.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agendajs/mongo-backend
|
|
3
|
+
*
|
|
4
|
+
* MongoDB backend for Agenda job scheduler.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Agenda } from 'agenda';
|
|
9
|
+
* import { MongoBackend } from '@agendajs/mongo-backend';
|
|
10
|
+
*
|
|
11
|
+
* // Create agenda with MongoDB backend
|
|
12
|
+
* const agenda = new Agenda({
|
|
13
|
+
* backend: new MongoBackend({
|
|
14
|
+
* address: 'mongodb://localhost/agenda'
|
|
15
|
+
* })
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Or with existing connection
|
|
19
|
+
* const agenda = new Agenda({
|
|
20
|
+
* backend: new MongoBackend({ mongo: existingDb })
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Define and run jobs
|
|
24
|
+
* agenda.define('myJob', async (job) => {
|
|
25
|
+
* console.log('Running job:', job.attrs.name);
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* await agenda.start();
|
|
29
|
+
* await agenda.every('5 minutes', 'myJob');
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export { MongoBackend } from './MongoBackend.js';
|
|
33
|
+
export { MongoJobRepository } from './MongoJobRepository.js';
|
|
34
|
+
export type { MongoBackendConfig, MongoJobRepositoryConfig, MongoDbConfig } from './types.js';
|
|
35
|
+
export type { Db, MongoClientOptions } from 'mongodb';
|
|
36
|
+
export { MongoClient, ObjectId } from 'mongodb';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agendajs/mongo-backend
|
|
3
|
+
*
|
|
4
|
+
* MongoDB backend for Agenda job scheduler.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Agenda } from 'agenda';
|
|
9
|
+
* import { MongoBackend } from '@agendajs/mongo-backend';
|
|
10
|
+
*
|
|
11
|
+
* // Create agenda with MongoDB backend
|
|
12
|
+
* const agenda = new Agenda({
|
|
13
|
+
* backend: new MongoBackend({
|
|
14
|
+
* address: 'mongodb://localhost/agenda'
|
|
15
|
+
* })
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Or with existing connection
|
|
19
|
+
* const agenda = new Agenda({
|
|
20
|
+
* backend: new MongoBackend({ mongo: existingDb })
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Define and run jobs
|
|
24
|
+
* agenda.define('myJob', async (job) => {
|
|
25
|
+
* console.log('Running job:', job.attrs.name);
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* await agenda.start();
|
|
29
|
+
* await agenda.every('5 minutes', 'myJob');
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export { MongoBackend } from './MongoBackend.js';
|
|
33
|
+
export { MongoJobRepository } from './MongoJobRepository.js';
|
|
34
|
+
export { MongoClient, ObjectId } from 'mongodb';
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAM7D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utilities for @agendajs/mongo-backend
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { mockMongo } from '@agendajs/mongo-backend/testing';
|
|
7
|
+
*
|
|
8
|
+
* const { db, disconnect } = await mockMongo();
|
|
9
|
+
* // Use db for testing
|
|
10
|
+
* await disconnect();
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { Db, MongoClient } from 'mongodb';
|
|
14
|
+
export interface IMockMongo {
|
|
15
|
+
disconnect: () => Promise<void>;
|
|
16
|
+
mongo: MongoClient;
|
|
17
|
+
uri: string;
|
|
18
|
+
db: Db;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a mock MongoDB connection for testing.
|
|
22
|
+
* Requires MONGO_URI environment variable to be set (usually by mongodb-memory-server).
|
|
23
|
+
*/
|
|
24
|
+
export declare function mockMongo(): Promise<IMockMongo>;
|
|
25
|
+
export type { Db, MongoClient } from 'mongodb';
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utilities for @agendajs/mongo-backend
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { mockMongo } from '@agendajs/mongo-backend/testing';
|
|
7
|
+
*
|
|
8
|
+
* const { db, disconnect } = await mockMongo();
|
|
9
|
+
* // Use db for testing
|
|
10
|
+
* await disconnect();
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { MongoClient } from 'mongodb';
|
|
14
|
+
import debug from 'debug';
|
|
15
|
+
import { randomUUID } from 'crypto';
|
|
16
|
+
const log = debug('agenda:mock-mongodb');
|
|
17
|
+
/**
|
|
18
|
+
* Create a mock MongoDB connection for testing.
|
|
19
|
+
* Requires MONGO_URI environment variable to be set (usually by mongodb-memory-server).
|
|
20
|
+
*/
|
|
21
|
+
export async function mockMongo() {
|
|
22
|
+
const baseUri = process.env.MONGO_URI;
|
|
23
|
+
if (!baseUri) {
|
|
24
|
+
throw new Error('MONGO_URI not set. Ensure global setup is configured.');
|
|
25
|
+
}
|
|
26
|
+
// Use unique database per test suite to avoid interference
|
|
27
|
+
const dbName = `agenda_test_${randomUUID().replace(/-/g, '')}`;
|
|
28
|
+
// Build URI with database name (ensure single slash before db name)
|
|
29
|
+
const uri = `${baseUri.replace(/\/$/, '')}/${dbName}`;
|
|
30
|
+
log('connecting to mongod with db', dbName);
|
|
31
|
+
const mongo = await MongoClient.connect(uri);
|
|
32
|
+
const db = mongo.db(dbName);
|
|
33
|
+
return {
|
|
34
|
+
uri,
|
|
35
|
+
db,
|
|
36
|
+
mongo,
|
|
37
|
+
disconnect: async () => {
|
|
38
|
+
// Drop the test database to clean up
|
|
39
|
+
await db.dropDatabase();
|
|
40
|
+
await mongo.close();
|
|
41
|
+
log('mongo client closed, db dropped:', dbName);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAM,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,GAAG,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;AASzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC1E,CAAC;IAED,2DAA2D;IAC3D,MAAM,MAAM,GAAG,eAAe,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;IAC/D,oEAAoE;IACpE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;IACtD,GAAG,CAAC,8BAA8B,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAE5B,OAAO;QACN,GAAG;QACH,EAAE;QACF,KAAK;QACL,UAAU,EAAE,KAAK,IAAI,EAAE;YACtB,qCAAqC;YACrC,MAAM,EAAE,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YACpB,GAAG,CAAC,kCAAkC,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Db, MongoClientOptions } from 'mongodb';
|
|
2
|
+
import type { SortDirection } from 'agenda';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for MongoBackend
|
|
5
|
+
*/
|
|
6
|
+
export type MongoConnectionConfig = {
|
|
7
|
+
/** Existing MongoDB database instance */
|
|
8
|
+
mongo: Db;
|
|
9
|
+
} | {
|
|
10
|
+
/** MongoDB connection string */
|
|
11
|
+
address: string;
|
|
12
|
+
/** MongoDB client options */
|
|
13
|
+
options?: MongoClientOptions;
|
|
14
|
+
};
|
|
15
|
+
export type MongoBackendConfig = MongoConnectionConfig & {
|
|
16
|
+
/** Collection name for jobs (default: 'agendaJobs') */
|
|
17
|
+
collection?: string;
|
|
18
|
+
/** Whether to create indexes on connect (default: true) */
|
|
19
|
+
ensureIndex?: boolean;
|
|
20
|
+
/** Sort order for job queries */
|
|
21
|
+
sort?: {
|
|
22
|
+
[key: string]: SortDirection;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Database configuration options used internally by the repository
|
|
27
|
+
*/
|
|
28
|
+
export interface MongoDbConfig {
|
|
29
|
+
ensureIndex?: boolean;
|
|
30
|
+
sort?: {
|
|
31
|
+
[key: string]: SortDirection;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Configuration options for MongoJobRepository
|
|
36
|
+
*/
|
|
37
|
+
export type MongoJobRepositoryConfig = MongoDbConfig & ({
|
|
38
|
+
/** MongoDB connection string */
|
|
39
|
+
db: {
|
|
40
|
+
address: string;
|
|
41
|
+
collection?: string;
|
|
42
|
+
options?: MongoClientOptions;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
/** Existing MongoDB database instance */
|
|
46
|
+
mongo: Db;
|
|
47
|
+
db?: {
|
|
48
|
+
collection?: string;
|
|
49
|
+
};
|
|
50
|
+
});
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agendajs/mongo-backend",
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
|
+
"description": "MongoDB backend for Agenda job scheduler",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"types": "./dist/testing.d.ts",
|
|
15
|
+
"import": "./dist/testing.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git://github.com/agenda/agenda"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"agenda",
|
|
33
|
+
"job",
|
|
34
|
+
"scheduler",
|
|
35
|
+
"mongodb",
|
|
36
|
+
"mongo"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/agenda/agenda/issues"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"debug": "^4.4.0",
|
|
44
|
+
"mongodb": "^6.12.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"agenda": "6.0.0-alpha.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/debug": "^4.1.12",
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"@vitest/coverage-v8": "^3.1.0",
|
|
53
|
+
"mongodb-memory-server": "^10.0.0",
|
|
54
|
+
"tsx": "^4.21.0",
|
|
55
|
+
"vitest": "^3.1.0",
|
|
56
|
+
"agenda": "6.0.0-alpha.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc -b",
|
|
60
|
+
"typecheck": "tsc -b && tsc -p tsconfig.test.json",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"test:coverage": "vitest run --coverage",
|
|
64
|
+
"test:debug": "DEBUG=agenda:**,-agenda:internal:** vitest run"
|
|
65
|
+
}
|
|
66
|
+
}
|