@getvision/server 0.4.2-107ad21-develop → 0.4.3-d4c761e-develop

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @getvision/server
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 3b8f782: Add configurable BullMQ options for queue, worker, and queue events
8
+ - 107ad21: bump package versions in bun.lock
9
+
3
10
  ## 0.4.1
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -205,6 +205,17 @@ const app = new Vision({
205
205
  },
206
206
  pubsub: {
207
207
  devMode: true, // In-memory BullMQ for local dev
208
+ redis: {
209
+ host: 'localhost',
210
+ port: 6379,
211
+ password: 'your-password',
212
+ // Connection settings to prevent timeouts
213
+ keepAlive: 30000, // Keep connection alive (30s)
214
+ maxRetriesPerRequest: 20, // Retry failed commands
215
+ enableReadyCheck: true, // Check Redis is ready
216
+ connectTimeout: 10000, // Connection timeout (10s)
217
+ enableOfflineQueue: true // Queue commands when offline
218
+ },
208
219
  // BullMQ options
209
220
  queue: {
210
221
  defaultJobOptions: {
@@ -362,6 +373,75 @@ app.service('users')
362
373
  })
363
374
  ```
364
375
 
376
+ ## Redis Connection Configuration
377
+
378
+ ### Preventing Connection Drops
379
+
380
+ Redis connections can close due to timeouts or network issues, causing workers to fail with "Connection is closed" errors. Vision Server includes sensible defaults to help prevent this:
381
+
382
+ ```typescript
383
+ const app = new Vision({
384
+ service: { name: 'My API' },
385
+ pubsub: {
386
+ devMode: false, // Use Redis in production
387
+ redis: {
388
+ host: process.env.REDIS_HOST,
389
+ port: parseInt(process.env.REDIS_PORT || '6379'),
390
+ password: process.env.REDIS_PASSWORD,
391
+ // Connection settings (these are the defaults)
392
+ keepAlive: 30000, // Keep connection alive (30s)
393
+ maxRetriesPerRequest: 20, // Retry failed commands
394
+ enableReadyCheck: true, // Check Redis is ready before commands
395
+ connectTimeout: 10000, // Connection timeout (10s)
396
+ enableOfflineQueue: true // Queue commands when offline
397
+ }
398
+ }
399
+ })
400
+ ```
401
+
402
+ **What this helps with:**
403
+ - ✅ **keepAlive (30s)**: Helps prevent idle connection timeouts
404
+ - ✅ **Automatic reconnection**: Retries up to 10 times with exponential backoff
405
+ - ✅ **Connection settings**: Each Queue/Worker/QueueEvents uses these connection settings
406
+ - ✅ **Offline queue**: Commands are queued when Redis is temporarily unavailable
407
+
408
+ ### Environment Variables
409
+
410
+ You can also configure Redis via environment variables:
411
+
412
+ ```bash
413
+ # Option 1: Redis URL (recommended)
414
+ REDIS_URL=redis://:password@hostname:6379
415
+
416
+ # Option 2: Individual variables
417
+ REDIS_HOST=hostname
418
+ REDIS_PORT=6379
419
+ REDIS_PASSWORD=password
420
+ ```
421
+
422
+ ### Troubleshooting
423
+
424
+ **"Connection is closed" errors:**
425
+ - Vision Server now includes automatic reconnection with exponential backoff
426
+ - Check logs for `🔄 Redis reconnecting...` messages
427
+ - If reconnection fails after 10 attempts, check your Redis server health
428
+
429
+ **"Could not renew lock for job" errors:**
430
+ - This happens when workers lose connection during job processing
431
+ - The new keepAlive setting (30s) prevents this
432
+ - Increase `keepAlive` if you have very long-running jobs
433
+
434
+ **Custom retry strategy:**
435
+ ```typescript
436
+ pubsub: {
437
+ redis: {
438
+ // ... other settings
439
+ keepAlive: 60000, // 60s for long-running jobs
440
+ maxRetriesPerRequest: 30 // More retries for unstable networks
441
+ }
442
+ }
443
+ ```
444
+
365
445
  ## Hono Compatibility
366
446
 
367
447
  Vision extends Hono, so all Hono features work:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvision/server",
3
- "version": "0.4.2-107ad21-develop",
3
+ "version": "0.4.3-d4c761e-develop",
4
4
  "type": "module",
5
5
  "description": "Vision Server - Meta-framework with built-in observability, pub/sub, and type-safe APIs",
6
6
  "exports": {
package/src/event-bus.ts CHANGED
@@ -10,6 +10,26 @@ export interface EventBusConfig {
10
10
  host?: string
11
11
  port?: number
12
12
  password?: string
13
+ /**
14
+ * Enable keepalive to prevent connection timeouts (default: true in production)
15
+ */
16
+ keepAlive?: number
17
+ /**
18
+ * Max retry attempts for failed commands (default: 20)
19
+ */
20
+ maxRetriesPerRequest?: number
21
+ /**
22
+ * Enable ready check before executing commands (default: true)
23
+ */
24
+ enableReadyCheck?: boolean
25
+ /**
26
+ * Connection timeout in ms (default: 10000)
27
+ */
28
+ connectTimeout?: number
29
+ /**
30
+ * Enable offline queue (default: true)
31
+ */
32
+ enableOfflineQueue?: boolean
13
33
  }
14
34
  queue?: Omit<QueueOptions, 'connection'>
15
35
  worker?: Omit<WorkerOptions, 'connection'>
@@ -86,6 +106,38 @@ export class EventBus {
86
106
  }
87
107
  }
88
108
 
109
+ /**
110
+ * Get Redis connection configuration
111
+ * Includes keepalive, retry strategy, and connection pooling
112
+ */
113
+ private getRedisConnection() {
114
+ const baseConfig = this.config.redis || {
115
+ host: 'localhost',
116
+ port: 6379,
117
+ }
118
+
119
+ return {
120
+ host: baseConfig.host,
121
+ port: baseConfig.port,
122
+ password: baseConfig.password,
123
+ keepAlive: baseConfig.keepAlive ?? 30000, // 30 seconds keepalive
124
+ maxRetriesPerRequest: baseConfig.maxRetriesPerRequest ?? 20,
125
+ enableReadyCheck: baseConfig.enableReadyCheck ?? true,
126
+ connectTimeout: baseConfig.connectTimeout ?? 10000,
127
+ enableOfflineQueue: baseConfig.enableOfflineQueue ?? true,
128
+ // Retry strategy for automatic reconnection
129
+ retryStrategy: (times: number) => {
130
+ if (times > 10) {
131
+ console.error('❌ Redis connection failed after 10 retries')
132
+ return null // Stop retrying
133
+ }
134
+ const delay = Math.min(times * 200, 3000)
135
+ console.log(`🔄 Redis reconnecting... attempt ${times}, delay ${delay}ms`)
136
+ return delay
137
+ },
138
+ }
139
+ }
140
+
89
141
  /**
90
142
  * Get or create a queue for an event
91
143
  */
@@ -97,13 +149,9 @@ export class EventBus {
97
149
 
98
150
  let queue = this.queues.get(eventName)
99
151
  if (!queue) {
100
- const connection = this.config.redis || {
101
- host: 'localhost',
102
- port: 6379,
103
- }
104
152
  queue = new Queue(eventName, {
105
153
  ...(this.config.queue || {}),
106
- connection,
154
+ connection: this.getRedisConnection(),
107
155
  })
108
156
  this.queues.set(eventName, queue)
109
157
  }
@@ -206,10 +254,6 @@ export class EventBus {
206
254
  this.devModeHandlers.set(eventName, handlers)
207
255
  } else {
208
256
  // Production mode - create BullMQ worker
209
- const connection = this.config.redis || {
210
- host: 'localhost',
211
- port: 6379,
212
- }
213
257
  const workerKey = `${eventName}-handler`
214
258
 
215
259
  // Close existing worker if it exists
@@ -231,7 +275,7 @@ export class EventBus {
231
275
  },
232
276
  {
233
277
  ...(this.config.worker || {}),
234
- connection,
278
+ connection: this.getRedisConnection(),
235
279
  concurrency:
236
280
  options?.concurrency ??
237
281
  this.config.workerConcurrency ??
@@ -244,13 +288,9 @@ export class EventBus {
244
288
 
245
289
  // Listen to queue events
246
290
  if (!this.queueEvents.has(eventName)) {
247
- const connection = this.config.redis || {
248
- host: 'localhost',
249
- port: 6379,
250
- }
251
291
  const queueEvents = new QueueEvents(eventName, {
252
292
  ...(this.config.queueEvents || {}),
253
- connection,
293
+ connection: this.getRedisConnection(),
254
294
  })
255
295
 
256
296
  queueEvents.on('completed', ({ jobId }) => {
@@ -284,10 +324,6 @@ export class EventBus {
284
324
  this.devModeHandlers.set(cronName, handlers)
285
325
  } else {
286
326
  // Production mode - create BullMQ worker for cron jobs
287
- const connection = this.config.redis || {
288
- host: 'localhost',
289
- port: 6379,
290
- }
291
327
  const cronWorkerKey = `${cronName}-handler`
292
328
 
293
329
  // Close existing cron worker if it exists
@@ -315,7 +351,7 @@ export class EventBus {
315
351
  },
316
352
  {
317
353
  ...(this.config.worker || {}),
318
- connection,
354
+ connection: this.getRedisConnection(),
319
355
  concurrency: this.config.worker?.concurrency ?? 1,
320
356
  }
321
357
  )
@@ -326,7 +362,7 @@ export class EventBus {
326
362
  if (!this.queueEvents.has(cronName)) {
327
363
  const queueEvents = new QueueEvents(cronName, {
328
364
  ...(this.config.queueEvents || {}),
329
- connection,
365
+ connection: this.getRedisConnection(),
330
366
  })
331
367
 
332
368
  queueEvents.on('completed', ({ jobId }) => {
package/src/vision-app.ts CHANGED
@@ -124,6 +124,26 @@ export interface VisionConfig {
124
124
  host?: string
125
125
  port?: number
126
126
  password?: string
127
+ /**
128
+ * Enable keepalive to prevent connection timeouts (default: 30000ms)
129
+ */
130
+ keepAlive?: number
131
+ /**
132
+ * Max retry attempts for failed commands (default: 20)
133
+ */
134
+ maxRetriesPerRequest?: number
135
+ /**
136
+ * Enable ready check before executing commands (default: true)
137
+ */
138
+ enableReadyCheck?: boolean
139
+ /**
140
+ * Connection timeout in ms (default: 10000)
141
+ */
142
+ connectTimeout?: number
143
+ /**
144
+ * Enable offline queue (default: true)
145
+ */
146
+ enableOfflineQueue?: boolean
127
147
  }
128
148
  devMode?: boolean // Use in-memory event bus (no Redis required)
129
149
  eventBus?: EventBus // Share EventBus instance across apps (for sub-apps)