queasy 0.2.0 → 0.3.1
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/.github/workflows/check.yml +3 -0
- package/.github/workflows/publish.yml +3 -0
- package/CLAUDE.md +5 -4
- package/Readme.md +9 -4
- package/biome.json +5 -1
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +199 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/{src → dist}/constants.js +2 -10
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/{src → dist}/errors.js +1 -13
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +19 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +67 -0
- package/dist/manager.js.map +1 -0
- package/dist/pool.d.ts +29 -0
- package/dist/pool.d.ts.map +1 -0
- package/{src → dist}/pool.js +23 -82
- package/dist/pool.js.map +1 -0
- package/dist/queasy.lua +390 -0
- package/dist/queue.d.ts +22 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +81 -0
- package/dist/queue.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +42 -0
- package/dist/worker.js.map +1 -0
- package/docker-compose.yml +0 -2
- package/fuzztest/Readme.md +185 -0
- package/fuzztest/fuzz.ts +356 -0
- package/fuzztest/handlers/cascade-a.ts +90 -0
- package/fuzztest/handlers/cascade-b.ts +71 -0
- package/fuzztest/handlers/fail-handler.ts +47 -0
- package/fuzztest/handlers/periodic.ts +89 -0
- package/fuzztest/process.ts +100 -0
- package/fuzztest/shared/chaos.ts +29 -0
- package/fuzztest/shared/stream.ts +40 -0
- package/package.json +8 -7
- package/plans/redis-options.md +279 -0
- package/src/client.ts +246 -0
- package/src/constants.ts +33 -0
- package/src/errors.ts +13 -0
- package/src/index.ts +2 -0
- package/src/manager.ts +78 -0
- package/src/pool.ts +129 -0
- package/src/queasy.lua +2 -3
- package/src/queue.ts +108 -0
- package/src/types.ts +16 -0
- package/src/{utils.js → utils.ts} +3 -20
- package/src/{worker.js → worker.ts} +5 -12
- package/test/{client.test.js → client.test.ts} +6 -7
- package/test/{errors.test.js → errors.test.ts} +1 -1
- package/test/fixtures/always-fail-handler.ts +5 -0
- package/test/fixtures/data-logger-handler.ts +11 -0
- package/test/fixtures/failure-handler.ts +6 -0
- package/test/fixtures/permanent-error-handler.ts +6 -0
- package/test/fixtures/slow-handler.ts +6 -0
- package/test/fixtures/success-handler.js +0 -5
- package/test/fixtures/success-handler.ts +6 -0
- package/test/fixtures/with-failure-handler.ts +5 -0
- package/test/{guards.test.js → guards.test.ts} +21 -34
- package/test/{manager.test.js → manager.test.ts} +26 -34
- package/test/{pool.test.js → pool.test.ts} +14 -16
- package/test/{queue.test.js → queue.test.ts} +21 -21
- package/test/{redis-functions.test.js → redis-functions.test.ts} +14 -20
- package/test/{utils.test.js → utils.test.ts} +1 -1
- package/tsconfig.json +20 -0
- package/jsconfig.json +0 -17
- package/src/client.js +0 -258
- package/src/index.js +0 -2
- package/src/manager.js +0 -94
- package/src/queue.js +0 -154
- package/test/fixtures/always-fail-handler.js +0 -8
- package/test/fixtures/data-logger-handler.js +0 -19
- package/test/fixtures/failure-handler.js +0 -9
- package/test/fixtures/permanent-error-handler.js +0 -10
- package/test/fixtures/slow-handler.js +0 -9
- package/test/fixtures/with-failure-handler.js +0 -8
- /package/test/fixtures/{no-handle-handler.js → no-handle-handler.ts} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzz test child process entry point.
|
|
3
|
+
*
|
|
4
|
+
* Each child process creates one queasy Client (with worker threads) and
|
|
5
|
+
* calls listen() on all three queues. Handlers run inside queasy's internal
|
|
6
|
+
* worker threads and communicate crash signals via BroadcastChannel.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { BroadcastChannel } from 'node:worker_threads';
|
|
12
|
+
import { Client } from '../src/index.ts';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
// ── Configuration ─────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const WORKER_THREADS = 2; // worker threads per child process
|
|
19
|
+
|
|
20
|
+
const BASE_OPTIONS = {
|
|
21
|
+
maxRetries: 3,
|
|
22
|
+
maxStalls: 2,
|
|
23
|
+
minBackoff: 200,
|
|
24
|
+
maxBackoff: 2000,
|
|
25
|
+
timeout: 3000,
|
|
26
|
+
size: 10,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const FAIL_RETRY_OPTIONS = {
|
|
30
|
+
maxRetries: 5,
|
|
31
|
+
minBackoff: 200,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ── Handler paths ──────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const failHandlerPath = join(__dirname, 'handlers', 'fail-handler.ts');
|
|
37
|
+
const periodicPath = join(__dirname, 'handlers', 'periodic.ts');
|
|
38
|
+
const cascadeAPath = join(__dirname, 'handlers', 'cascade-a.ts');
|
|
39
|
+
const cascadeBPath = join(__dirname, 'handlers', 'cascade-b.ts');
|
|
40
|
+
|
|
41
|
+
// ── Crash signal ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
// Handlers running in worker threads post to this channel to trigger a crash.
|
|
44
|
+
const crashChannel = new BroadcastChannel('fuzz-crash');
|
|
45
|
+
crashChannel.onmessage = () => {
|
|
46
|
+
process.exit(1);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ── Redis + queasy client ──────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
const client = await new Promise<Client>((resolve) => new Client({}, WORKER_THREADS, resolve));
|
|
52
|
+
|
|
53
|
+
client.on('disconnected', (reason: string) => {
|
|
54
|
+
console.error(`[process ${process.pid}] Client disconnected: ${reason}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Forward client lifecycle events to the orchestrator via IPC.
|
|
59
|
+
// The orchestrator uses these to authoritatively track active jobs.
|
|
60
|
+
client.on('dequeue', (queue: string, job: { id: string; runAt: number }) => {
|
|
61
|
+
process.send!({ type: 'dequeue', queue, jobId: job.id, runAt: job.runAt });
|
|
62
|
+
});
|
|
63
|
+
client.on('finish', (queue: string, jobId: string) => {
|
|
64
|
+
process.send!({ type: 'finish', queue, jobId });
|
|
65
|
+
});
|
|
66
|
+
client.on('retry', (queue: string, jobId: string) => {
|
|
67
|
+
process.send!({ type: 'retry', queue, jobId });
|
|
68
|
+
});
|
|
69
|
+
client.on('fail', (queue: string, jobId: string) => {
|
|
70
|
+
process.send!({ type: 'fail', queue, jobId });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ── Queue setup ────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
const periodicQueue = client.queue('{fuzz}:periodic', true);
|
|
76
|
+
const cascadeAQueue = client.queue('{fuzz}:cascade-a', true);
|
|
77
|
+
const cascadeBQueue = client.queue('{fuzz}:cascade-b', true);
|
|
78
|
+
|
|
79
|
+
await Promise.all([
|
|
80
|
+
periodicQueue.listen(periodicPath, {
|
|
81
|
+
...BASE_OPTIONS,
|
|
82
|
+
priority: 300,
|
|
83
|
+
failHandler: failHandlerPath,
|
|
84
|
+
failRetryOptions: FAIL_RETRY_OPTIONS,
|
|
85
|
+
}),
|
|
86
|
+
cascadeAQueue.listen(cascadeAPath, {
|
|
87
|
+
...BASE_OPTIONS,
|
|
88
|
+
priority: 200,
|
|
89
|
+
failHandler: failHandlerPath,
|
|
90
|
+
failRetryOptions: FAIL_RETRY_OPTIONS,
|
|
91
|
+
}),
|
|
92
|
+
cascadeBQueue.listen(cascadeBPath, {
|
|
93
|
+
...BASE_OPTIONS,
|
|
94
|
+
priority: 100,
|
|
95
|
+
failHandler: failHandlerPath,
|
|
96
|
+
failRetryOptions: FAIL_RETRY_OPTIONS,
|
|
97
|
+
}),
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
console.log(`[process ${process.pid}] Ready`);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weighted random chaos behavior picker.
|
|
3
|
+
* All handlers apply the same set of chaos behaviors.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type ChaosAction = 'normal' | 'retriable' | 'permanent' | 'stall' | 'spin' | 'crash';
|
|
7
|
+
|
|
8
|
+
const BEHAVIORS: { action: ChaosAction; weight: number }[] = [
|
|
9
|
+
{ action: 'normal', weight: 65 },
|
|
10
|
+
{ action: 'retriable', weight: 15 },
|
|
11
|
+
{ action: 'permanent', weight: 5 },
|
|
12
|
+
{ action: 'stall', weight: 10 },
|
|
13
|
+
{ action: 'spin', weight: 3 },
|
|
14
|
+
{ action: 'crash', weight: 2 },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const TOTAL_WEIGHT = BEHAVIORS.reduce((sum, b) => sum + b.weight, 0);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pick a chaos action based on weighted probability.
|
|
21
|
+
*/
|
|
22
|
+
export function pickChaos(): ChaosAction {
|
|
23
|
+
let r = Math.random() * TOTAL_WEIGHT;
|
|
24
|
+
for (const { action, weight } of BEHAVIORS) {
|
|
25
|
+
r -= weight;
|
|
26
|
+
if (r <= 0) return action;
|
|
27
|
+
}
|
|
28
|
+
return 'normal';
|
|
29
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis stream helpers for the fuzz test event log.
|
|
3
|
+
* All events are written to the 'fuzz:events' stream.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RedisClientType } from 'redis';
|
|
7
|
+
|
|
8
|
+
export const STREAM_KEY = 'fuzz:events';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Emit a structured event to the fuzz:events stream.
|
|
12
|
+
*/
|
|
13
|
+
export async function emitEvent(redis: RedisClientType, fields: Record<string, string>): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
await redis.xAdd(STREAM_KEY, '*', fields);
|
|
16
|
+
} catch {
|
|
17
|
+
// Never let event emission crash a handler
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Async generator that yields parsed event objects from the fuzz:events stream.
|
|
23
|
+
* Blocks for up to 1 second waiting for new events, then yields control back.
|
|
24
|
+
*/
|
|
25
|
+
export async function* readEvents(
|
|
26
|
+
redis: RedisClientType,
|
|
27
|
+
lastId = '0'
|
|
28
|
+
): AsyncGenerator<Record<string, string>> {
|
|
29
|
+
let id = lastId;
|
|
30
|
+
while (true) {
|
|
31
|
+
const results = await redis.xRead({ key: STREAM_KEY, id }, { BLOCK: 1000, COUNT: 100 });
|
|
32
|
+
if (!results) continue;
|
|
33
|
+
for (const { messages } of results) {
|
|
34
|
+
for (const { id: msgId, message } of messages) {
|
|
35
|
+
id = msgId;
|
|
36
|
+
yield message;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "queasy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A simple Redis-backed queue library for Node.js",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json && cp src/queasy.lua dist/queasy.lua",
|
|
8
10
|
"test": "node --test",
|
|
9
|
-
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=96 --test-coverage-include '
|
|
11
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=96 --test-coverage-include 'dist/**/*.js' 'test/**/*.test.js'",
|
|
10
12
|
"test:watch": "node --test --watch",
|
|
13
|
+
"test:fuzz": "node fuzztest/fuzz.ts",
|
|
11
14
|
"lint": "biome check .",
|
|
12
15
|
"lint:fix": "biome check --write .",
|
|
13
|
-
"
|
|
14
|
-
"typecheck": "tsc --noEmit -p jsconfig.json",
|
|
16
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
15
17
|
"docker:up": "docker compose up -d",
|
|
16
18
|
"docker:down": "docker compose down",
|
|
17
19
|
"docker:logs": "docker compose logs -f"
|
|
@@ -29,13 +31,12 @@
|
|
|
29
31
|
],
|
|
30
32
|
"author": "",
|
|
31
33
|
"license": "ISC",
|
|
32
|
-
"
|
|
34
|
+
"dependencies": {
|
|
33
35
|
"redis": "^5.10.0"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@biomejs/biome": "^2.3.14",
|
|
37
39
|
"@types/node": "^25.2.0",
|
|
38
|
-
"redis": "^5.10.0",
|
|
39
40
|
"typescript": "^5.9.3"
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Plan: Client constructs its own Redis connection
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Change the `Client` constructor so that instead of accepting a pre-built node-redis
|
|
6
|
+
connection as its first argument, it accepts an options object and constructs the
|
|
7
|
+
connection itself (calling either `createClient` or `createCluster` from the `redis`
|
|
8
|
+
package). This gives Queasy full control over connection lifecycle.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Options object design
|
|
13
|
+
|
|
14
|
+
The first argument changes from `RedisClient` to a `RedisOptions` object:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { RedisClientOptions, RedisClusterOptions } from 'redis';
|
|
18
|
+
|
|
19
|
+
type SingleNodeOptions = Pick<RedisClientOptions, 'url' | 'socket' | 'username' | 'password' | 'database'>;
|
|
20
|
+
|
|
21
|
+
type RedisOptions =
|
|
22
|
+
| SingleNodeOptions
|
|
23
|
+
| {
|
|
24
|
+
rootNodes: SingleNodeOptions[];
|
|
25
|
+
defaults?: Partial<SingleNodeOptions>;
|
|
26
|
+
nodeAddressMap?: RedisClusterOptions['nodeAddressMap'];
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`RedisClientOptions` and `RedisClusterOptions` are both exported from the top-level
|
|
31
|
+
`redis` package and can be imported directly. `RedisClusterClientOptions` (the type
|
|
32
|
+
node-redis uses for `rootNodes` elements and `defaults`) is **not** exported at the
|
|
33
|
+
top level, so we define the cluster form inline, reusing `SingleNodeOptions` to
|
|
34
|
+
constrain both `rootNodes` elements and `defaults` to the same permitted fields as
|
|
35
|
+
the single-node case. `nodeAddressMap` is taken via an index type from
|
|
36
|
+
`RedisClusterOptions` directly to avoid depending on the unexported `NodeAddressMap`
|
|
37
|
+
type.
|
|
38
|
+
|
|
39
|
+
The dispatch rule: **if `options.rootNodes` exists, use `createCluster`; otherwise
|
|
40
|
+
use `createClient`.**
|
|
41
|
+
|
|
42
|
+
- If `options.rootNodes` is present: call `createCluster(options)`, passing the
|
|
43
|
+
object through directly. The caller may also provide `defaults` (shared auth/TLS
|
|
44
|
+
for all nodes) and `nodeAddressMap` — all standard `createCluster` options,
|
|
45
|
+
named consistently with the node-redis API.
|
|
46
|
+
- Otherwise: call `createClient(options)`, passing the object through directly.
|
|
47
|
+
- The constructed connection is stored as `this.redis`.
|
|
48
|
+
- `connect()` is called internally in the constructor.
|
|
49
|
+
- `close()` calls `this.redis.destroy()` to disconnect.
|
|
50
|
+
|
|
51
|
+
### Constructor signature
|
|
52
|
+
|
|
53
|
+
All three arguments default so that `new Client()` works (connects to `localhost:6379`):
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
constructor(options = {}, workerCount = os.cpus().length, callback = undefined)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. Changes to `src/client.js`
|
|
62
|
+
|
|
63
|
+
1. Add imports:
|
|
64
|
+
```js
|
|
65
|
+
import { createClient, createCluster } from 'redis';
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. Add a helper at module level:
|
|
69
|
+
```js
|
|
70
|
+
function buildRedisConnection(options) {
|
|
71
|
+
if (options.rootNodes) {
|
|
72
|
+
return createCluster(options);
|
|
73
|
+
}
|
|
74
|
+
return createClient(options);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
3. Update constructor:
|
|
79
|
+
- Parameter `redis` → `options` (with JSDoc type `RedisOptions`)
|
|
80
|
+
- Replace `this.redis = redis;` with:
|
|
81
|
+
```js
|
|
82
|
+
this.redis = buildRedisConnection(options);
|
|
83
|
+
```
|
|
84
|
+
- Replace the bare `installLuaFunctions(this.redis).then(...)` call with:
|
|
85
|
+
```js
|
|
86
|
+
this.redis.connect()
|
|
87
|
+
.then(() => installLuaFunctions(this.redis))
|
|
88
|
+
.then((disconnect) => { ... })
|
|
89
|
+
.catch((err) => {
|
|
90
|
+
this.disconnected = true;
|
|
91
|
+
this.emit('disconnected', err.message);
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
- Apply defaults to all parameters:
|
|
95
|
+
```js
|
|
96
|
+
constructor(options = {}, workerCount = os.cpus().length, callback)
|
|
97
|
+
```
|
|
98
|
+
Add `import os from 'node:os';` (check if already imported; if so, reuse).
|
|
99
|
+
|
|
100
|
+
4. Update `close()`:
|
|
101
|
+
- After `this.disconnected = true;`, add:
|
|
102
|
+
```js
|
|
103
|
+
await this.redis.destroy().catch(() => {});
|
|
104
|
+
```
|
|
105
|
+
`destroy()` is the documented disconnect method for both `RedisClientType`
|
|
106
|
+
and `RedisClusterType` in node-redis v5.
|
|
107
|
+
|
|
108
|
+
5. Update JSDoc typedef:
|
|
109
|
+
```js
|
|
110
|
+
/** @typedef {import('./types').RedisOptions} RedisOptions */
|
|
111
|
+
```
|
|
112
|
+
Point to `types.ts` rather than duplicating the type inline in JS, since the
|
|
113
|
+
full `Pick<>` construction is cleaner to express in TypeScript. Remove the old
|
|
114
|
+
`RedisClient` typedef.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 3. Changes to `src/types.ts`
|
|
119
|
+
|
|
120
|
+
Import the relevant node-redis types and define `RedisOptions` using `Pick<>` and
|
|
121
|
+
an inline cluster shape:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import type { RedisClientOptions, RedisClusterOptions } from 'redis';
|
|
125
|
+
|
|
126
|
+
type SingleNodeOptions = Pick<RedisClientOptions, 'url' | 'socket' | 'username' | 'password' | 'database'>;
|
|
127
|
+
|
|
128
|
+
export type RedisOptions =
|
|
129
|
+
| SingleNodeOptions
|
|
130
|
+
| {
|
|
131
|
+
rootNodes: SingleNodeOptions[];
|
|
132
|
+
defaults?: Partial<SingleNodeOptions>;
|
|
133
|
+
nodeAddressMap?: RedisClusterOptions['nodeAddressMap'];
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`SingleNodeOptions` is not exported — it's an internal building block. Only
|
|
138
|
+
`RedisOptions` is exported. Update the `Client` constructor signature to accept
|
|
139
|
+
`RedisOptions` instead of `RedisClientType`.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 4. Changes to `package.json`
|
|
144
|
+
|
|
145
|
+
- Move `redis` from `peerDependencies` + `devDependencies` to **`dependencies`**
|
|
146
|
+
(queasy now owns the connection; callers no longer need to install it themselves).
|
|
147
|
+
- Remove the `peerDependencies` section entirely.
|
|
148
|
+
|
|
149
|
+
Before:
|
|
150
|
+
```json
|
|
151
|
+
"peerDependencies": { "redis": "^5.10.0" },
|
|
152
|
+
"devDependencies": { "redis": "^5.10.0", ... }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
After:
|
|
156
|
+
```json
|
|
157
|
+
"dependencies": { "redis": "^5.10.0" },
|
|
158
|
+
"devDependencies": { ... }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 5. Changes to `test/queue.test.js`
|
|
164
|
+
|
|
165
|
+
Currently each `beforeEach` creates and connects a Redis client, passes it to
|
|
166
|
+
`Client`, and each `afterEach` calls `redis.quit()`.
|
|
167
|
+
|
|
168
|
+
Changes:
|
|
169
|
+
- Remove the `createClient` import and the `redis` variable.
|
|
170
|
+
- Change `new Client(redis, 1)` → `new Client({}, 1)`.
|
|
171
|
+
- Remove `await redis.quit()` — `client.close()` now handles disconnection.
|
|
172
|
+
- Any direct Redis inspection calls (`redis.zScore`, `redis.hGetAll`, `redis.keys`,
|
|
173
|
+
`redis.del`) still need a Redis connection. Add a dedicated `redisInspect` client
|
|
174
|
+
used only for test-side assertions:
|
|
175
|
+
```js
|
|
176
|
+
// beforeEach
|
|
177
|
+
redisInspect = createClient();
|
|
178
|
+
await redisInspect.connect();
|
|
179
|
+
// afterEach
|
|
180
|
+
await redisInspect.quit();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 6. Changes to `test/client.test.js`
|
|
186
|
+
|
|
187
|
+
Same pattern as `queue.test.js`:
|
|
188
|
+
- Remove `createClient` import and `redis` variable.
|
|
189
|
+
- `new Client(redis, 1)` → `new Client({}, 1)`.
|
|
190
|
+
- Direct Redis calls (`redis.zScore`, `redis.keys`, `redis.del`) move to a
|
|
191
|
+
separate `redisInspect` client.
|
|
192
|
+
- Remove `await redis.quit()` from `afterEach`; `client.close()` handles it.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 7. Changes to `test/redis-functions.test.js`
|
|
197
|
+
|
|
198
|
+
This test file does **not** use the `Client` class — it builds its own `redis`
|
|
199
|
+
connection to test Lua functions directly. **No changes needed.**
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 8. Changes to `fuzztest/process.js`
|
|
204
|
+
|
|
205
|
+
Currently:
|
|
206
|
+
```js
|
|
207
|
+
import { createClient } from 'redis';
|
|
208
|
+
const redis = createClient();
|
|
209
|
+
await redis.connect();
|
|
210
|
+
const client = await new Promise((resolve) => new Client(redis, WORKER_THREADS, resolve));
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Change to:
|
|
214
|
+
```js
|
|
215
|
+
const client = await new Promise((resolve) => new Client({}, WORKER_THREADS, resolve));
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Remove the `import { createClient } from 'redis'` line and the three lines that
|
|
219
|
+
create and connect `redis`.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 9. Changes to `Readme.md`
|
|
224
|
+
|
|
225
|
+
Update the `client()` API section:
|
|
226
|
+
|
|
227
|
+
**Before:**
|
|
228
|
+
```
|
|
229
|
+
### `client(redisConnection, workerCount)`
|
|
230
|
+
Returns a Queasy client.
|
|
231
|
+
- `redisConnection`: a node-redis connection object.
|
|
232
|
+
- `workerCount`: number; Size of the worker pool. ...
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**After:**
|
|
236
|
+
```
|
|
237
|
+
### `new Client(options, workerCount)`
|
|
238
|
+
Returns a Queasy client. Queasy creates and manages its own Redis connection internally.
|
|
239
|
+
- `options`: connection options. Two forms are accepted:
|
|
240
|
+
- **Single node** (plain object): passed to node-redis `createClient`. Accepts
|
|
241
|
+
`url`, `socket`, `username`, `password`, and `database`. Defaults to `{}`
|
|
242
|
+
(connects to `localhost:6379`).
|
|
243
|
+
- **Cluster** (object with `rootNodes`): passed to node-redis `createCluster`.
|
|
244
|
+
Accepts `rootNodes` (required — array of per-node connection options, at least
|
|
245
|
+
three recommended), `defaults` (options shared across all nodes, e.g. auth and
|
|
246
|
+
TLS), and `nodeAddressMap` (for address translation in NAT environments).
|
|
247
|
+
- `workerCount`: number; size of the worker pool. Defaults to number of CPUs.
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Also update the terminology section to reflect that the client manages its own
|
|
251
|
+
connection rather than accepting one from the caller.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 10. Changes to `CLAUDE.md`
|
|
256
|
+
|
|
257
|
+
In the Architecture section, update the JS layer description:
|
|
258
|
+
- "On construction, it calls `createClient` or `createCluster` (based on whether
|
|
259
|
+
`options.rootNodes` is present), connects, then uploads the Lua script via
|
|
260
|
+
`FUNCTION LOAD REPLACE`."
|
|
261
|
+
- Remove the mention of `WeakSet (initializedClients)` tracking which Redis clients
|
|
262
|
+
have had functions loaded (no longer relevant since the connection is internal and
|
|
263
|
+
only created once per `Client` instance).
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Summary of file changes
|
|
268
|
+
|
|
269
|
+
| File | Change |
|
|
270
|
+
|---|---|
|
|
271
|
+
| `src/client.js` | Accept `RedisOptions`; call `createClient`/`createCluster`; connect in constructor; `destroy()` in `close()`; emit `'disconnected'` on connect error; default all args |
|
|
272
|
+
| `src/types.ts` | Add `RedisOptions` type using `Pick<RedisClientOptions, ...> \| Pick<RedisClusterOptions, ...>`; remove `RedisClientType` reference |
|
|
273
|
+
| `package.json` | Move `redis` from `peerDependencies`+`devDependencies` to `dependencies` |
|
|
274
|
+
| `test/queue.test.js` | Remove external `redis` client; pass `{}` to `Client`; add `redisInspect` for assertions |
|
|
275
|
+
| `test/client.test.js` | Same as above |
|
|
276
|
+
| `test/redis-functions.test.js` | No changes |
|
|
277
|
+
| `fuzztest/process.js` | Remove `createClient`/`redis`; pass `{}` to `Client` |
|
|
278
|
+
| `Readme.md` | Update `client()` API docs |
|
|
279
|
+
| `CLAUDE.md` | Update architecture description |
|