Skip to content

Commit 778381b

Browse files
committed
fixes
1 parent 2f7183b commit 778381b

File tree

7 files changed

+150
-74
lines changed

7 files changed

+150
-74
lines changed

spec/utils/__snapshots__/generate-workers-entry-content.spec.ts.snap

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`generate-workers-entry-content > matches snapshot for multiple workers and redis url 1`] = `
3+
exports[`generate-workers-entry-content > matches snapshot for multiple workers and redis config 1`] = `
44
"
55
import { fileURLToPath } from 'node:url'
66
import { resolve as resolvePath } from 'node:path'
77
import { consola } from 'consola'
88
import { $workers } from '#processor-utils'
9+
import { resolveRedisConnection } from '#resolve-redis'
910
1011
// Initialize connection as early as possible so any imports that register
1112
// workers/queues have a valid connection available.
1213
const api = $workers()
13-
api.setConnection(new (await import("ioredis")).default("redis://localhost:6379"))
14+
api.setConnection(resolveRedisConnection({"host":"localhost","port":6379}))
1415
1516
export async function createWorkersApp() {
1617
// Avoid EPIPE when stdout/stderr are closed by terminal (e.g., Ctrl+C piping)
@@ -108,17 +109,18 @@ export default { createWorkersApp }
108109
"
109110
`;
110111
111-
exports[`generate-workers-entry-content > matches snapshot for single worker and undefined redis 1`] = `
112+
exports[`generate-workers-entry-content > matches snapshot for single worker and empty redis config 1`] = `
112113
"
113114
import { fileURLToPath } from 'node:url'
114115
import { resolve as resolvePath } from 'node:path'
115116
import { consola } from 'consola'
116117
import { $workers } from '#processor-utils'
118+
import { resolveRedisConnection } from '#resolve-redis'
117119
118120
// Initialize connection as early as possible so any imports that register
119121
// workers/queues have a valid connection available.
120122
const api = $workers()
121-
api.setConnection(undefined)
123+
api.setConnection(resolveRedisConnection({}))
122124
123125
export async function createWorkersApp() {
124126
// Avoid EPIPE when stdout/stderr are closed by terminal (e.g., Ctrl+C piping)
Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2-
import { generateRedisConnectionExpr } from '../../src/utils/generate-redis-connection-expr'
3-
4-
// Helper: evaluate the generated expression in current process context
5-
function evalExpr(expr: string): unknown {
6-
return eval(expr)
7-
}
2+
import { generateRedisConnectionExpr, getRedisConnectionImport } from '../../src/utils/generate-redis-connection-expr'
3+
import { resolveRedisConnection } from '../../src/runtime/server/utils/resolve-redis-connection'
84

95
function clearEnvKey(key: string) {
106
Reflect.deleteProperty(process.env, key)
117
}
128

139
describe('generateRedisConnectionExpr', () => {
14-
const savedEnv: Record<string, string | undefined> = {}
10+
it('wraps static config in a resolveRedisConnection call', () => {
11+
const expr = generateRedisConnectionExpr('{"host":"10.0.0.1"}')
12+
expect(expr).toBe('resolveRedisConnection({"host":"10.0.0.1"})')
13+
})
14+
15+
it('handles empty config', () => {
16+
const expr = generateRedisConnectionExpr('{}')
17+
expect(expr).toBe('resolveRedisConnection({})')
18+
})
19+
})
20+
21+
describe('getRedisConnectionImport', () => {
22+
it('returns an import statement with the given alias', () => {
23+
expect(getRedisConnectionImport('#resolve-redis'))
24+
.toBe('import { resolveRedisConnection } from \'#resolve-redis\'')
25+
})
26+
})
27+
28+
describe('resolveRedisConnection', () => {
1529
const envKeys = [
1630
'NUXT_REDIS_URL',
1731
'NUXT_REDIS_HOST',
@@ -22,6 +36,7 @@ describe('generateRedisConnectionExpr', () => {
2236
'NUXT_REDIS_LAZY_CONNECT',
2337
'NUXT_REDIS_CONNECT_TIMEOUT',
2438
]
39+
const savedEnv: Record<string, string | undefined> = {}
2540

2641
beforeEach(() => {
2742
for (const key of envKeys) {
@@ -42,9 +57,7 @@ describe('generateRedisConnectionExpr', () => {
4257
})
4358

4459
it('returns static config when no env vars are set', () => {
45-
const staticRedis = JSON.stringify({ host: '10.0.0.1', port: 6380, password: 'secret', db: 2 })
46-
const expr = generateRedisConnectionExpr(staticRedis)
47-
const result = evalExpr(expr) as Record<string, unknown>
60+
const result = resolveRedisConnection({ host: '10.0.0.1', port: 6380, password: 'secret', db: 2 })
4861

4962
expect(result).toEqual({
5063
host: '10.0.0.1',
@@ -57,21 +70,21 @@ describe('generateRedisConnectionExpr', () => {
5770
})
5871
})
5972

60-
it('returns NUXT_REDIS_URL when set at runtime', () => {
73+
it('includes url in returned object when NUXT_REDIS_URL is set', () => {
6174
process.env.NUXT_REDIS_URL = 'redis://prod-host:6379/1'
62-
const staticRedis = JSON.stringify({ host: '127.0.0.1', port: 6379 })
63-
const expr = generateRedisConnectionExpr(staticRedis)
64-
const result = evalExpr(expr)
75+
const result = resolveRedisConnection({ host: '127.0.0.1', port: 6379 })
6576

66-
expect(result).toBe('redis://prod-host:6379/1')
77+
expect(result).toMatchObject({ url: 'redis://prod-host:6379/1' })
78+
// Other options are still present alongside url
79+
expect(result).toHaveProperty('host')
80+
expect(result).toHaveProperty('port')
6781
})
6882

69-
it('returns static url when set in config and no env url', () => {
70-
const staticRedis = JSON.stringify({ url: 'redis://config-host:6379/0', host: '127.0.0.1' })
71-
const expr = generateRedisConnectionExpr(staticRedis)
72-
const result = evalExpr(expr)
83+
it('includes url from static config when no env url is set', () => {
84+
const result = resolveRedisConnection({ url: 'redis://config-host:6379/0', host: '127.0.0.1' })
7385

74-
expect(result).toBe('redis://config-host:6379/0')
86+
expect(result).toMatchObject({ url: 'redis://config-host:6379/0' })
87+
expect(result).toHaveProperty('host', '127.0.0.1')
7588
})
7689

7790
it('env vars override individual static fields', () => {
@@ -81,9 +94,7 @@ describe('generateRedisConnectionExpr', () => {
8194
process.env.NUXT_REDIS_USERNAME = 'admin'
8295
process.env.NUXT_REDIS_DB = '5'
8396

84-
const staticRedis = JSON.stringify({ host: '10.0.0.1', port: 6380, password: 'build-pw', db: 2 })
85-
const expr = generateRedisConnectionExpr(staticRedis)
86-
const result = evalExpr(expr) as Record<string, unknown>
97+
const result = resolveRedisConnection({ host: '10.0.0.1', port: 6380, password: 'build-pw', db: 2 })
8798

8899
expect(result).toEqual({
89100
host: '192.168.1.100',
@@ -98,11 +109,8 @@ describe('generateRedisConnectionExpr', () => {
98109

99110
it('env vars partially override static fields', () => {
100111
process.env.NUXT_REDIS_HOST = 'new-host'
101-
// port, password etc. not set in env
102112

103-
const staticRedis = JSON.stringify({ host: '10.0.0.1', port: 6380, password: 'build-pw', db: 2 })
104-
const expr = generateRedisConnectionExpr(staticRedis)
105-
const result = evalExpr(expr) as Record<string, unknown>
113+
const result = resolveRedisConnection({ host: '10.0.0.1', port: 6380, password: 'build-pw', db: 2 })
106114

107115
expect(result).toMatchObject({
108116
host: 'new-host',
@@ -112,22 +120,18 @@ describe('generateRedisConnectionExpr', () => {
112120
})
113121
})
114122

115-
it('NUXT_REDIS_URL env takes priority over static url in config', () => {
123+
it('NUXT_REDIS_URL env takes priority over static url', () => {
116124
process.env.NUXT_REDIS_URL = 'redis://env-host:6379'
117-
const staticRedis = JSON.stringify({ url: 'redis://config-host:6379' })
118-
const expr = generateRedisConnectionExpr(staticRedis)
119-
const result = evalExpr(expr)
125+
const result = resolveRedisConnection({ url: 'redis://config-host:6379' })
120126

121-
expect(result).toBe('redis://env-host:6379')
127+
expect(result).toMatchObject({ url: 'redis://env-host:6379' })
122128
})
123129

124130
it('handles lazyConnect and connectTimeout from env', () => {
125131
process.env.NUXT_REDIS_LAZY_CONNECT = 'true'
126132
process.env.NUXT_REDIS_CONNECT_TIMEOUT = '5000'
127133

128-
const staticRedis = JSON.stringify({})
129-
const expr = generateRedisConnectionExpr(staticRedis)
130-
const result = evalExpr(expr) as Record<string, unknown>
134+
const result = resolveRedisConnection({})
131135

132136
expect(result).toMatchObject({
133137
lazyConnect: true,
@@ -136,9 +140,7 @@ describe('generateRedisConnectionExpr', () => {
136140
})
137141

138142
it('falls back to defaults when both static and env are empty', () => {
139-
const staticRedis = JSON.stringify({})
140-
const expr = generateRedisConnectionExpr(staticRedis)
141-
const result = evalExpr(expr) as Record<string, unknown>
143+
const result = resolveRedisConnection({})
142144

143145
expect(result).toEqual({
144146
host: '127.0.0.1',
@@ -151,11 +153,27 @@ describe('generateRedisConnectionExpr', () => {
151153
})
152154
})
153155

154-
it('generated expression contains process.env references', () => {
155-
const expr = generateRedisConnectionExpr('{}')
156-
expect(expr).toContain('process.env.NUXT_REDIS_URL')
157-
expect(expr).toContain('process.env.NUXT_REDIS_HOST')
158-
expect(expr).toContain('process.env.NUXT_REDIS_PORT')
159-
expect(expr).toContain('process.env.NUXT_REDIS_PASSWORD')
156+
it('does not include url key when no url is provided', () => {
157+
const result = resolveRedisConnection({ host: '10.0.0.1' })
158+
159+
expect(result).not.toHaveProperty('url')
160+
})
161+
162+
it('preserves all options alongside url for setConnection', () => {
163+
process.env.NUXT_REDIS_URL = 'redis://prod:6379'
164+
process.env.NUXT_REDIS_PASSWORD = 'prod-pw'
165+
166+
const result = resolveRedisConnection({ host: '127.0.0.1', connectTimeout: 10000 })
167+
168+
expect(result).toEqual({
169+
url: 'redis://prod:6379',
170+
host: '127.0.0.1',
171+
port: 6379,
172+
password: 'prod-pw',
173+
username: undefined,
174+
db: 0,
175+
lazyConnect: undefined,
176+
connectTimeout: 10000,
177+
})
160178
})
161179
})

spec/utils/generate-workers-entry-content.spec.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
import { describe, it, expect } from 'vitest'
22
import { generateWorkersEntryContent } from '../../src/utils/generate-workers-entry-content'
3+
import { generateRedisConnectionExpr } from '../../src/utils/generate-redis-connection-expr'
34

45
describe('generate-workers-entry-content', () => {
5-
it('matches snapshot for single worker and undefined redis', () => {
6+
it('matches snapshot for single worker and empty redis config', () => {
7+
const redisExpr = generateRedisConnectionExpr('{}')
68
const content = generateWorkersEntryContent(
79
['/path/to/worker.mjs'],
8-
'undefined',
10+
redisExpr,
911
)
1012
expect(content).toMatchSnapshot()
1113
})
1214

13-
it('matches snapshot for multiple workers and redis url', () => {
15+
it('matches snapshot for multiple workers and redis config', () => {
16+
const redisExpr = generateRedisConnectionExpr(JSON.stringify({ host: 'localhost', port: 6379 }))
1417
const content = generateWorkersEntryContent(
1518
['/app/server/workers/basic.ts', '/app/server/workers/hello.ts'],
16-
'new (await import("ioredis")).default("redis://localhost:6379")',
19+
redisExpr,
1720
)
1821
expect(content).toMatchSnapshot()
1922
})
2023

24+
it('generates entry that imports resolveRedisConnection', () => {
25+
const redisExpr = generateRedisConnectionExpr('{}')
26+
const content = generateWorkersEntryContent(['/path/to/worker.mjs'], redisExpr)
27+
expect(content).toContain('import { resolveRedisConnection } from \'#resolve-redis\'')
28+
})
29+
2130
it('generates entry that parses --workers flag from process.argv', () => {
31+
const redisExpr = generateRedisConnectionExpr('{}')
2232
const content = generateWorkersEntryContent(
2333
['/path/to/worker.mjs'],
24-
'undefined',
34+
redisExpr,
2535
)
2636
expect(content).toContain('process.argv.find(a => typeof a === \'string\' && a.startsWith(\'--workers=\'))')
2737
expect(content).toContain('workersArg.split(\'=\')[1].split(\',\').map(s => s.trim()).filter(Boolean)')
@@ -30,36 +40,40 @@ describe('generate-workers-entry-content', () => {
3040
})
3141

3242
it('generates entry that filters workers by name when --workers is set', () => {
43+
const redisExpr = generateRedisConnectionExpr('{}')
3344
const content = generateWorkersEntryContent(
3445
['/path/to/worker.mjs'],
35-
'undefined',
46+
redisExpr,
3647
)
3748
expect(content).toContain('selectedWorkers.includes(w.name)')
3849
expect(content).toContain('api.workers.filter(w => w && selectedWorkers.includes(w.name))')
3950
})
4051

4152
it('generates entry that runs all workers when --workers is not set', () => {
53+
const redisExpr = generateRedisConnectionExpr('{}')
4254
const content = generateWorkersEntryContent(
4355
['/path/to/worker.mjs'],
44-
'undefined',
56+
redisExpr,
4557
)
4658
expect(content).toContain('(Array.isArray(api.workers) ? api.workers : [])')
4759
})
4860

4961
it('generates entry that returns stop closing only workersToRun', () => {
62+
const redisExpr = generateRedisConnectionExpr('{}')
5063
const content = generateWorkersEntryContent(
5164
['/path/to/worker.mjs'],
52-
'undefined',
65+
redisExpr,
5366
)
5467
expect(content).toContain('workersToRun.map(w => w.close())')
5568
expect(content).toContain('closeRunningWorkers')
5669
expect(content).toContain('return { stop: closeRunningWorkers, workers: workersToRun }')
5770
})
5871

5972
it('generates entry that warns and exits when no workers match --workers filter', () => {
73+
const redisExpr = generateRedisConnectionExpr('{}')
6074
const content = generateWorkersEntryContent(
6175
['/path/to/worker.mjs'],
62-
'undefined',
76+
redisExpr,
6377
)
6478
expect(content).toContain('selectedWorkers && workersToRun.length === 0')
6579
expect(content).toContain('logger.warn')

src/module.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { Plugin } from 'rollup'
55
import { relative } from 'node:path'
66
import scanFolder from './utils/scan-folder'
77
import { generateWorkersEntryContent } from './utils/generate-workers-entry-content'
8-
import { generateRedisConnectionExpr } from './utils/generate-redis-connection-expr'
8+
import { generateRedisConnectionExpr, getRedisConnectionImport } from './utils/generate-redis-connection-expr'
99

1010
// Module options TypeScript interface definition
1111
type ModuleRedisOptions = BullRedisOptions & { url?: string }
@@ -47,9 +47,12 @@ export default defineNuxtModule<ModuleOptions>({
4747
const staticRedis = JSON.stringify(_options.redis ?? {})
4848
const redisConnectionExpr = generateRedisConnectionExpr(staticRedis)
4949

50+
const redisImport = getRedisConnectionImport('#resolve-redis')
51+
5052
const nitroPlugin = `
5153
import { defineNitroPlugin } from '#imports'
5254
import { $workers } from '#processor-utils'
55+
${redisImport}
5356
5457
export default defineNitroPlugin(() => {
5558
$workers().setConnection(${redisConnectionExpr})
@@ -69,6 +72,7 @@ export default defineNuxtModule<ModuleOptions>({
6972
nuxt.options.alias['nuxt-processor'] = resolve('./runtime/server/handlers')
7073
nuxt.options.alias['#processor'] = resolve('./runtime/server/handlers')
7174
nuxt.options.alias['#processor-utils'] = resolve('./runtime/server/utils/workers')
75+
nuxt.options.alias['#resolve-redis'] = resolve('./runtime/server/utils/resolve-redis-connection')
7276
// Allow swapping BullMQ implementation allowing for bullmq pro (default to 'bullmq')
7377
if (!nuxt.options.alias['#bullmq']) {
7478
nuxt.options.alias['#bullmq'] = 'bullmq'
@@ -92,6 +96,10 @@ declare module '#processor-utils' {
9296
export { $workers } from '${resolve('./runtime/server/utils/workers')}'
9397
}
9498
99+
declare module '#resolve-redis' {
100+
export { resolveRedisConnection } from '${resolve('./runtime/server/utils/resolve-redis-connection')}'
101+
}
102+
95103
declare module '#bullmq' {
96104
export * from 'bullmq'
97105
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Resolves Redis connection options at runtime.
3+
*
4+
* Environment variables take precedence over the static config snapshot
5+
* captured at build time. Returns an object with all options preserved,
6+
* including `url` when applicable, so `setConnection` can pass them
7+
* all to the IORedis constructor.
8+
*/
9+
export function resolveRedisConnection(
10+
staticConfig: Record<string, unknown>,
11+
): Record<string, unknown> {
12+
const url = process.env.NUXT_REDIS_URL ?? staticConfig.url
13+
return {
14+
...(url ? { url } : {}),
15+
host: process.env.NUXT_REDIS_HOST ?? staticConfig.host ?? '127.0.0.1',
16+
port: Number(process.env.NUXT_REDIS_PORT ?? staticConfig.port ?? 6379),
17+
password: process.env.NUXT_REDIS_PASSWORD ?? staticConfig.password ?? '',
18+
username: process.env.NUXT_REDIS_USERNAME ?? staticConfig.username ?? undefined,
19+
db: Number(process.env.NUXT_REDIS_DB ?? staticConfig.db ?? 0),
20+
lazyConnect: process.env.NUXT_REDIS_LAZY_CONNECT
21+
? process.env.NUXT_REDIS_LAZY_CONNECT === 'true'
22+
: (staticConfig.lazyConnect as boolean | undefined),
23+
connectTimeout: process.env.NUXT_REDIS_CONNECT_TIMEOUT
24+
? Number(process.env.NUXT_REDIS_CONNECT_TIMEOUT)
25+
: (staticConfig.connectTimeout as number | undefined),
26+
}
27+
}

0 commit comments

Comments
 (0)