Skip to content

Commit 8bc6a9d

Browse files
committed
feat: mongoose driver
1 parent bcf3511 commit 8bc6a9d

10 files changed

Lines changed: 954 additions & 599 deletions

File tree

docs/mongoose-adapter.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Mongoose Adapter for adapter-queue
2+
3+
The Mongoose adapter allows you to use Mongoose models with the adapter-queue system, providing seamless integration with your existing Mongoose-based application. It implements the `DatabaseAdapter` interface directly, offering a native Mongoose experience.
4+
5+
## Installation
6+
7+
First, ensure you have both `adapter-queue` and `mongoose` installed:
8+
9+
```bash
10+
npm install adapter-queue mongoose
11+
```
12+
13+
## Basic Usage
14+
15+
```typescript
16+
import mongoose from 'mongoose';
17+
import { createMongooseQueue } from 'adapter-queue/adapters/mongoose';
18+
19+
// Connect to MongoDB
20+
await mongoose.connect('mongodb://localhost:27017/your-database');
21+
22+
// Create a queue
23+
const queue = createMongooseQueue('my-app');
24+
25+
// Define job handlers
26+
queue.setHandlers({
27+
'send-email': async (job, payload) => {
28+
console.log(`Sending email to ${payload.to}`);
29+
}
30+
});
31+
32+
// Push a job
33+
await queue.push('send-email', { to: 'user@example.com' });
34+
35+
// Start the worker
36+
await queue.run();
37+
```
38+
39+
## Using Custom Models
40+
41+
You can use a custom Mongoose model if you need specific configuration:
42+
43+
```typescript
44+
import mongoose from 'mongoose';
45+
import { createQueueModel, createMongooseQueue } from 'adapter-queue/adapters/mongoose';
46+
47+
// Create a custom model
48+
const JobModel = createQueueModel('MyJob', 'my_jobs_collection');
49+
50+
// Or use the schema directly
51+
import { QueueJobSchema } from 'adapter-queue/adapters/mongoose';
52+
const CustomJobModel = mongoose.model('CustomJob', QueueJobSchema, 'custom_jobs');
53+
54+
// Create queue with custom model
55+
const queue = createMongooseQueue('my-app', CustomJobModel);
56+
```
57+
58+
## Schema Structure
59+
60+
The Mongoose adapter uses the following schema for job documents:
61+
62+
```typescript
63+
{
64+
payload: Buffer, // Serialized job data
65+
ttr: Number, // Time to run (seconds)
66+
delaySeconds: Number, // Delay before available
67+
priority: Number, // Job priority
68+
pushTime: Date, // When job was created
69+
delayTime: Date | null, // When job becomes available
70+
reserveTime: Date | null, // When job was reserved
71+
doneTime: Date | null, // When job was completed
72+
expireTime: Date | null, // When job reservation expires
73+
status: String, // 'waiting' | 'reserved' | 'done' | 'failed'
74+
attempt: Number, // Number of attempts
75+
errorMessage: String // Error message if failed
76+
}
77+
```
78+
79+
## Indexes
80+
81+
The adapter automatically creates the following indexes for optimal performance:
82+
83+
1. `{ status: 1, delayTime: 1, priority: -1, pushTime: 1 }` - For job reservation
84+
2. `{ status: 1, expireTime: 1 }` - For expired job recovery
85+
3. `{ _id: 1, status: 1 }` - For job status lookup
86+
87+
## Integration with Existing Mongoose Apps
88+
89+
The Mongoose adapter integrates seamlessly with existing Mongoose applications:
90+
91+
```typescript
92+
// Your existing Mongoose models
93+
import { User } from './models/User';
94+
import { createMongooseQueue } from 'adapter-queue/adapters/mongoose';
95+
96+
const queue = createMongooseQueue('my-app');
97+
98+
queue.setHandlers({
99+
'welcome-email': async (job, payload) => {
100+
const user = await User.findById(payload.userId);
101+
if (user) {
102+
await sendWelcomeEmail(user.email);
103+
}
104+
}
105+
});
106+
107+
// When creating a user
108+
const user = await User.create({ email: 'new@example.com' });
109+
await queue.push('welcome-email', { userId: user._id });
110+
```
111+
112+
## TypeScript Support
113+
114+
The Mongoose adapter provides full TypeScript support:
115+
116+
```typescript
117+
interface MyJobs {
118+
'send-email': {
119+
to: string;
120+
subject: string;
121+
body: string;
122+
};
123+
'process-payment': {
124+
userId: string;
125+
amount: number;
126+
};
127+
}
128+
129+
const queue = createMongooseQueue<MyJobs>('my-app');
130+
131+
// TypeScript will enforce correct payload types
132+
await queue.push('send-email', {
133+
to: 'user@example.com',
134+
subject: 'Hello',
135+
body: 'Welcome!'
136+
});
137+
```
138+
139+
## Connection Management
140+
141+
The Mongoose adapter uses your existing Mongoose connection. Make sure to:
142+
143+
1. Connect to MongoDB before creating the queue
144+
2. Handle connection errors appropriately
145+
3. Close the connection when shutting down
146+
147+
```typescript
148+
mongoose.connection.on('error', (err) => {
149+
console.error('MongoDB connection error:', err);
150+
});
151+
152+
process.on('SIGINT', async () => {
153+
await mongoose.connection.close();
154+
process.exit(0);
155+
});
156+
```
157+
158+
## Differences from MongoDB Adapter
159+
160+
While the MongoDB adapter works with the native MongoDB driver, the Mongoose adapter:
161+
162+
- Uses Mongoose models and schemas
163+
- Integrates with Mongoose middleware and plugins
164+
- Follows Mongoose conventions and patterns
165+
- Provides schema validation (though disabled for performance in queue operations)
166+
- Works with existing Mongoose connections
167+
168+
Choose the Mongoose adapter when you're already using Mongoose in your application and want to maintain consistency.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import mongoose from 'mongoose';
2+
import { createMongooseQueue, QueueJob } from '../src/adapters/mongoose.ts';
3+
4+
// Define your job types
5+
interface MyJobs {
6+
'send-email': {
7+
to: string;
8+
subject: string;
9+
body: string;
10+
};
11+
'process-image': {
12+
url: string;
13+
width: number;
14+
height: number;
15+
};
16+
}
17+
18+
async function main() {
19+
// Connect to MongoDB
20+
await mongoose.connect('mongodb://localhost:27017/queue-example');
21+
console.log('Connected to MongoDB');
22+
23+
// Create a queue instance
24+
const queue = createMongooseQueue<MyJobs>('my-app');
25+
26+
// Set up job handlers
27+
queue.setHandlers({
28+
'send-email': async (job, payload) => {
29+
console.log(`Sending email to ${payload.to}`);
30+
console.log(`Subject: ${payload.subject}`);
31+
console.log(`Body: ${payload.body}`);
32+
// Simulate email sending
33+
await new Promise(resolve => setTimeout(resolve, 1000));
34+
console.log('Email sent successfully!');
35+
},
36+
37+
'process-image': async (job, payload) => {
38+
console.log(`Processing image: ${payload.url}`);
39+
console.log(`Dimensions: ${payload.width}x${payload.height}`);
40+
// Simulate image processing
41+
await new Promise(resolve => setTimeout(resolve, 2000));
42+
console.log('Image processed successfully!');
43+
}
44+
});
45+
46+
// Push some jobs
47+
const emailJobId = await queue.addJob('send-email', {
48+
payload: {
49+
to: 'user@example.com',
50+
subject: 'Welcome!',
51+
body: 'Thank you for signing up!'
52+
}
53+
});
54+
console.log(`Email job created with ID: ${emailJobId}`);
55+
56+
const imageJobId = await queue.addJob('process-image', {
57+
payload: {
58+
url: 'https://example.com/image.jpg',
59+
width: 800,
60+
height: 600
61+
},
62+
delaySeconds: 5, // Delay for 5 seconds
63+
priority: 10 // Higher priority
64+
});
65+
console.log(`Image job created with ID: ${imageJobId}`);
66+
67+
// Run the queue worker
68+
console.log('\nStarting queue worker...');
69+
await queue.run();
70+
}
71+
72+
// Alternative: Using a custom model
73+
async function customModelExample(): Promise<void> {
74+
await mongoose.connect('mongodb://localhost:27017/queue-example');
75+
76+
// Create a custom model with a different collection name
77+
const JobModel = mongoose.model('CustomJob', QueueJob.schema, 'custom_jobs');
78+
79+
// Create queue with custom model
80+
const queue = createMongooseQueue<MyJobs>('custom-app', JobModel);
81+
82+
// Set handlers and use as normal
83+
queue.setHandlers({
84+
'send-email': async (job, payload) => {
85+
console.log(`Custom handler: Sending email to ${payload.to}`);
86+
}
87+
});
88+
89+
await queue.addJob('send-email', {
90+
payload: {
91+
to: 'custom@example.com',
92+
subject: 'Custom Queue',
93+
body: 'This uses a custom model!'
94+
}
95+
});
96+
}
97+
98+
// Run the example
99+
main().catch(console.error);
100+
101+
// Export for testing
102+
export { main, customModelExample };

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@types/node": "^20.0.0",
5252
"better-sqlite3": "^12.0.0",
5353
"mongodb": "^6.17.0",
54+
"mongoose": "^8.0.0",
5455
"redis": "^5.5.6",
5556
"testcontainers": "^11.0.3",
5657
"typescript": "^5.0.0",
@@ -84,12 +85,17 @@
8485
"./memory": {
8586
"import": "./dist/src/adapters/memory.js",
8687
"types": "./dist/src/adapters/memory.d.ts"
88+
},
89+
"./mongoose": {
90+
"import": "./dist/src/adapters/mongoose.js",
91+
"types": "./dist/src/adapters/mongoose.d.ts"
8792
}
8893
},
8994
"peerDependencies": {
9095
"@aws-sdk/client-sqs": "^3.0.0",
9196
"better-sqlite3": "^9.0.0",
9297
"mongodb": "^6.0.0",
98+
"mongoose": "^7.0.0 || ^8.0.0",
9399
"redis": "^4.0.0"
94100
},
95101
"peerDependenciesMeta": {
@@ -102,6 +108,9 @@
102108
"mongodb": {
103109
"optional": true
104110
},
111+
"mongoose": {
112+
"optional": true
113+
},
105114
"redis": {
106115
"optional": true
107116
}

0 commit comments

Comments
 (0)