@jgamaraalv/ts-dev-kit 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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# ioredis Advanced Patterns Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Streams (XADD/XREAD)](#streams)
|
|
6
|
+
- [Auto-pipelining](#auto-pipelining)
|
|
7
|
+
- [Lua scripting details](#lua-scripting-details)
|
|
8
|
+
- [Argument and reply transformers](#argument-and-reply-transformers)
|
|
9
|
+
- [Binary data](#binary-data)
|
|
10
|
+
- [Key prefixing](#key-prefixing)
|
|
11
|
+
- [Monitor mode](#monitor-mode)
|
|
12
|
+
- [Error handling](#error-handling)
|
|
13
|
+
- [Debugging](#debugging)
|
|
14
|
+
|
|
15
|
+
## Streams
|
|
16
|
+
|
|
17
|
+
Redis Streams (v5+) for event streaming and log-like data:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// Producer:
|
|
21
|
+
await redis.xadd("mystream", "*", "field1", "value1", "field2", "value2");
|
|
22
|
+
|
|
23
|
+
// Consumer (blocking read):
|
|
24
|
+
async function listenForMessage(lastId = "$") {
|
|
25
|
+
const results = await redis.xread("BLOCK", 0, "STREAMS", "mystream", lastId);
|
|
26
|
+
const [key, messages] = results[0];
|
|
27
|
+
// messages: Array<[id, [field1, value1, field2, value2]]>
|
|
28
|
+
|
|
29
|
+
for (const [id, fields] of messages) {
|
|
30
|
+
console.log("Id:", id, "Data:", fields);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Continue from the last received ID:
|
|
34
|
+
const lastMsg = messages[messages.length - 1];
|
|
35
|
+
await listenForMessage(lastMsg[0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
listenForMessage();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Consumer groups:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// Create group:
|
|
45
|
+
await redis.xgroup("CREATE", "mystream", "mygroup", "0", "MKSTREAM");
|
|
46
|
+
|
|
47
|
+
// Read as consumer:
|
|
48
|
+
const results = await redis.xreadgroup(
|
|
49
|
+
"GROUP",
|
|
50
|
+
"mygroup",
|
|
51
|
+
"consumer1",
|
|
52
|
+
"COUNT",
|
|
53
|
+
10,
|
|
54
|
+
"BLOCK",
|
|
55
|
+
5000,
|
|
56
|
+
"STREAMS",
|
|
57
|
+
"mystream",
|
|
58
|
+
">",
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Acknowledge:
|
|
62
|
+
await redis.xack("mystream", "mygroup", messageId);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Auto-pipelining
|
|
66
|
+
|
|
67
|
+
Automatically batches all commands issued in the same event loop tick into a single pipeline:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const redis = new Redis({ enableAutoPipelining: true });
|
|
71
|
+
|
|
72
|
+
// These three commands from the same tick are sent as one pipeline:
|
|
73
|
+
const [a, b, c] = await Promise.all([redis.get("key1"), redis.get("key2"), redis.get("key3")]);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Performance: 35-50% throughput improvement in benchmarks.
|
|
77
|
+
|
|
78
|
+
In Cluster mode, one pipeline per node is created. Commands are routed by slot.
|
|
79
|
+
|
|
80
|
+
Exclude specific commands from auto-pipelining:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const redis = new Redis({
|
|
84
|
+
enableAutoPipelining: true,
|
|
85
|
+
autoPipeliningIgnoredCommands: ["subscribe", "unsubscribe"],
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Lua scripting details
|
|
90
|
+
|
|
91
|
+
### defineCommand
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
redis.defineCommand("mycommand", {
|
|
95
|
+
numberOfKeys: 2,
|
|
96
|
+
lua: `
|
|
97
|
+
local key1 = KEYS[1]
|
|
98
|
+
local key2 = KEYS[2]
|
|
99
|
+
local arg1 = ARGV[1]
|
|
100
|
+
return redis.call('SET', key1, arg1)
|
|
101
|
+
`,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Use:
|
|
105
|
+
await redis.mycommand("k1", "k2", "value");
|
|
106
|
+
|
|
107
|
+
// Buffer variant is auto-created:
|
|
108
|
+
await redis.mycommandBuffer("k1", "k2", "value");
|
|
109
|
+
|
|
110
|
+
// Works with pipeline:
|
|
111
|
+
await redis.pipeline().mycommand("k1", "k2", "value").exec();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Dynamic key count
|
|
115
|
+
|
|
116
|
+
Omit `numberOfKeys` and pass it as the first argument each time:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
redis.defineCommand("dynamicScript", {
|
|
120
|
+
lua: "return {KEYS[1], ARGV[1]}",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await redis.dynamicScript(1, "mykey", "myarg"); // 1 = numberOfKeys
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Constructor option
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const redis = new Redis({
|
|
130
|
+
scripts: {
|
|
131
|
+
mycommand: {
|
|
132
|
+
numberOfKeys: 2,
|
|
133
|
+
lua: "return {KEYS[1],KEYS[2],ARGV[1]}",
|
|
134
|
+
readOnly: true, // Hint for Cluster read-write splitting
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
ioredis uses EVALSHA internally and falls back to EVAL only when the script is not cached.
|
|
141
|
+
|
|
142
|
+
### TypeScript typing
|
|
143
|
+
|
|
144
|
+
For TypeScript projects, declare custom commands on the Redis interface:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
declare module "ioredis" {
|
|
148
|
+
interface RedisCommander<Context> {
|
|
149
|
+
mycommand(key1: string, key2: string, arg1: string): Result<string, Context>;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Argument and reply transformers
|
|
155
|
+
|
|
156
|
+
Transform command arguments or replies globally:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { Redis } from "ioredis";
|
|
160
|
+
|
|
161
|
+
// Built-in: hmset accepts objects:
|
|
162
|
+
await redis.hmset("hash", { field1: "val1", field2: "val2" });
|
|
163
|
+
// Equivalent to: HMSET hash field1 val1 field2 val2
|
|
164
|
+
|
|
165
|
+
// Built-in: hgetall returns object:
|
|
166
|
+
const hash = await redis.hgetall("hash");
|
|
167
|
+
// { field1: "val1", field2: "val2" }
|
|
168
|
+
|
|
169
|
+
// Built-in: mset accepts objects:
|
|
170
|
+
await redis.mset({ k1: "v1", k2: "v2" });
|
|
171
|
+
|
|
172
|
+
// Custom argument transformer:
|
|
173
|
+
Redis.Command.setArgumentTransformer("hmset", (args) => {
|
|
174
|
+
if (args.length === 2 && typeof args[1] === "object") {
|
|
175
|
+
const flat = [];
|
|
176
|
+
for (const [k, v] of Object.entries(args[1])) {
|
|
177
|
+
flat.push(k, v);
|
|
178
|
+
}
|
|
179
|
+
return [args[0], ...flat];
|
|
180
|
+
}
|
|
181
|
+
return args;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Custom reply transformer:
|
|
185
|
+
Redis.Command.setReplyTransformer("hgetall", (result) => {
|
|
186
|
+
if (Array.isArray(result)) {
|
|
187
|
+
const obj: Record<string, string> = {};
|
|
188
|
+
for (let i = 0; i < result.length; i += 2) {
|
|
189
|
+
obj[result[i]] = result[i + 1];
|
|
190
|
+
}
|
|
191
|
+
return obj;
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Binary data
|
|
198
|
+
|
|
199
|
+
Send binary data with Buffers:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
await redis.set("binary", Buffer.from([0x62, 0x75, 0x66]));
|
|
203
|
+
|
|
204
|
+
// Use Buffer variant to receive binary:
|
|
205
|
+
const buf = await redis.getBuffer("binary");
|
|
206
|
+
// buf: <Buffer 62 75 66>
|
|
207
|
+
|
|
208
|
+
// Every bulk-string command has a Buffer variant:
|
|
209
|
+
// getBuffer, hgetallBuffer, lrangeBuffer, etc.
|
|
210
|
+
|
|
211
|
+
// You don't need setBuffer — regular set() accepts Buffers:
|
|
212
|
+
await redis.set("key", Buffer.from("hello"));
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Key prefixing
|
|
216
|
+
|
|
217
|
+
Auto-prefix all keys transparently:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const redis = new Redis({ keyPrefix: "app:" });
|
|
221
|
+
|
|
222
|
+
await redis.set("user:1", "Alice");
|
|
223
|
+
// Actually sends: SET app:user:1 Alice
|
|
224
|
+
|
|
225
|
+
await redis.get("user:1");
|
|
226
|
+
// Actually sends: GET app:user:1
|
|
227
|
+
|
|
228
|
+
// Works with pipeline and custom commands:
|
|
229
|
+
redis
|
|
230
|
+
.pipeline()
|
|
231
|
+
.set("k1", "v1") // SET app:k1 v1
|
|
232
|
+
.get("k1") // GET app:k1
|
|
233
|
+
.exec();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Limitation**: Does not apply to pattern-based commands like `KEYS`, `SCAN`, or to reply values that happen to be key names.
|
|
237
|
+
|
|
238
|
+
## Monitor mode
|
|
239
|
+
|
|
240
|
+
See all commands received by the server across all clients:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
const monitor = await redis.monitor();
|
|
244
|
+
|
|
245
|
+
monitor.on("monitor", (time, args, source, database) => {
|
|
246
|
+
// time: Unix timestamp from Redis
|
|
247
|
+
// args: command arguments array
|
|
248
|
+
// source: client address
|
|
249
|
+
// database: database number
|
|
250
|
+
console.log(time, args.join(" "), "from", source);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// When done:
|
|
254
|
+
monitor.disconnect();
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
A monitor connection cannot run other commands.
|
|
258
|
+
|
|
259
|
+
## Error handling
|
|
260
|
+
|
|
261
|
+
All Redis server errors are `ReplyError` instances:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { Redis } from "ioredis";
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
await redis.set("foo"); // wrong number of args
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err instanceof Redis.ReplyError) {
|
|
270
|
+
console.log(err.message); // "ERR wrong number of arguments for 'set' command"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Friendly error stacks (dev only)
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// Default stack: internal ioredis frames (useless for debugging)
|
|
279
|
+
// Friendly stack: points to your code
|
|
280
|
+
|
|
281
|
+
const redis = new Redis({ showFriendlyErrorStack: true });
|
|
282
|
+
// NEVER use in production — significant performance overhead
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Pipeline error handling
|
|
286
|
+
|
|
287
|
+
`pipeline.exec()` never rejects. Each command's result is `[error, value]`:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const results = await redis.pipeline().set("foo", "bar").get("nonexistent").exec();
|
|
291
|
+
|
|
292
|
+
// results[0] = [null, "OK"] — success
|
|
293
|
+
// results[1] = [null, null] — key doesn't exist (not an error)
|
|
294
|
+
|
|
295
|
+
// If a command fails:
|
|
296
|
+
// results[N] = [ReplyError, undefined]
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Debugging
|
|
300
|
+
|
|
301
|
+
Enable debug logging via the DEBUG environment variable:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
DEBUG=ioredis:* node app.js
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Prefix patterns:
|
|
308
|
+
|
|
309
|
+
- `ioredis:*` — all logs
|
|
310
|
+
- `ioredis:redis` — connection/command logs
|
|
311
|
+
- `ioredis:cluster` — cluster-specific logs
|
|
312
|
+
- `ioredis:sentinel` — sentinel-specific logs
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# ioredis Cluster & Sentinel Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Redis Cluster](#redis-cluster)
|
|
6
|
+
- [ClusterOptions](#clusteroptions)
|
|
7
|
+
- [Read-write splitting](#read-write-splitting)
|
|
8
|
+
- [NAT mapping](#nat-mapping)
|
|
9
|
+
- [Pipeline & transaction in Cluster](#pipeline--transaction-in-cluster)
|
|
10
|
+
- [Pub/Sub in Cluster](#pubsub-in-cluster)
|
|
11
|
+
- [Cluster events](#cluster-events)
|
|
12
|
+
- [Cluster password](#cluster-password)
|
|
13
|
+
- [AWS ElastiCache with TLS](#aws-elasticache-with-tls)
|
|
14
|
+
- [Sentinel](#sentinel)
|
|
15
|
+
- [SentinelConnectionOptions](#sentinelconnectionoptions)
|
|
16
|
+
- [Sentinel failover](#sentinel-failover)
|
|
17
|
+
|
|
18
|
+
> **Terminology note:** ioredis uses `slave` in its API (e.g., `scaleReads: "slave"`, `role: "slave"`, `preferredSlaves`); Redis 5.0+ documentation uses `replica` instead.
|
|
19
|
+
|
|
20
|
+
## Redis Cluster
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { Cluster } from "ioredis";
|
|
24
|
+
|
|
25
|
+
const cluster = new Cluster([
|
|
26
|
+
{ host: "127.0.0.1", port: 6380 },
|
|
27
|
+
{ host: "127.0.0.1", port: 6381 },
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
await cluster.set("foo", "bar");
|
|
31
|
+
const val = await cluster.get("foo"); // "bar"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
First argument: startup nodes (not all nodes needed — ioredis auto-discovers the rest).
|
|
35
|
+
|
|
36
|
+
## ClusterOptions
|
|
37
|
+
|
|
38
|
+
Second argument to `new Cluster(nodes, options)`:
|
|
39
|
+
|
|
40
|
+
| Option | Type | Default | Description |
|
|
41
|
+
| ------------------------- | ------------------------------------------ | ------------------------------ | ------------------------------------------------------------------------------ |
|
|
42
|
+
| `clusterRetryStrategy` | `(times) => number \| void` | backoff 100+times\*2, max 2000 | Delay before reconnecting when no startup nodes reachable. Return void to stop |
|
|
43
|
+
| `scaleReads` | `"master" \| "slave" \| "all" \| Function` | `"master"` | Where to route read queries |
|
|
44
|
+
| `enableOfflineQueue` | `boolean` | `true` | Queue commands before ready |
|
|
45
|
+
| `enableReadyCheck` | `boolean` | `true` | Wait for CLUSTER INFO ready |
|
|
46
|
+
| `maxRedirections` | `number` | `16` | Max MOVED/ASK redirections per command |
|
|
47
|
+
| `retryDelayOnFailover` | `number` | `100` | Ms delay when target node disconnected |
|
|
48
|
+
| `retryDelayOnClusterDown` | `number` | `100` | Ms delay on CLUSTERDOWN error |
|
|
49
|
+
| `retryDelayOnTryAgain` | `number` | `100` | Ms delay on TRYAGAIN error |
|
|
50
|
+
| `retryDelayOnMoved` | `number` | `0` | Ms delay on MOVED error |
|
|
51
|
+
| `slotsRefreshTimeout` | `number` | `1000` | Ms timeout for slot refresh |
|
|
52
|
+
| `slotsRefreshInterval` | `number` | disabled | Ms between automatic slot refreshes |
|
|
53
|
+
| `redisOptions` | `RedisOptions` | `{}` | Default options for each node connection |
|
|
54
|
+
| `dnsLookup` | `Function` | `dns.lookup` | Custom DNS resolution |
|
|
55
|
+
| `natMap` | `object \| Function` | `undefined` | NAT address translation |
|
|
56
|
+
| `shardedSubscribers` | `boolean` | `false` | Enable sharded Pub/Sub connections |
|
|
57
|
+
|
|
58
|
+
### clusterRetryStrategy
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// Default:
|
|
62
|
+
clusterRetryStrategy(times) {
|
|
63
|
+
return Math.min(100 + times * 2, 2000);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Switch startup nodes on failure:
|
|
67
|
+
clusterRetryStrategy(times) {
|
|
68
|
+
this.startupNodes = [{ port: 6790, host: "127.0.0.1" }];
|
|
69
|
+
return Math.min(100 + times * 2, 2000);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Read-write splitting
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const cluster = new Cluster(nodes, { scaleReads: "slave" });
|
|
77
|
+
cluster.set("foo", "bar"); // -> master
|
|
78
|
+
cluster.get("foo"); // -> random slave (may lag behind master)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`scaleReads` values:
|
|
82
|
+
|
|
83
|
+
- `"master"` (default) — all queries to master
|
|
84
|
+
- `"slave"` — writes to master, reads to slaves
|
|
85
|
+
- `"all"` — writes to master, reads to master or slaves randomly
|
|
86
|
+
- `function(nodes, command)` — custom routing. First node is always master. Return single node or array (random pick)
|
|
87
|
+
|
|
88
|
+
## NAT mapping
|
|
89
|
+
|
|
90
|
+
For clusters behind NAT (Docker, AWS VPC):
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// Object mapping:
|
|
94
|
+
const cluster = new Cluster([{ host: "203.0.113.73", port: 30001 }], {
|
|
95
|
+
natMap: {
|
|
96
|
+
"10.0.1.230:30001": { host: "203.0.113.73", port: 30001 },
|
|
97
|
+
"10.0.1.231:30001": { host: "203.0.113.73", port: 30002 },
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Function mapping:
|
|
102
|
+
const cluster = new Cluster([{ host: "203.0.113.73", port: 30001 }], {
|
|
103
|
+
natMap: (key) => {
|
|
104
|
+
if (key.includes("30001")) return { host: "203.0.113.73", port: 30001 };
|
|
105
|
+
return null;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Pipeline & transaction in Cluster
|
|
111
|
+
|
|
112
|
+
**Constraint**: All keys in a pipeline must hash to slots on the same node.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// Works — same slot via hash tags:
|
|
116
|
+
cluster.pipeline().set("{user:1}:name", "Alice").set("{user:1}:email", "alice@example.com").exec();
|
|
117
|
+
|
|
118
|
+
// multi() without pipeline is NOT supported in Cluster:
|
|
119
|
+
// cluster.multi({ pipeline: false }) — throws error
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Auto-resend: If all commands get the same MOVED/ASK error and all successful commands were read-only, ioredis automatically resends the entire pipeline to the correct node.
|
|
123
|
+
|
|
124
|
+
## Pub/Sub in Cluster
|
|
125
|
+
|
|
126
|
+
Works the same as standalone. One node is subscribed; messages are broadcast cluster-wide.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const pub = new Cluster(nodes);
|
|
130
|
+
const sub = new Cluster(nodes);
|
|
131
|
+
|
|
132
|
+
sub.on("message", (channel, message) => console.log(channel, message));
|
|
133
|
+
await sub.subscribe("news");
|
|
134
|
+
pub.publish("news", "hello");
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Sharded Pub/Sub (Redis >= 7)
|
|
138
|
+
|
|
139
|
+
Enable `shardedSubscribers: true`. All channels in one `ssubscribe` call must hash to the same slot.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const cluster = new Cluster(nodes, { shardedSubscribers: true });
|
|
143
|
+
|
|
144
|
+
cluster.on("smessage", (channel, message) => console.log(message));
|
|
145
|
+
await cluster.ssubscribe("channel{my}:1", "channel{my}:2");
|
|
146
|
+
cluster.spublish("channel{my}:1", "test message");
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Cluster events
|
|
150
|
+
|
|
151
|
+
| Event | Description |
|
|
152
|
+
| -------------- | -------------------------------------------------------- |
|
|
153
|
+
| `connect` | Connection established |
|
|
154
|
+
| `ready` | Cluster ready (after CLUSTER INFO if `enableReadyCheck`) |
|
|
155
|
+
| `error` | Error with `lastNodeError` property |
|
|
156
|
+
| `close` | Connection closed |
|
|
157
|
+
| `reconnecting` | Reconnecting, arg = delay ms |
|
|
158
|
+
| `end` | No more reconnections |
|
|
159
|
+
| `+node` | New node connected |
|
|
160
|
+
| `-node` | Node disconnected |
|
|
161
|
+
| `node error` | Error connecting to node (2nd arg = address) |
|
|
162
|
+
|
|
163
|
+
## Cluster password
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// Same password for all nodes:
|
|
167
|
+
const cluster = new Cluster(nodes, {
|
|
168
|
+
redisOptions: { password: "cluster-password" },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Per-node passwords:
|
|
172
|
+
const cluster = new Cluster(
|
|
173
|
+
[
|
|
174
|
+
{ port: 30001, password: "pass-for-30001" },
|
|
175
|
+
{ port: 30002, password: null }, // no password
|
|
176
|
+
],
|
|
177
|
+
{
|
|
178
|
+
redisOptions: { password: "fallback-password" },
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## AWS ElastiCache with TLS
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const cluster = new Cluster(
|
|
187
|
+
[{ host: "clustercfg.myCluster.abcdefg.xyz.cache.amazonaws.com", port: 6379 }],
|
|
188
|
+
{
|
|
189
|
+
dnsLookup: (address, callback) => callback(null, address),
|
|
190
|
+
redisOptions: { tls: {} },
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The `dnsLookup` override prevents certificate validation errors with ElastiCache TLS.
|
|
196
|
+
|
|
197
|
+
## Sentinel
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { Redis } from "ioredis";
|
|
201
|
+
|
|
202
|
+
const redis = new Redis({
|
|
203
|
+
sentinels: [
|
|
204
|
+
{ host: "localhost", port: 26379 },
|
|
205
|
+
{ host: "localhost", port: 26380 },
|
|
206
|
+
],
|
|
207
|
+
name: "mymaster", // Master group name
|
|
208
|
+
// Optional:
|
|
209
|
+
sentinelPassword: "sentinel-pass",
|
|
210
|
+
sentinelUsername: "sentinel-user", // Redis >= 6
|
|
211
|
+
role: "master", // "master" (default) or "slave"
|
|
212
|
+
password: "redis-pass", // Password for the actual Redis instance
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
ioredis guarantees:
|
|
217
|
+
|
|
218
|
+
- Always connects to the current master (even after failover)
|
|
219
|
+
- Commands during failover are queued and run on the new master
|
|
220
|
+
- If `role: "slave"`, always connects to a slave; reconnects to another slave if promoted
|
|
221
|
+
|
|
222
|
+
### preferredSlaves
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// Array format (lower prio = higher priority):
|
|
226
|
+
const redis = new Redis({
|
|
227
|
+
sentinels: [...],
|
|
228
|
+
name: "mymaster",
|
|
229
|
+
role: "slave",
|
|
230
|
+
preferredSlaves: [
|
|
231
|
+
{ ip: "127.0.0.1", port: "31231", prio: 1 },
|
|
232
|
+
{ ip: "127.0.0.1", port: "31232", prio: 2 },
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Function format:
|
|
237
|
+
const redis = new Redis({
|
|
238
|
+
sentinels: [...],
|
|
239
|
+
name: "mymaster",
|
|
240
|
+
role: "slave",
|
|
241
|
+
preferredSlaves(availableSlaves) {
|
|
242
|
+
return availableSlaves.find(s => s.port === "31234") || false;
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## SentinelConnectionOptions
|
|
248
|
+
|
|
249
|
+
| Option | Type | Default | Description |
|
|
250
|
+
| --------------------------- | --------------------------- | --------------------- | ------------------------------------------- |
|
|
251
|
+
| `sentinels` | `Array<{ host?, port? }>` | required | List of sentinel nodes |
|
|
252
|
+
| `name` | `string` | required | Master group name |
|
|
253
|
+
| `role` | `"master" \| "slave"` | `"master"` | Connect to master or slave |
|
|
254
|
+
| `sentinelPassword` | `string` | `undefined` | Password for sentinel instances |
|
|
255
|
+
| `sentinelUsername` | `string` | `undefined` | Username for sentinel (Redis >= 6) |
|
|
256
|
+
| `sentinelRetryStrategy` | `(times) => number \| void` | `min(times*10, 1000)` | Retry when all sentinels unreachable |
|
|
257
|
+
| `sentinelReconnectStrategy` | `(times) => number \| void` | — | Reconnect strategy for sentinel connections |
|
|
258
|
+
| `preferredSlaves` | `Array \| Function` | `undefined` | Slave selection preference |
|
|
259
|
+
| `enableTLSForSentinelMode` | `boolean` | `false` | TLS for sentinel connections |
|
|
260
|
+
| `sentinelTLS` | `tls.ConnectionOptions` | `undefined` | TLS options for sentinel |
|
|
261
|
+
| `tls` | `tls.ConnectionOptions` | `undefined` | TLS for Redis connection |
|
|
262
|
+
| `updateSentinels` | `boolean` | `true` | Auto-update sentinel list |
|
|
263
|
+
| `failoverDetector` | `boolean` | `false` | Detect failover proactively |
|
|
264
|
+
| `natMap` | `object \| Function` | `undefined` | NAT mapping |
|
|
265
|
+
| `sentinelMaxConnections` | `number` | `10` | Max sentinel connections |
|
|
266
|
+
| `sentinelCommandTimeout` | `number` | `undefined` | Timeout for sentinel commands |
|
|
267
|
+
| `connectTimeout` | `number` | `undefined` | Connect timeout for sentinel |
|
|
268
|
+
| `disconnectTimeout` | `number` | `undefined` | Disconnect timeout |
|
|
269
|
+
|
|
270
|
+
## Sentinel failover
|
|
271
|
+
|
|
272
|
+
Default `sentinelRetryStrategy`:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
sentinelRetryStrategy(times) {
|
|
276
|
+
return Math.min(times * 10, 1000);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
When all sentinels are unreachable, this controls the delay before retrying from scratch. Return `undefined` to stop.
|