@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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -0
  5. package/README.md +128 -0
  6. package/agents/accessibility-pro.md +139 -0
  7. package/agents/api-builder.md +110 -0
  8. package/agents/code-reviewer.md +190 -0
  9. package/agents/database-expert.md +138 -0
  10. package/agents/debugger.md +241 -0
  11. package/agents/docker-expert.md +51 -0
  12. package/agents/multi-agent-coordinator.md +378 -0
  13. package/agents/nextjs-expert.md +136 -0
  14. package/agents/performance-engineer.md +138 -0
  15. package/agents/playwright-expert.md +126 -0
  16. package/agents/react-specialist.md +97 -0
  17. package/agents/security-scanner.md +105 -0
  18. package/agents/test-generator.md +221 -0
  19. package/agents/typescript-pro.md +253 -0
  20. package/agents/ux-optimizer.md +93 -0
  21. package/docs/rules/orchestration.md.template +126 -0
  22. package/package.json +28 -0
  23. package/skills/bullmq/SKILL.md +225 -0
  24. package/skills/bullmq/references/flows-and-schedulers.md +186 -0
  25. package/skills/bullmq/references/job-types-and-options.md +163 -0
  26. package/skills/bullmq/references/patterns.md +273 -0
  27. package/skills/bullmq/references/production.md +308 -0
  28. package/skills/composition-patterns/SKILL.md +58 -0
  29. package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
  30. package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
  31. package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
  32. package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
  33. package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
  34. package/skills/composition-patterns/references/state-context-interface.md +194 -0
  35. package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
  36. package/skills/composition-patterns/references/state-lift-state.md +126 -0
  37. package/skills/conventional-commits/SKILL.md +148 -0
  38. package/skills/docker/SKILL.md +55 -0
  39. package/skills/docker/references/compose-configs.md +95 -0
  40. package/skills/docker/references/monorepo-dockerfile.md +111 -0
  41. package/skills/drizzle-pg/SKILL.md +202 -0
  42. package/skills/drizzle-pg/references/advanced.md +299 -0
  43. package/skills/drizzle-pg/references/migrations.md +214 -0
  44. package/skills/drizzle-pg/references/queries.md +321 -0
  45. package/skills/drizzle-pg/references/relations.md +272 -0
  46. package/skills/drizzle-pg/references/schema-pg.md +256 -0
  47. package/skills/drizzle-pg/references/sql-operator.md +215 -0
  48. package/skills/fastify-best-practices/SKILL.md +143 -0
  49. package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
  50. package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
  51. package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
  52. package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
  53. package/skills/fastify-best-practices/references/server-and-options.md +127 -0
  54. package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
  55. package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
  56. package/skills/ioredis/SKILL.md +51 -0
  57. package/skills/ioredis/references/advanced-patterns.md +312 -0
  58. package/skills/ioredis/references/cluster-sentinel.md +280 -0
  59. package/skills/ioredis/references/connection-options.md +187 -0
  60. package/skills/ioredis/references/core-api.md +179 -0
  61. package/skills/nextjs-best-practices/SKILL.md +194 -0
  62. package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
  63. package/skills/nextjs-best-practices/references/bundling.md +192 -0
  64. package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
  65. package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
  66. package/skills/nextjs-best-practices/references/directives.md +74 -0
  67. package/skills/nextjs-best-practices/references/error-handling.md +237 -0
  68. package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
  69. package/skills/nextjs-best-practices/references/font.md +175 -0
  70. package/skills/nextjs-best-practices/references/functions.md +116 -0
  71. package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
  72. package/skills/nextjs-best-practices/references/image.md +184 -0
  73. package/skills/nextjs-best-practices/references/metadata.md +305 -0
  74. package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
  75. package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
  76. package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
  77. package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
  78. package/skills/nextjs-best-practices/references/scripts.md +148 -0
  79. package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
  80. package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
  81. package/skills/owasp-security-review/SKILL.md +98 -0
  82. package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
  83. package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
  84. package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
  85. package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
  86. package/skills/owasp-security-review/references/a05-injection.md +106 -0
  87. package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
  88. package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
  89. package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
  90. package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
  91. package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
  92. package/skills/postgresql/SKILL.md +50 -0
  93. package/skills/postgresql/references/ddl-schema.md +300 -0
  94. package/skills/postgresql/references/indexes.md +257 -0
  95. package/skills/postgresql/references/jsonb.md +261 -0
  96. package/skills/postgresql/references/performance.md +291 -0
  97. package/skills/postgresql/references/psql-cli.md +153 -0
  98. package/skills/postgresql/references/queries.md +287 -0
  99. package/skills/postgresql/references/transactions.md +280 -0
  100. package/skills/react-best-practices/SKILL.md +110 -0
  101. package/skills/react-best-practices/references/advanced-patterns.md +91 -0
  102. package/skills/react-best-practices/references/async-patterns.md +233 -0
  103. package/skills/react-best-practices/references/bundle-optimization.md +201 -0
  104. package/skills/react-best-practices/references/client-patterns.md +178 -0
  105. package/skills/react-best-practices/references/js-performance.md +210 -0
  106. package/skills/react-best-practices/references/rendering-performance.md +209 -0
  107. package/skills/react-best-practices/references/rerender-optimization.md +316 -0
  108. package/skills/react-best-practices/references/server-performance.md +274 -0
  109. package/skills/service-worker/SKILL.md +195 -0
  110. package/skills/service-worker/references/api-reference.md +114 -0
  111. package/skills/service-worker/references/caching-strategies.md +202 -0
  112. package/skills/service-worker/references/push-and-sync.md +261 -0
  113. package/skills/typescript-conventions/SKILL.md +51 -0
  114. package/skills/ui-ux-guidelines/SKILL.md +105 -0
  115. package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
  116. package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
  117. 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.