worker-que 1.0.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/DASHBOARD-QUICKSTART.md +278 -0
- package/DASHBOARD.md +556 -0
- package/LICENSE +21 -0
- package/README.md +414 -0
- package/SSL-QUICK-REFERENCE.md +225 -0
- package/SSL.md +516 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +64 -0
- package/dist/client.js.map +1 -0
- package/dist/dashboard/index.d.ts +34 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +164 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/service.d.ts +66 -0
- package/dist/dashboard/service.d.ts.map +1 -0
- package/dist/dashboard/service.js +201 -0
- package/dist/dashboard/service.js.map +1 -0
- package/dist/dashboard/views.d.ts +3 -0
- package/dist/dashboard/views.d.ts.map +1 -0
- package/dist/dashboard/views.js +786 -0
- package/dist/dashboard/views.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/job.d.ts +19 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +36 -0
- package/dist/job.js.map +1 -0
- package/dist/sql.d.ts +8 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +57 -0
- package/dist/sql.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +31 -0
- package/dist/utils.js.map +1 -0
- package/dist/worker.d.ts +19 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +99 -0
- package/dist/worker.js.map +1 -0
- package/migrations/schema.sql +26 -0
- package/package.json +105 -0
package/README.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# que-ts
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Duke-Engineering/que-ts/actions/workflows/test.yml)
|
|
4
|
+
[](https://github.com/Duke-Engineering/que-ts/actions/workflows/coverage.yml)
|
|
5
|
+
[](https://github.com/Duke-Engineering/que-ts/actions/workflows/security.yml)
|
|
6
|
+
[](https://badge.fury.io/js/que-ts)
|
|
7
|
+
|
|
8
|
+
A TypeScript job queue library for PostgreSQL, compatible with Ruby Que and que-go implementations.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Cross-language compatibility**: Works with [Que (Ruby)](https://github.com/chanks/que) and [que-go](https://github.com/bgentry/que-go) job queues
|
|
13
|
+
- **PostgreSQL advisory locks**: Reliable job processing with no duplicate execution
|
|
14
|
+
- **TypeScript support**: Full type safety with comprehensive interfaces
|
|
15
|
+
- **Retry logic**: Exponential backoff for failed jobs
|
|
16
|
+
- **Multiple queues**: Support for named queues and priorities
|
|
17
|
+
- **Transaction support**: Enqueue jobs within existing database transactions
|
|
18
|
+
- **Web Dashboard**: Real-time monitoring and management UI (Express.js integration)
|
|
19
|
+
- **SSL/TLS Support**: Secure connections with client certificates
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### From npm (when published)
|
|
24
|
+
```bash
|
|
25
|
+
npm install que-ts
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### From GitHub (development)
|
|
29
|
+
```bash
|
|
30
|
+
npm install github:Duke-Engineering/que-ts#master
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Note**: When installing from GitHub, the package will automatically build from TypeScript source using the `prepare` script.
|
|
34
|
+
|
|
35
|
+
### Troubleshooting GitHub Installation
|
|
36
|
+
|
|
37
|
+
If you encounter "Cannot find module 'que-ts'" errors when installing from GitHub:
|
|
38
|
+
|
|
39
|
+
1. **Check the installation completed successfully**:
|
|
40
|
+
```bash
|
|
41
|
+
cd node_modules/que-ts
|
|
42
|
+
ls dist/ # Should show compiled JavaScript files
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **Manual build if needed**:
|
|
46
|
+
```bash
|
|
47
|
+
cd node_modules/que-ts
|
|
48
|
+
npm run build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
3. **Verify installation**:
|
|
52
|
+
```bash
|
|
53
|
+
cd node_modules/que-ts
|
|
54
|
+
node test-install.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
4. **Alternative: Use specific tag**:
|
|
58
|
+
```bash
|
|
59
|
+
npm install github:Duke-Engineering/que-ts#v1.0.0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### Database Setup
|
|
65
|
+
|
|
66
|
+
#### Option 1: Using Docker (Recommended for Development)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Start PostgreSQL with Docker
|
|
70
|
+
npm run docker:up
|
|
71
|
+
|
|
72
|
+
# Run tests
|
|
73
|
+
npm test
|
|
74
|
+
|
|
75
|
+
# Stop when done
|
|
76
|
+
npm run docker:down
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For detailed Docker usage, see [DOCKER.md](DOCKER.md).
|
|
80
|
+
|
|
81
|
+
#### Option 2: Manual Database Setup
|
|
82
|
+
|
|
83
|
+
Create the required database table:
|
|
84
|
+
|
|
85
|
+
```sql
|
|
86
|
+
CREATE TABLE que_jobs (
|
|
87
|
+
priority smallint NOT NULL DEFAULT 100,
|
|
88
|
+
run_at timestamptz NOT NULL DEFAULT now(),
|
|
89
|
+
job_id bigserial NOT NULL,
|
|
90
|
+
job_class text NOT NULL,
|
|
91
|
+
args json NOT NULL DEFAULT '[]'::json,
|
|
92
|
+
error_count integer NOT NULL DEFAULT 0,
|
|
93
|
+
last_error text,
|
|
94
|
+
queue text NOT NULL DEFAULT '',
|
|
95
|
+
PRIMARY KEY (queue, priority, run_at, job_id)
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Basic Usage
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { Client, Worker } from 'que-ts';
|
|
103
|
+
|
|
104
|
+
// Create a client
|
|
105
|
+
const client = new Client({
|
|
106
|
+
host: 'localhost',
|
|
107
|
+
port: 5432,
|
|
108
|
+
database: 'myapp',
|
|
109
|
+
user: 'postgres',
|
|
110
|
+
password: 'password'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Enqueue jobs
|
|
114
|
+
await client.enqueue('SendEmail', ['user@example.com', 'Welcome!']);
|
|
115
|
+
|
|
116
|
+
await client.enqueue('ProcessPayment', [{ amount: 100, currency: 'USD' }], {
|
|
117
|
+
priority: 10,
|
|
118
|
+
runAt: new Date(Date.now() + 60000) // Run in 1 minute
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Create and start a worker
|
|
122
|
+
const worker = new Worker({
|
|
123
|
+
host: 'localhost',
|
|
124
|
+
port: 5432,
|
|
125
|
+
database: 'myapp',
|
|
126
|
+
user: 'postgres',
|
|
127
|
+
password: 'password'
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Register job handlers
|
|
131
|
+
worker.register('SendEmail', async (job) => {
|
|
132
|
+
const [email, message] = job.args;
|
|
133
|
+
console.log(`Sending email to ${email}: ${message}`);
|
|
134
|
+
// Email sending logic here
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
worker.register('ProcessPayment', async (job) => {
|
|
138
|
+
const paymentData = job.args[0];
|
|
139
|
+
console.log(`Processing payment of ${paymentData.amount} ${paymentData.currency}`);
|
|
140
|
+
// Payment processing logic here
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Start processing jobs
|
|
144
|
+
await worker.work();
|
|
145
|
+
|
|
146
|
+
// Graceful shutdown
|
|
147
|
+
process.on('SIGINT', async () => {
|
|
148
|
+
await worker.shutdown();
|
|
149
|
+
await client.close();
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Web Dashboard
|
|
154
|
+
|
|
155
|
+
que-ts includes a beautiful, real-time web dashboard for monitoring and managing your job queue.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import express from 'express';
|
|
159
|
+
import { Pool } from 'pg';
|
|
160
|
+
import { createDashboard } from 'que-ts/dashboard';
|
|
161
|
+
|
|
162
|
+
const app = express();
|
|
163
|
+
const pool = new Pool({ /* your config */ });
|
|
164
|
+
|
|
165
|
+
// Mount dashboard at /admin/queue
|
|
166
|
+
app.use('/admin/queue', createDashboard(pool, {
|
|
167
|
+
title: 'My App Queue',
|
|
168
|
+
refreshInterval: 3000,
|
|
169
|
+
auth: (req, res, next) => {
|
|
170
|
+
// Add your authentication logic
|
|
171
|
+
return req.isAuthenticated();
|
|
172
|
+
}
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
app.listen(3000);
|
|
176
|
+
// Visit http://localhost:3000/admin/queue
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Dashboard Features:**
|
|
180
|
+
- 📊 Real-time statistics (total, ready, scheduled, failed jobs)
|
|
181
|
+
- 📈 Visual analytics (charts by queue and job class)
|
|
182
|
+
- 🔍 Advanced filtering (status, queue, job class)
|
|
183
|
+
- 🔄 Job management (retry failed jobs, delete jobs)
|
|
184
|
+
- 🔐 Built-in authentication support
|
|
185
|
+
- 📱 Responsive design
|
|
186
|
+
|
|
187
|
+
For complete dashboard documentation, see [DASHBOARD.md](DASHBOARD.md).
|
|
188
|
+
|
|
189
|
+
## SSL Configuration
|
|
190
|
+
|
|
191
|
+
For detailed SSL configuration including AWS RDS, Google Cloud SQL, Azure, and certificate setup, see [SSL.md](SSL.md).
|
|
192
|
+
|
|
193
|
+
## API Reference
|
|
194
|
+
|
|
195
|
+
### Client
|
|
196
|
+
|
|
197
|
+
#### Constructor
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
new Client(config?: ClientConfig)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Methods
|
|
204
|
+
|
|
205
|
+
- `enqueue(jobClass: string, args?: any[], options?: EnqueueOptions): Promise<Job>`
|
|
206
|
+
- `enqueueInTx(client: PoolClient, jobClass: string, args?: any[], options?: EnqueueOptions): Promise<Job>`
|
|
207
|
+
- `lockJob(queue?: string): Promise<Job | null>`
|
|
208
|
+
- `close(): Promise<void>`
|
|
209
|
+
|
|
210
|
+
### Worker
|
|
211
|
+
|
|
212
|
+
#### Constructor
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
new Worker(clientConfig?: ClientConfig, options?: WorkerOptions)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Methods
|
|
219
|
+
|
|
220
|
+
- `register(jobClass: string, workFunc: WorkFunction): void`
|
|
221
|
+
- `work(): Promise<void>`
|
|
222
|
+
- `workOne(): Promise<boolean>`
|
|
223
|
+
- `shutdown(): Promise<void>`
|
|
224
|
+
|
|
225
|
+
### Job
|
|
226
|
+
|
|
227
|
+
#### Properties
|
|
228
|
+
|
|
229
|
+
- `id: number`
|
|
230
|
+
- `queue: string`
|
|
231
|
+
- `priority: number`
|
|
232
|
+
- `runAt: Date`
|
|
233
|
+
- `jobClass: string`
|
|
234
|
+
- `args: any[]`
|
|
235
|
+
- `errorCount: number`
|
|
236
|
+
- `lastError?: string`
|
|
237
|
+
|
|
238
|
+
#### Methods
|
|
239
|
+
|
|
240
|
+
- `done(): Promise<void>`
|
|
241
|
+
- `delete(): Promise<void>`
|
|
242
|
+
- `error(errorMessage: string): Promise<void>`
|
|
243
|
+
|
|
244
|
+
## Configuration
|
|
245
|
+
|
|
246
|
+
### ClientConfig
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
interface ClientConfig {
|
|
250
|
+
connectionString?: string;
|
|
251
|
+
host?: string;
|
|
252
|
+
port?: number;
|
|
253
|
+
database?: string;
|
|
254
|
+
user?: string;
|
|
255
|
+
password?: string;
|
|
256
|
+
ssl?: boolean | SSLConfig; // See SSL Configuration below
|
|
257
|
+
maxConnections?: number;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### SSL Configuration
|
|
262
|
+
|
|
263
|
+
que-ts supports SSL/TLS connections with client certificates:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { Client } from 'que-ts';
|
|
267
|
+
import * as fs from 'fs';
|
|
268
|
+
|
|
269
|
+
// Basic SSL
|
|
270
|
+
const client = new Client({
|
|
271
|
+
host: 'your-database.com',
|
|
272
|
+
port: 5432,
|
|
273
|
+
database: 'mydb',
|
|
274
|
+
user: 'myuser',
|
|
275
|
+
password: 'mypassword',
|
|
276
|
+
ssl: {
|
|
277
|
+
rejectUnauthorized: true,
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// SSL with client certificates
|
|
282
|
+
const clientWithCerts = new Client({
|
|
283
|
+
host: 'your-database.com',
|
|
284
|
+
port: 5432,
|
|
285
|
+
database: 'mydb',
|
|
286
|
+
user: 'myuser',
|
|
287
|
+
password: 'mypassword',
|
|
288
|
+
ssl: {
|
|
289
|
+
rejectUnauthorized: true,
|
|
290
|
+
cert: fs.readFileSync('./certs/client-cert.pem'),
|
|
291
|
+
key: fs.readFileSync('./certs/client-key.pem'),
|
|
292
|
+
ca: fs.readFileSync('./certs/ca-cert.pem'),
|
|
293
|
+
passphrase: 'optional-key-passphrase', // For encrypted keys
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
For detailed SSL configuration including AWS RDS, Google Cloud SQL, Azure, and certificate setup, see [SSL.md](SSL.md).
|
|
299
|
+
|
|
300
|
+
### WorkerOptions
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
interface WorkerOptions {
|
|
304
|
+
queue?: string; // Queue name to process (default: '')
|
|
305
|
+
interval?: number; // Polling interval in ms (default: 5000)
|
|
306
|
+
maxAttempts?: number; // Max retry attempts (default: 5)
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### EnqueueOptions
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
interface EnqueueOptions {
|
|
314
|
+
priority?: number; // Job priority (lower = higher priority, default: 100)
|
|
315
|
+
runAt?: Date; // When to run the job (default: now)
|
|
316
|
+
queue?: string; // Queue name (default: '')
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Error Handling and Retries
|
|
321
|
+
|
|
322
|
+
Failed jobs are automatically retried with exponential backoff:
|
|
323
|
+
|
|
324
|
+
- 1st retry: after 1 second
|
|
325
|
+
- 2nd retry: after 16 seconds
|
|
326
|
+
- 3rd retry: after 81 seconds
|
|
327
|
+
- 4th retry: after 256 seconds
|
|
328
|
+
- etc.
|
|
329
|
+
|
|
330
|
+
Jobs that exceed the maximum number of retries remain in the queue for manual inspection.
|
|
331
|
+
|
|
332
|
+
## Queues and Priorities
|
|
333
|
+
|
|
334
|
+
Jobs can be organized into named queues and assigned priorities:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// High priority job in 'critical' queue
|
|
338
|
+
await client.enqueue('ProcessPayment', [paymentData], {
|
|
339
|
+
queue: 'critical',
|
|
340
|
+
priority: 1
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Low priority job in 'background' queue
|
|
344
|
+
await client.enqueue('SendNewsletter', [newsletterData], {
|
|
345
|
+
queue: 'background',
|
|
346
|
+
priority: 500
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Worker processing only critical queue
|
|
350
|
+
const criticalWorker = new Worker(config, { queue: 'critical' });
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Cross-Language Compatibility
|
|
354
|
+
|
|
355
|
+
que-ts is designed to be fully compatible with:
|
|
356
|
+
|
|
357
|
+
- **[Ruby Que](https://github.com/chanks/que)** - The original Ruby implementation
|
|
358
|
+
- **[que-go](https://github.com/bgentry/que-go)** - Golang port (currently unmaintained)
|
|
359
|
+
|
|
360
|
+
You can enqueue jobs in one language and process them in another, or run workers in multiple languages simultaneously.
|
|
361
|
+
|
|
362
|
+
## Related Projects
|
|
363
|
+
|
|
364
|
+
### Official Implementations
|
|
365
|
+
- **[Que (Ruby)](https://github.com/chanks/que)** - The original and most mature implementation
|
|
366
|
+
- **[que-go](https://github.com/bgentry/que-go)** - Go implementation (unmaintained, but stable)
|
|
367
|
+
- **[que-ts](https://github.com/Duke-Engineering/que-ts)** - This TypeScript/Node.js implementation
|
|
368
|
+
|
|
369
|
+
## Development
|
|
370
|
+
|
|
371
|
+
### Using Docker (Recommended)
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# Install dependencies
|
|
375
|
+
npm install
|
|
376
|
+
|
|
377
|
+
# Start PostgreSQL with Docker
|
|
378
|
+
npm run docker:up
|
|
379
|
+
|
|
380
|
+
# Run tests
|
|
381
|
+
npm test
|
|
382
|
+
|
|
383
|
+
# Run tests in watch mode
|
|
384
|
+
npm run test:watch
|
|
385
|
+
|
|
386
|
+
# Build
|
|
387
|
+
npm run build
|
|
388
|
+
|
|
389
|
+
# Lint
|
|
390
|
+
npm run lint
|
|
391
|
+
|
|
392
|
+
# Stop Docker containers
|
|
393
|
+
npm run docker:down
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Docker Commands
|
|
397
|
+
|
|
398
|
+
- `npm run docker:up` - Start PostgreSQL and Adminer
|
|
399
|
+
- `npm run docker:down` - Stop containers
|
|
400
|
+
- `npm run docker:logs` - View PostgreSQL logs
|
|
401
|
+
- `npm run docker:clean` - Remove containers and volumes
|
|
402
|
+
- `npm run test:docker` - Full test cycle with Docker
|
|
403
|
+
|
|
404
|
+
Access database admin at http://localhost:8080 (user: `que_user`, password: `que_password`)
|
|
405
|
+
|
|
406
|
+
See [DOCKER.md](DOCKER.md) for detailed Docker documentation.
|
|
407
|
+
|
|
408
|
+
### Manual Setup
|
|
409
|
+
|
|
410
|
+
If you prefer not to use Docker, ensure PostgreSQL is running and create the database schema manually using `migrations/schema.sql`.
|
|
411
|
+
|
|
412
|
+
## License
|
|
413
|
+
|
|
414
|
+
MIT
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# SSL Quick Reference
|
|
2
|
+
|
|
3
|
+
## Quick SSL Setup Examples
|
|
4
|
+
|
|
5
|
+
### 1. Basic SSL (No Certificates)
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Client } from 'que-ts';
|
|
9
|
+
|
|
10
|
+
const client = new Client({
|
|
11
|
+
host: 'db.example.com',
|
|
12
|
+
port: 5432,
|
|
13
|
+
database: 'mydb',
|
|
14
|
+
user: 'myuser',
|
|
15
|
+
password: 'mypass',
|
|
16
|
+
ssl: { rejectUnauthorized: true }
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. SSL with Client Certificates
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import * as fs from 'fs';
|
|
24
|
+
|
|
25
|
+
const client = new Client({
|
|
26
|
+
host: 'db.example.com',
|
|
27
|
+
port: 5432,
|
|
28
|
+
database: 'mydb',
|
|
29
|
+
user: 'myuser',
|
|
30
|
+
password: 'mypass',
|
|
31
|
+
ssl: {
|
|
32
|
+
rejectUnauthorized: true,
|
|
33
|
+
cert: fs.readFileSync('./client-cert.pem'),
|
|
34
|
+
key: fs.readFileSync('./client-key.pem'),
|
|
35
|
+
ca: fs.readFileSync('./ca-cert.pem'),
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. Using Environment Variables
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// .env file:
|
|
44
|
+
// DB_SSL_CERT=/path/to/client-cert.pem
|
|
45
|
+
// DB_SSL_KEY=/path/to/client-key.pem
|
|
46
|
+
// DB_SSL_CA=/path/to/ca-cert.pem
|
|
47
|
+
|
|
48
|
+
import * as fs from 'fs';
|
|
49
|
+
import * as dotenv from 'dotenv';
|
|
50
|
+
dotenv.config();
|
|
51
|
+
|
|
52
|
+
const client = new Client({
|
|
53
|
+
host: process.env.DB_HOST,
|
|
54
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
55
|
+
database: process.env.DB_NAME,
|
|
56
|
+
user: process.env.DB_USER,
|
|
57
|
+
password: process.env.DB_PASSWORD,
|
|
58
|
+
ssl: {
|
|
59
|
+
rejectUnauthorized: true,
|
|
60
|
+
cert: fs.readFileSync(process.env.DB_SSL_CERT!),
|
|
61
|
+
key: fs.readFileSync(process.env.DB_SSL_KEY!),
|
|
62
|
+
ca: fs.readFileSync(process.env.DB_SSL_CA!),
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Cloud Providers
|
|
68
|
+
|
|
69
|
+
### AWS RDS
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Download CA bundle: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
|
|
73
|
+
const client = new Client({
|
|
74
|
+
host: 'instance.region.rds.amazonaws.com',
|
|
75
|
+
ssl: {
|
|
76
|
+
rejectUnauthorized: true,
|
|
77
|
+
ca: fs.readFileSync('./rds-ca-bundle.pem'),
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Google Cloud SQL
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const client = new Client({
|
|
86
|
+
host: 'instance-ip',
|
|
87
|
+
ssl: {
|
|
88
|
+
rejectUnauthorized: true,
|
|
89
|
+
ca: fs.readFileSync('./server-ca.pem'),
|
|
90
|
+
cert: fs.readFileSync('./client-cert.pem'),
|
|
91
|
+
key: fs.readFileSync('./client-key.pem'),
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Azure PostgreSQL
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Download cert: https://learn.microsoft.com/en-us/azure/postgresql/
|
|
100
|
+
const client = new Client({
|
|
101
|
+
host: 'server.postgres.database.azure.com',
|
|
102
|
+
user: 'user@server',
|
|
103
|
+
ssl: {
|
|
104
|
+
rejectUnauthorized: true,
|
|
105
|
+
ca: fs.readFileSync('./BaltimoreCyberTrustRoot.crt.pem'),
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Heroku Postgres
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const client = new Client({
|
|
114
|
+
connectionString: process.env.DATABASE_URL,
|
|
115
|
+
ssl: { rejectUnauthorized: false } // Heroku uses self-signed
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Certificate File Permissions
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
chmod 600 client-key.pem # Private key (read/write owner only)
|
|
123
|
+
chmod 644 client-cert.pem # Certificate (readable by all)
|
|
124
|
+
chmod 644 ca-cert.pem # CA certificate (readable by all)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Generate Self-Signed Certificates (Dev/Testing)
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# CA certificate
|
|
131
|
+
openssl req -new -x509 -days 365 -nodes -text \
|
|
132
|
+
-out ca-cert.pem -keyout ca-key.pem
|
|
133
|
+
|
|
134
|
+
# Client certificate and key
|
|
135
|
+
openssl req -new -nodes -text \
|
|
136
|
+
-out client.csr -keyout client-key.pem
|
|
137
|
+
|
|
138
|
+
openssl x509 -req -in client.csr -text -days 365 \
|
|
139
|
+
-CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \
|
|
140
|
+
-out client-cert.pem
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Troubleshooting
|
|
144
|
+
|
|
145
|
+
| Error | Solution |
|
|
146
|
+
|-------|----------|
|
|
147
|
+
| "self signed certificate" | Add `ca` certificate or set `rejectUnauthorized: false` (dev only) |
|
|
148
|
+
| "unable to verify first certificate" | Include complete certificate chain in `ca` |
|
|
149
|
+
| "certificate has expired" | Renew certificates |
|
|
150
|
+
| "ENOENT: no such file" | Check file paths are correct and files exist |
|
|
151
|
+
|
|
152
|
+
## Test SSL Connection
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
async function testConnection() {
|
|
156
|
+
const client = new Client({
|
|
157
|
+
host: 'your-host',
|
|
158
|
+
ssl: {
|
|
159
|
+
rejectUnauthorized: true,
|
|
160
|
+
cert: fs.readFileSync('./client-cert.pem'),
|
|
161
|
+
key: fs.readFileSync('./client-key.pem'),
|
|
162
|
+
ca: fs.readFileSync('./ca-cert.pem'),
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const job = await client.enqueue('Test', []);
|
|
168
|
+
console.log('✓ Connected! Job ID:', job.id);
|
|
169
|
+
await client.close();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error('✗ Failed:', err.message);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Security Checklist
|
|
177
|
+
|
|
178
|
+
- [ ] Use `rejectUnauthorized: true` in production
|
|
179
|
+
- [ ] Never commit certificates to git
|
|
180
|
+
- [ ] Use environment variables for paths/secrets
|
|
181
|
+
- [ ] Set correct file permissions (600 for keys)
|
|
182
|
+
- [ ] Rotate certificates regularly
|
|
183
|
+
- [ ] Use strong key passphrases
|
|
184
|
+
- [ ] Add `*.pem`, `*.key`, `*.crt` to `.gitignore`
|
|
185
|
+
|
|
186
|
+
## Complete Example
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// config.ts
|
|
190
|
+
import { Client, SSLConfig } from 'que-ts';
|
|
191
|
+
import * as fs from 'fs';
|
|
192
|
+
|
|
193
|
+
export function createSecureClient(): Client {
|
|
194
|
+
const sslConfig: SSLConfig = {
|
|
195
|
+
rejectUnauthorized: true,
|
|
196
|
+
cert: fs.readFileSync(process.env.DB_SSL_CERT || './certs/client-cert.pem'),
|
|
197
|
+
key: fs.readFileSync(process.env.DB_SSL_KEY || './certs/client-key.pem'),
|
|
198
|
+
ca: fs.readFileSync(process.env.DB_SSL_CA || './certs/ca-cert.pem'),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return new Client({
|
|
202
|
+
host: process.env.DB_HOST || 'localhost',
|
|
203
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
204
|
+
database: process.env.DB_NAME || 'mydb',
|
|
205
|
+
user: process.env.DB_USER || 'postgres',
|
|
206
|
+
password: process.env.DB_PASSWORD,
|
|
207
|
+
ssl: process.env.NODE_ENV === 'production' ? sslConfig : false,
|
|
208
|
+
maxConnections: 10,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// usage.ts
|
|
213
|
+
import { createSecureClient } from './config';
|
|
214
|
+
|
|
215
|
+
const client = createSecureClient();
|
|
216
|
+
|
|
217
|
+
async function main() {
|
|
218
|
+
await client.enqueue('SendEmail', ['user@example.com']);
|
|
219
|
+
await client.close();
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
For full documentation, see [SSL.md](SSL.md)
|