@venizia/ignis-docs 0.2.0 → 0.2.1-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.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -0,0 +1,784 @@
1
+ ---
2
+ title: Redis - Full Reference
3
+ description: Complete reference for the Redis helper class hierarchy, all three topology constructors, the full data API, lifecycle events, and framework integrations
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # Redis - Full Reference
8
+
9
+ Exhaustive reference for `AbstractRedisHelper`, the three topology classes, the `createRedisHelper` factory, and every data-API method. For a readable introduction and the common tasks, start with the [Redis overview](/extensions/helpers/redis/).
10
+
11
+ Backed by **ioredis** under the hood.
12
+
13
+ **Files:**
14
+
15
+ - [`packages/helpers/src/modules/redis/base/abstract.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/base/abstract.helper.ts) - `AbstractRedisHelper`
16
+ - [`packages/helpers/src/modules/redis/single/single.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/single/single.helper.ts) - `RedisSingleHelper`
17
+ - [`packages/helpers/src/modules/redis/cluster/cluster.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/cluster/cluster.helper.ts) - `RedisClusterHelper`
18
+ - [`packages/helpers/src/modules/redis/sentinel/sentinel.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/sentinel/sentinel.helper.ts) - `RedisSentinelHelper`
19
+ - [`packages/helpers/src/modules/redis/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/factory.ts) - `createRedisHelper` factory
20
+ - [`packages/helpers/src/modules/redis/common/interfaces.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/interfaces.ts) - `IRedisHelper` and its nine capability interfaces
21
+ - [`packages/helpers/src/modules/redis/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/types.ts) - option and callback types
22
+ - [`packages/helpers/src/modules/redis/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/constants.ts) - `RedisModes`, `RedisSentinelRoles`
23
+
24
+ ## Find what you need
25
+
26
+ | You want to | Go to |
27
+ |---|---|
28
+ | See the class hierarchy and the nine capability interfaces | [Class and Interface Model](#class-and-interface-model) |
29
+ | Construct a single-node client | [Construction - Single](#construction---single) |
30
+ | Construct a cluster client | [Construction - Cluster](#construction---cluster) |
31
+ | Construct a Sentinel client, or understand Sentinel HA | [Construction - Sentinel](#construction---sentinel) |
32
+ | Pick a topology from configuration instead of hardcoding a class | [Selecting a Topology - the Factory](#selecting-a-topology---the-factory) |
33
+ | Understand `autoConnect`, the four lifecycle callbacks, or `duplicateClient()` | [Lifecycle and Events](#lifecycle-and-events) |
34
+ | Look up a method's signature and behavior | [Full Method Reference](#full-method-reference) |
35
+ | Wire the helper into BullMQ, Socket.IO, WebSocket, or Casbin | [Using the Helper Across IGNIS](#using-the-helper-across-ignis) |
36
+ | Avoid a production footgun | [Production Notes](#production-notes) |
37
+ | Copy the full import list | [Import Reference](#import-reference) |
38
+
39
+ ## Class and Interface Model
40
+
41
+ ### Class hierarchy
42
+
43
+ | Class | Extends | Notes |
44
+ |-------|---------|-------|
45
+ | `AbstractRedisHelper` | `BaseHelper` | Base class; implements `IRedisHelper`; never constructed directly |
46
+ | `RedisSingleHelper` | `AbstractRedisHelper` | ioredis `Redis` client; returns `Redis` from `getClient()` |
47
+ | `RedisClusterHelper` | `AbstractRedisHelper` | ioredis `Cluster` client; returns `Cluster` from `getClient()` |
48
+ | `RedisSentinelHelper` | `AbstractRedisHelper` | ioredis `Redis` client (Sentinel mode); returns `Redis` from `getClient()` |
49
+
50
+ > [!TIP] Typing rule
51
+ > Declare parameters and bindings as `IRedisHelper`. Use `isRedisHelper(value)` for runtime checks - it reads a `Symbol.for` brand, so it still answers `true` for a helper built by a second installed copy of the package, where `instanceof AbstractRedisHelper` answers `false`. Never type-check against the concrete subclasses unless you need topology-specific behavior.
52
+
53
+ ### TRedisClient
54
+
55
+ ```typescript
56
+ type TRedisClient = Redis | Cluster; // ioredis
57
+ ```
58
+
59
+ `RedisSingleHelper` and `RedisSentinelHelper` return a `Redis` instance. `RedisClusterHelper` returns a `Cluster` instance. Both satisfy `TRedisClient`.
60
+
61
+ ### IRedisHelper and its nine capability interfaces
62
+
63
+ `Source ->` [`packages/helpers/src/modules/redis/common/interfaces.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/interfaces.ts)
64
+
65
+ `IRedisHelper` extends all nine of the following:
66
+
67
+ | Interface | Responsibility |
68
+ |-----------|----------------|
69
+ | `IRedisConnection` | Connection lifecycle - `connect`, `disconnect`, `ping`, `getClient`, `duplicateClient` |
70
+ | `IRedisKey` | Key TTL and counters - `exists`, `expire`, `expireAt`, `ttl`, `persist`, `incr`/`decr` variants |
71
+ | `IRedisKeyValue` | String key-value - `get`, `set`, `del`, `keys`, `getString(s)`, `getObject(s)`, `mSet`, `mGet` |
72
+ | `IRedisHash` | Hash maps - `hSet`, `hGetAll`, `hGet`, `hDel`, `hExists`, `hKeys`, `hVals`, `hIncrBy`, `hLen` |
73
+ | `IRedisSet` | Sets - `sAdd`, `sRem`, `sMembers`, `sIsMember`, `sCard` |
74
+ | `IRedisList` | Lists - `lPush`, `rPush`, `lPop`, `rPop`, `lRange`, `lLen` |
75
+ | `IRedisPubSub` | Pub/Sub with optional zlib compression - `publish`, `subscribe`, `unsubscribe` |
76
+ | `IRedisJson` | RedisJSON module operations - `jSet`, `jGet`, `jDelete`, `jNumberIncreaseBy`, `jStringAppend`, `jPush`, `jPop` |
77
+ | `IRedisCommand` | Raw escape hatch - `execute(command, params?)` |
78
+
79
+ ## Construction - Single
80
+
81
+ `Source ->` [`packages/helpers/src/modules/redis/single/single.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/single/single.helper.ts)
82
+
83
+ ```typescript
84
+ import { RedisSingleHelper } from '@venizia/ignis-helpers';
85
+
86
+ const redis = new RedisSingleHelper({
87
+ name: 'cache',
88
+ host: 'localhost',
89
+ port: 6379,
90
+ password: 'secret',
91
+ database: 0,
92
+ autoConnect: true,
93
+ maxRetry: 5,
94
+
95
+ onInitialized: ({ name, helper }) => {
96
+ console.log(`[${name}] initialized`);
97
+ },
98
+ onConnected: ({ name }) => {
99
+ console.log(`[${name}] connected`);
100
+ },
101
+ onReady: ({ name }) => {
102
+ console.log(`[${name}] ready`);
103
+ },
104
+ onError: ({ name, error }) => {
105
+ console.error(`[${name}] error`, error);
106
+ },
107
+ });
108
+ ```
109
+
110
+ ### IRedisSingleHelperOptions
111
+
112
+ Combines `IRedisSingleHelperProps` and `IRedisHelperCallbacks`.
113
+
114
+ | Field | Type | Required | Default | Description |
115
+ |-------|------|----------|---------|-------------|
116
+ | `name` | `string` | Yes | - | Helper identifier used in logs and as `IRedisHelper.name` |
117
+ | `host` | `string` | Yes | - | Redis server hostname |
118
+ | `port` | `string \| number` | Yes | - | Redis server port |
119
+ | `user` | `string` | No | - | Accepted by the type but not read by the constructor - has no effect. Set auth via `password` only. |
120
+ | `password` | `string` | Yes | - | Redis `requirepass` value |
121
+ | `database` | `number` | No | `0` | Redis database index (0-15) |
122
+ | `autoConnect` | `boolean` | No | `true` | Connect immediately; `false` uses ioredis `lazyConnect` |
123
+ | `maxRetry` | `number` | No | `0` | Reconnect attempts before giving up. `0` (default) = no reconnect; `-1` = reconnect forever |
124
+ | `onInitialized` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired synchronously after construction |
125
+ | `onConnected` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired on TCP connection established |
126
+ | `onReady` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired when client is ready for commands |
127
+ | `onError` | `(opts: { name: string; helper: IRedisHelper; error: unknown }) => void` | No | - | Fired on connection or command errors |
128
+
129
+ ### Retry strategy
130
+
131
+ ioredis calls the strategy with `attempt` starting at `1`, so the first reconnect waits 2 s.
132
+
133
+ | `attempt` | Delay |
134
+ |---|---|
135
+ | 1 | 2 s |
136
+ | 2 | 4 s |
137
+ | 3 and later | 5 s |
138
+
139
+ - **Backoff formula.** `Math.max(Math.min(attempt * 2000, 5000), 1000)`. The `1000` floor is unreachable, because the smallest `attempt` is `1`.
140
+ - **Backoff resets on success.** ioredis sets `retryAttempts` back to `0` once the client reaches `ready`.
141
+ - **Stop condition.** Reconnect stops when `attempt > maxRetry`, unless `maxRetry` is `-1`. Under the default `maxRetry: 0` that fires on the very first attempt, so the client never reconnects.
142
+ - **This strategy does not retry commands.** It only schedules reconnection.
143
+ - **BullMQ requirement.** The framework always sets `maxRetriesPerRequest: null` internally, which BullMQ requires.
144
+
145
+ ### What happens to commands while the connection is down
146
+
147
+ `maxRetriesPerRequest: null` turns off the per-request retry limit, so ioredis parks commands in its offline queue instead of failing them after N attempts. How that ends depends on `maxRetry`.
148
+
149
+ | `maxRetry` | Outcome for queued commands |
150
+ |---|---|
151
+ | `-1` or a positive number | Flushed to the server once the client reconnects |
152
+ | `0` (default) | Rejected with `Connection is closed.` as soon as the client gives up |
153
+
154
+ To survive a restart or a failover without losing commands, set `maxRetry: -1`.
155
+
156
+ ## Construction - Cluster
157
+
158
+ `Source ->` [`packages/helpers/src/modules/redis/cluster/cluster.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/cluster/cluster.helper.ts)
159
+
160
+ ```typescript
161
+ import { RedisClusterHelper } from '@venizia/ignis-helpers';
162
+
163
+ const cluster = new RedisClusterHelper({
164
+ name: 'cache-cluster',
165
+ nodes: [
166
+ { host: 'redis-node-1', port: 7000 },
167
+ { host: 'redis-node-2', port: 7001 },
168
+ { host: 'redis-node-3', port: 7002 },
169
+ ],
170
+ clusterOptions: {
171
+ redisOptions: { password: 'cluster-password' },
172
+ },
173
+ onReady: ({ name }) => {
174
+ console.log(`[${name}] cluster ready`);
175
+ },
176
+ });
177
+ ```
178
+
179
+ ### IRedisClusterHelperOptions
180
+
181
+ | Field | Type | Required | Default | Description |
182
+ |-------|------|----------|---------|-------------|
183
+ | `name` | `string` | Yes | - | Helper identifier |
184
+ | `nodes` | `Array<{ host: string; port: string \| number; password?: string }>` | Yes | - | Startup nodes - ioredis discovers the rest |
185
+ | `clusterOptions` | `ClusterOptions` | No | - | Passed verbatim to `new Cluster(nodes, clusterOptions)` |
186
+ | `onInitialized` / `onConnected` / `onReady` / `onError` | callbacks | No | - | Same shape as single |
187
+
188
+ > [!WARNING] No framework defaults injected
189
+ > Cluster does not apply the backoff retry strategy or `maxRetriesPerRequest: null` automatically. Pass those inside `clusterOptions.redisOptions` if your consumers (e.g. BullMQ) require them.
190
+
191
+ `duplicateClient()` on a cluster creates a **new** `Cluster` instance from the same startup nodes and options. ioredis `Cluster` doesn't implement `.duplicate()`, so the helper builds a fresh instance instead.
192
+
193
+ ## Construction - Sentinel
194
+
195
+ `Source ->` [`packages/helpers/src/modules/redis/sentinel/sentinel.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/sentinel/sentinel.helper.ts)
196
+
197
+ ### What Redis Sentinel is
198
+
199
+ Redis Sentinel is a high-availability (HA) architecture made of three process types:
200
+
201
+ ```
202
+ +------------------+ monitors +--------------------+
203
+ | Sentinel (1) |<------------------>| Master (primary) |
204
+ +------------------+ +--------------------+
205
+ +------------------+ replication |
206
+ | Sentinel (2) | +--------------+
207
+ +------------------+ v
208
+ +------------------+ +--------------------+
209
+ | Sentinel (3) | | Replica (standby) |
210
+ +------------------+ +--------------------+
211
+ ```
212
+
213
+ - **Sentinels** form a quorum (typically 3 processes). They monitor the master, agree by majority vote when it is down, and promote a replica to master (failover).
214
+ - **Master** accepts all writes.
215
+ - **Replicas** replicate from master and serve reads when `role: 'slave'` is set.
216
+
217
+ ### Why your app connects to sentinels, not the master
218
+
219
+ - **The master's address can change after a failover.** Hard-coding a master host would break on every failover.
220
+ - **You give ioredis the sentinel addresses instead**, plus the monitored master group name (`masterName`). ioredis asks a sentinel for the current master, then connects to it.
221
+ - **ioredis re-queries sentinels automatically after a failover** - no manual reconnection logic needed.
222
+ - **Your application code does not change during a failover.** The only observable effect is a few seconds of transient command failures. Set `maxRetry: -1` so the client reconnects and drains its queued commands - the default `maxRetry: 0` rejects them instead.
223
+
224
+ ### Constructor
225
+
226
+ ```typescript
227
+ import { RedisSentinelHelper } from '@venizia/ignis-helpers';
228
+
229
+ const redis = new RedisSentinelHelper({
230
+ name: 'ha-cache', // helper identifier (logging/scope)
231
+ masterName: 'mymaster', // group name in sentinel.conf "sentinel monitor mymaster ..."
232
+ sentinels: [
233
+ { host: '10.0.0.1', port: 26379 },
234
+ { host: '10.0.0.2', port: 26379 },
235
+ { host: '10.0.0.3' }, // port defaults to 26379
236
+ ],
237
+ role: 'master', // 'master' (default) or 'slave'
238
+ password: 'data-secret', // master/replica requirepass
239
+ sentinelPassword: 'sentinel-secret', // sentinel.conf requirepass
240
+ database: 0,
241
+ autoConnect: true,
242
+ maxRetry: 5,
243
+ });
244
+ ```
245
+
246
+ ### IRedisSentinelHelperOptions
247
+
248
+ | Field | Type | Required | Default | Description |
249
+ |-------|------|----------|---------|-------------|
250
+ | `name` | `string` | Yes | - | Helper identifier (logs, `IRedisHelper.name`) |
251
+ | `masterName` | `string` | Yes | - | Sentinel-monitored master group name - maps to ioredis `name` |
252
+ | `sentinels` | `Array<{ host: string; port?: string \| number }>` | Yes | - | Sentinel process addresses |
253
+ | `role` | `TRedisSentinelRole` (`RedisSentinelRoles.MASTER \| SLAVE`) | No | `RedisSentinelRoles.MASTER` | Connect to master (writes) or a replica (reads) |
254
+ | `password` | `string` | No | - | Data-node `requirepass` (master and replicas) |
255
+ | `sentinelPassword` | `string` | No | - | Sentinel-process `requirepass` |
256
+ | `sentinelUsername` | `string` | No | - | Sentinel ACL username |
257
+ | `database` | `number` | No | `0` | Redis database index |
258
+ | `autoConnect` | `boolean` | No | `true` | Connect immediately |
259
+ | `maxRetry` | `number` | No | `0` | Reconnect attempts before giving up. `0` (default) = no reconnect; `-1` = reconnect forever. See [Retry strategy](#retry-strategy) |
260
+ | `redisOptions` | `Partial<RedisOptions>` | No | - | Extra ioredis options; first-class fields above always override matching keys here |
261
+ | `onInitialized` / `onConnected` / `onReady` / `onError` | callbacks | No | - | Same shape as single |
262
+
263
+ ### Field clarity - name vs masterName
264
+
265
+ This is the most common source of confusion:
266
+
267
+ | Field | What it is | Where it goes |
268
+ |-------|-----------|---------------|
269
+ | `name` | The helper's own identifier used for logging and `IRedisHelper.name` | `AbstractRedisHelper` identifier |
270
+ | `masterName` | The master group being monitored by Sentinel processes, matching `sentinel monitor <name> ...` in sentinel.conf | Maps to ioredis `name` option |
271
+
272
+ They are independent. You can name the helper `'ha-cache'` while `masterName` is `'mymaster'`.
273
+
274
+ ### The authentication model - 4 relationships
275
+
276
+ Sentinel deployments have four separate authentication relationships. The app configures two of them. The other two are server-side.
277
+
278
+ | # | From | To | App field | Server config |
279
+ |---|------|----|-----------|---------------|
280
+ | 1 | **App** | **Sentinel processes** | `sentinelPassword` | `requirepass` in sentinel.conf |
281
+ | 2 | **App** | **Data nodes** (master/replica) | `password` | `requirepass` in redis.conf |
282
+ | 3 | **Sentinel** | **Data nodes** | (not set by app) | `sentinel auth-pass <group> <pass>` in sentinel.conf |
283
+ | 4 | **Replica** | **Master** (replication) | (not set by app) | `masterauth` in redis.conf |
284
+
285
+ **Relationship 3 is the silent failure.** If the data nodes require a password but `sentinel.conf` has no `sentinel auth-pass`, the sentinel processes can't check master health. They trigger false failovers instead. Configure `sentinel auth-pass` on the server side, even when your app sets only `password`.
286
+
287
+ **Common cases:**
288
+ - Private network, sentinels unauthenticated: set only `password`.
289
+ - Fully secured cluster: set both `password` and `sentinelPassword`; configure relationships 3 and 4 on the servers.
290
+
291
+ ### Failover behavior
292
+
293
+ When the master fails:
294
+
295
+ 1. Sentinels reach quorum and promote a replica.
296
+ 2. ioredis detects the master change via sentinel notification.
297
+ 3. ioredis reconnects to the new master transparently.
298
+ 4. Commands issued during the few-second window queue up, then flush once the client reconnects. This needs `maxRetry: -1` or a positive `maxRetry`; the default `0` rejects them.
299
+ 5. **Your application code does not change.** The same `RedisSentinelHelper` instance continues to work after failover.
300
+
301
+ ### Local testing with Docker Compose
302
+
303
+ The following Compose file starts a master, one replica, and one sentinel with full authentication. It demonstrates all four relationships.
304
+
305
+ ```yaml
306
+ version: '3.8'
307
+
308
+ services:
309
+ redis-master:
310
+ image: redis:7-alpine
311
+ command: redis-server --requirepass data-secret --masterauth data-secret
312
+ ports:
313
+ - '6379:6379'
314
+
315
+ redis-replica:
316
+ image: redis:7-alpine
317
+ command: >
318
+ redis-server
319
+ --requirepass data-secret
320
+ --masterauth data-secret
321
+ --replicaof redis-master 6379
322
+ depends_on:
323
+ - redis-master
324
+
325
+ redis-sentinel:
326
+ image: redis:7-alpine
327
+ command: >
328
+ sh -c "
329
+ echo 'sentinel monitor mymaster redis-master 6379 1' > /sentinel.conf &&
330
+ echo 'sentinel auth-pass mymaster data-secret' >> /sentinel.conf &&
331
+ echo 'sentinel down-after-milliseconds mymaster 3000' >> /sentinel.conf &&
332
+ echo 'sentinel failover-timeout mymaster 10000' >> /sentinel.conf &&
333
+ echo 'requirepass sentinel-secret' >> /sentinel.conf &&
334
+ redis-sentinel /sentinel.conf
335
+ "
336
+ ports:
337
+ - '26379:26379'
338
+ depends_on:
339
+ - redis-master
340
+ - redis-replica
341
+ ```
342
+
343
+ Connect and verify failover:
344
+
345
+ ```typescript
346
+ import { RedisSentinelHelper } from '@venizia/ignis-helpers';
347
+
348
+ const redis = new RedisSentinelHelper({
349
+ name: 'test',
350
+ masterName: 'mymaster',
351
+ sentinels: [{ host: 'localhost', port: 26379 }],
352
+ password: 'data-secret',
353
+ sentinelPassword: 'sentinel-secret',
354
+ onReady: ({ name }) => console.log(`[${name}] ready`),
355
+ });
356
+
357
+ await redis.set({ key: 'hello', value: 'world' });
358
+ const val = await redis.getObject({ key: 'hello' });
359
+ console.log(val); // "world"
360
+
361
+ // Stop the master container - wait ~5 s - the replica is promoted.
362
+ // The same redis instance will reconnect automatically and continue working.
363
+ ```
364
+
365
+ ## Selecting a Topology - the Factory
366
+
367
+ `Source ->` [`packages/helpers/src/modules/redis/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/factory.ts)
368
+
369
+ `createRedisHelper` is an overloaded factory that returns the concrete type when `mode` is a literal.
370
+
371
+ ```typescript
372
+ import { createRedisHelper, RedisModes } from '@venizia/ignis-helpers';
373
+
374
+ // Type inferred as RedisSingleHelper
375
+ const single = createRedisHelper({
376
+ mode: RedisModes.SINGLE,
377
+ name: 'cache',
378
+ host: process.env.REDIS_HOST!,
379
+ port: Number(process.env.REDIS_PORT),
380
+ password: process.env.REDIS_PASSWORD!,
381
+ });
382
+
383
+ // Type inferred as RedisSentinelHelper
384
+ const sentinel = createRedisHelper({
385
+ mode: RedisModes.SENTINEL,
386
+ name: 'cache-ha',
387
+ masterName: 'mymaster',
388
+ sentinels: [{ host: process.env.SENTINEL_HOST! }],
389
+ password: process.env.REDIS_PASSWORD!,
390
+ });
391
+ ```
392
+
393
+ Switch by environment:
394
+
395
+ ```typescript
396
+ import { createRedisHelper, RedisModes, type IRedisHelper } from '@venizia/ignis-helpers';
397
+
398
+ const mode = (process.env.REDIS_MODE ?? RedisModes.SINGLE) as typeof RedisModes.SINGLE;
399
+
400
+ const redis: IRedisHelper = createRedisHelper({
401
+ mode,
402
+ name: 'app-cache',
403
+ host: process.env.REDIS_HOST!,
404
+ port: Number(process.env.REDIS_PORT),
405
+ password: process.env.REDIS_PASSWORD!,
406
+ });
407
+ ```
408
+
409
+ | `mode` value | Returns |
410
+ |---|---|
411
+ | `RedisModes.SINGLE` (`'single'`) | `RedisSingleHelper` |
412
+ | `RedisModes.CLUSTER` (`'cluster'`) | `RedisClusterHelper` |
413
+ | `RedisModes.SENTINEL` (`'sentinel'`) | `RedisSentinelHelper` |
414
+
415
+ Unknown mode throws an `ApplicationError`.
416
+
417
+ ## Lifecycle and Events
418
+
419
+ `Source ->` [`packages/helpers/src/modules/redis/base/abstract.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/base/abstract.helper.ts)
420
+
421
+ ### autoConnect and manual connect
422
+
423
+ When `autoConnect: true` (default), the ioredis client starts connecting immediately in the constructor. When `autoConnect: false`, ioredis uses `lazyConnect` mode and you must call `connect()` explicitly before issuing commands.
424
+
425
+ ```typescript
426
+ // Lazy connect
427
+ const redis = new RedisSingleHelper({
428
+ name: 'cache',
429
+ host: 'localhost',
430
+ port: 6379,
431
+ password: 'secret',
432
+ autoConnect: false,
433
+ });
434
+
435
+ await redis.connect(); // resolves true when status === 'ready'
436
+ ```
437
+
438
+ ### Connection methods
439
+
440
+ | Method | Behavior |
441
+ |--------|----------|
442
+ | `connect()` | Resolves `false` (no-op) if status is `ready`, `reconnecting`, or `connecting`; otherwise calls ioredis `connect()` and resolves `true` when status reaches `ready` |
443
+ | `disconnect()` | Resolves `false` (no-op) if status is `end` or `close`; otherwise sends `QUIT` and resolves `true` on `'OK'` |
444
+ | `ping()` | Sends `PING`; returns `'PONG'` |
445
+
446
+ ### The four lifecycle callbacks
447
+
448
+ All helpers accept `IRedisHelperCallbacks`:
449
+
450
+ | Callback | Fired when | ioredis event |
451
+ |----------|-----------|---------------|
452
+ | `onInitialized` | Synchronously at the end of the constructor | - |
453
+ | `onConnected` | TCP connection is established | `connect` |
454
+ | `onReady` | Client is ready to accept commands | `ready` |
455
+ | `onError` | A connection or command error occurs | `error` |
456
+
457
+ The client also logs a `WARN` internally on `reconnecting` events (not surfaced as a callback).
458
+
459
+ ### duplicateClient
460
+
461
+ ```typescript
462
+ const dedicated = redis.duplicateClient();
463
+ ```
464
+
465
+ - **Creates an independent ioredis connection** from the same configuration.
466
+ - **Implementation.** `AbstractRedisHelper.duplicateClient()` calls ioredis `client.duplicate()`. That copies the parent's `options`, including `lazyConnect`.
467
+ - **Lazy or immediate.** `lazyConnect` is set to `!autoConnect`. With the default `autoConnect: true`, the duplicate connects immediately (`lazyConnect: false`). With `autoConnect: false`, the duplicate is lazy - it connects on first use.
468
+ - **When to use it.** Whenever a consumer needs its own dedicated connection - Pub/Sub, BullMQ, Socket.IO adapters.
469
+ - **Cluster is different.** For `RedisClusterHelper`, `duplicateClient()` builds a new `Cluster` from the same startup nodes and options instead. ioredis `Cluster` has no `.duplicate()`.
470
+
471
+ ## Full Method Reference
472
+
473
+ `Source ->` [`packages/helpers/src/modules/redis/common/interfaces.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/interfaces.ts) (signatures) and [`packages/helpers/src/modules/redis/base/abstract.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/base/abstract.helper.ts) (behavior)
474
+
475
+ ### General notes (apply to all families)
476
+
477
+ - **Empty-input no-ops.** `keys`, `members`, `values`, `fields`, and `payload` are all array inputs. An empty array skips the ioredis call and returns immediately - this avoids the "wrong number of arguments" error ioredis throws on empty varargs.
478
+
479
+ | Return value | Applies to |
480
+ |---|---|
481
+ | `0` | Count methods - `del`, `hDel`, `sRem`, `sAdd`, `lPush`, `rPush` |
482
+ | `[]` | Array reads - `mGet`, `sMembers` |
483
+ | `void` (no-op) | `mSet` |
484
+ - **Boolean mapping.** `expire`, `expireAt`, `persist`, `hExists`, and `sIsMember` map ioredis numeric replies to `true` (`=== 1`) or `false`.
485
+ - **JSON auto-serialization.**
486
+
487
+ | Method group | Behavior |
488
+ |---|---|
489
+ | `set`, `mSet`, `jSet`, `jPush` | `JSON.stringify` before writing |
490
+ | `getObject`, `getObjects`, `jGet` | `JSON.parse` on read |
491
+ | `get`, `mGet` | Return the raw string, unless you pass `transform` |
492
+
493
+ - **TTL units differ by field.**
494
+
495
+ | Field | Unit | ioredis command |
496
+ |---|---|---|
497
+ | `set.options.expiresIn` | milliseconds | `PX` |
498
+ | `expire.seconds` | seconds | `EXPIRE` |
499
+ | `expireAt.atEpochSeconds` | Unix epoch seconds | `EXPIREAT` |
500
+
501
+ - **Pub/Sub errors.** On a subscription callback error, the helper logs the error and returns. It never throws - throwing inside an ioredis async callback would be an unhandled rejection.
502
+
503
+ ### IRedisConnection - connection lifecycle
504
+
505
+ | Method | Signature | Behavior |
506
+ |--------|-----------|----------|
507
+ | `getClient` | `(): TRedisClient` | Returns the raw ioredis `Redis` or `Cluster` instance |
508
+ | `duplicateClient` | `(): TRedisClient` | Creates an independent ioredis connection (same config, separate socket) |
509
+ | `ping` | `(): Promise<string>` | Sends `PING`; returns `'PONG'` on success |
510
+ | `connect` | `(): Promise<boolean>` | Connects the client; resolves `false` if already connected/connecting/ready |
511
+ | `disconnect` | `(): Promise<boolean>` | Graceful `QUIT`; resolves `false` if already ended/closed |
512
+
513
+ ### IRedisKey - key lifecycle and counters
514
+
515
+ | Method | Signature | Behavior |
516
+ |--------|-----------|----------|
517
+ | `exists` | `(opts: { keys: string[] }): Promise<number>` | Returns the count of keys that exist; empty `keys` returns `0` without calling Redis |
518
+ | `expire` | `(opts: { key: string; seconds: number }): Promise<boolean>` | Sets expiry in seconds; `true` if applied |
519
+ | `expireAt` | `(opts: { key: string; atEpochSeconds: number }): Promise<boolean>` | Sets expiry at an epoch second timestamp; `true` if applied |
520
+ | `ttl` | `(opts: { key: string }): Promise<number>` | Returns remaining TTL in seconds; `-1` = no expiry; `-2` = key missing |
521
+ | `persist` | `(opts: { key: string }): Promise<boolean>` | Removes expiry; `true` if the timeout was removed |
522
+ | `incr` | `(opts: { key: string }): Promise<number>` | Atomically increments integer at key by 1; returns new value |
523
+ | `decr` | `(opts: { key: string }): Promise<number>` | Atomically decrements integer at key by 1; returns new value |
524
+ | `incrBy` | `(opts: { key: string; value: number }): Promise<number>` | Atomically increments by `value`; returns new value |
525
+ | `decrBy` | `(opts: { key: string; value: number }): Promise<number>` | Atomically decrements by `value`; returns new value |
526
+
527
+ ### IRedisKeyValue - string key-value
528
+
529
+ | Method | Signature | Behavior |
530
+ |--------|-----------|----------|
531
+ | `set<T>` | `(opts: { key: string; value: T; options?: { log?: boolean; expiresIn?: number } }): Promise<void>` | JSON-serializes `value` and writes it; `expiresIn` is in **milliseconds** (uses `PX`); `log: true` emits an info log |
532
+ | `get<T>` | `(opts: { key: string; transform?: (input: string) => T }): Promise<T \| null>` | Returns raw string or applies `transform`; `null` if key missing |
533
+ | `del` | `(opts: { keys: string[] }): Promise<number>` | Deletes one or more keys; empty `keys` returns `0` |
534
+ | `keys` | `(opts: { key: string }): Promise<string[]>` | Runs Redis `KEYS` with a glob pattern; avoid on large keyspaces - prefer `SCAN` via `execute` |
535
+ | `getString` | `(opts: { key: string }): Promise<string \| null>` | Alias for `get` without transform |
536
+ | `getStrings` | `(opts: { keys: string[] }): Promise<(string \| null)[]>` | Alias for `mGet` without transform |
537
+ | `getObject<T>` | `(opts: { key: string }): Promise<T \| null>` | `get` with `JSON.parse` transform |
538
+ | `getObjects` | `(opts: { keys: string[] }): Promise<(unknown \| null)[]>` | `mGet` with `JSON.parse` transform |
539
+ | `mSet<T>` | `(opts: { payload: Array<{ key: string; value: T }>; options?: { log?: boolean } }): Promise<void>` | Bulk write; each value is JSON-serialized; empty `payload` is a no-op |
540
+ | `mGet<T>` | `(opts: { keys: string[]; transform?: (input: string) => T }): Promise<(T \| null)[]>` | Bulk read with optional transform; empty `keys` returns `[]` |
541
+
542
+ ### IRedisHash - hash maps
543
+
544
+ | Method | Signature | Behavior |
545
+ |--------|-----------|----------|
546
+ | `hSet<T>` | `(opts: { key: string; value: T; options?: { log?: boolean } }): Promise<number>` | Sets all fields in a hash object; returns number of **new** fields added |
547
+ | `hGetAll` | `(opts: { key: string; transform?: <T, R>(input: T) => R }): Promise<unknown>` | Returns all fields and values of a hash; applies optional transform |
548
+ | `hGet` | `(opts: { key: string; field: string }): Promise<string \| null>` | Returns the value of a single hash field |
549
+ | `hDel` | `(opts: { key: string; fields: string[] }): Promise<number>` | Deletes one or more hash fields; empty `fields` returns `0` |
550
+ | `hExists` | `(opts: { key: string; field: string }): Promise<boolean>` | `true` if the field exists in the hash |
551
+ | `hKeys` | `(opts: { key: string }): Promise<string[]>` | Returns all field names in the hash |
552
+ | `hVals` | `(opts: { key: string }): Promise<string[]>` | Returns all field values in the hash |
553
+ | `hIncrBy` | `(opts: { key: string; field: string; value: number }): Promise<number>` | Atomically increments a numeric hash field; returns new value |
554
+ | `hLen` | `(opts: { key: string }): Promise<number>` | Returns the number of fields in the hash |
555
+
556
+ ### IRedisSet - sets
557
+
558
+ | Method | Signature | Behavior |
559
+ |--------|-----------|----------|
560
+ | `sAdd` | `(opts: { key: string; members: Array<string \| number> }): Promise<number>` | Adds members to a set; returns count of new members; empty `members` returns `0` |
561
+ | `sRem` | `(opts: { key: string; members: Array<string \| number> }): Promise<number>` | Removes members from a set; returns count removed; empty `members` returns `0` |
562
+ | `sMembers` | `(opts: { key: string }): Promise<string[]>` | Returns all members of the set |
563
+ | `sIsMember` | `(opts: { key: string; member: string \| number }): Promise<boolean>` | `true` if `member` belongs to the set |
564
+ | `sCard` | `(opts: { key: string }): Promise<number>` | Returns the number of members in the set |
565
+
566
+ ### IRedisList - lists
567
+
568
+ | Method | Signature | Behavior |
569
+ |--------|-----------|----------|
570
+ | `lPush` | `(opts: { key: string; values: Array<string \| number> }): Promise<number>` | Prepends values to the list head; returns list length after push; empty `values` returns `0` |
571
+ | `rPush` | `(opts: { key: string; values: Array<string \| number> }): Promise<number>` | Appends values to the list tail; returns list length after push; empty `values` returns `0` |
572
+ | `lPop` | `(opts: { key: string }): Promise<string \| null>` | Removes and returns the head element; `null` if list is empty |
573
+ | `rPop` | `(opts: { key: string }): Promise<string \| null>` | Removes and returns the tail element; `null` if list is empty |
574
+ | `lRange` | `(opts: { key: string; start: number; stop: number }): Promise<string[]>` | Returns elements from `start` to `stop` (inclusive, 0-indexed, `-1` = last) |
575
+ | `lLen` | `(opts: { key: string }): Promise<number>` | Returns the number of elements in the list |
576
+
577
+ ### IRedisPubSub - publish/subscribe
578
+
579
+ | Method | Signature | Behavior |
580
+ |--------|-----------|----------|
581
+ | `publish<T>` | `(opts: { topics: string[]; payload: T; useCompress?: boolean }): Promise<void>` | JSON-serializes `payload` into a `Buffer`, optionally zlib-deflates it, and publishes to each topic. Filters out empty/blank topics first; if none remain, logs an error and returns without publishing |
582
+ | `subscribe` | `(opts: { topic: string }): void` | Subscribes the client to `topic`; on subscription error the helper **logs** it and does not throw |
583
+ | `unsubscribe` | `(opts: { topic: string }): void` | Unsubscribes from `topic`; on error the helper **logs** it and does not throw |
584
+
585
+ > [!IMPORTANT]
586
+ > A subscribed ioredis connection enters subscriber mode and cannot run regular commands. Use `duplicateClient()` to obtain a separate connection for pub/sub.
587
+
588
+ ```typescript
589
+ // Correct pattern - separate connections for data and pub/sub
590
+ const dataClient = new RedisSingleHelper({ name: 'data', host, port, password });
591
+ const subClient = new RedisSingleHelper({ name: 'sub', host, port, password });
592
+
593
+ subClient.subscribe({ topic: 'events' });
594
+ subClient.getClient().on('message', (channel, message) => {
595
+ const parsed = JSON.parse(message.toString());
596
+ console.log(channel, parsed);
597
+ });
598
+
599
+ await dataClient.set({ key: 'foo', value: 'bar' });
600
+ ```
601
+
602
+ ### IRedisJson - RedisJSON module
603
+
604
+ Requires the [RedisJSON module](https://redis.io/docs/stack/json/) on the server.
605
+
606
+ | Method | Signature | Behavior |
607
+ |--------|-----------|----------|
608
+ | `jSet<T>` | `(opts: { key: string; path: string; value: T }): Promise<string \| null>` | Sets a JSON document at `path` (`JSON.SET`); value is `JSON.stringify`-ed |
609
+ | `jGet<T>` | `(opts: { key: string; path?: string }): Promise<T \| null>` | Retrieves the document or sub-path (`JSON.GET`); `path` defaults to `'$'` |
610
+ | `jDelete` | `(opts: { key: string; path?: string }): Promise<number>` | Deletes the document or sub-path (`JSON.DEL`); `path` defaults to `'$'`; returns count deleted |
611
+ | `jNumberIncreaseBy` | `(opts: { key: string; path: string; value: number }): Promise<string \| null>` | Increments a numeric field by `value` (`JSON.NUMINCRBY`) |
612
+ | `jStringAppend` | `(opts: { key: string; path: string; value: string }): Promise<number[] \| null>` | Appends `value` to a string field (`JSON.STRAPPEND`); returns new string lengths |
613
+ | `jPush<T>` | `(opts: { key: string; path: string; value: T }): Promise<number[] \| null>` | Appends `value` to an array field (`JSON.ARRAPPEND`); value is `JSON.stringify`-ed; returns new array lengths |
614
+ | `jPop<T>` | `(opts: { key: string; path: string }): Promise<T \| null>` | Pops the last element from an array field (`JSON.ARRPOP`) |
615
+
616
+ ### IRedisCommand - raw escape hatch
617
+
618
+ | Method | Signature | Behavior |
619
+ |--------|-----------|----------|
620
+ | `execute<R>` | `(command: string, parameters?: Array<string \| number \| Buffer>): Promise<R>` | Calls any ioredis command directly via `client.call(command, parameters)` |
621
+
622
+ ```typescript
623
+ // Use SCAN instead of KEYS on large keyspaces
624
+ const [cursor, keys] = await redis.execute<[string, string[]]>(
625
+ 'SCAN',
626
+ [0, 'MATCH', 'user:*', 'COUNT', 100],
627
+ );
628
+
629
+ // Fetch server info
630
+ const info = await redis.execute<string>('INFO');
631
+ ```
632
+
633
+ ## Using the Helper Across IGNIS
634
+
635
+ All three topologies implement `IRedisHelper` and are interchangeable in every integration below. Switch topology by changing the helper you construct. The consumer code does not change.
636
+
637
+ ### BullMQ (BullMQHelper)
638
+
639
+ `BullMQHelper` accepts a `redisConnection: IRedisHelper`. Internally it:
640
+ - Calls `redisConnection.duplicateClient()` to create dedicated `Queue` and `Worker` connections. BullMQ requires a separate connection per role.
641
+ - Calls `redisConnection.getClient() instanceof Cluster` to detect cluster topology and adjust BullMQ configuration.
642
+
643
+ `RedisSingleHelper` and `RedisSentinelHelper` are BullMQ-compatible out of the box. The framework sets `maxRetriesPerRequest: null` in `buildDefaultOpts`.
644
+
645
+ ```typescript
646
+ import { BullMQHelper } from '@venizia/ignis-helpers/bullmq';
647
+ import { RedisSingleHelper } from '@venizia/ignis-helpers';
648
+
649
+ const redis = new RedisSingleHelper({ name: 'queue-redis', host, port, password });
650
+
651
+ const queue = BullMQHelper.newInstance({
652
+ queueName: 'email',
653
+ role: 'queue',
654
+ redisConnection: redis,
655
+ });
656
+ ```
657
+
658
+ ### Socket.IO component (SocketIOServerHelper)
659
+
660
+ - **Resolved from a binding key.** The Socket.IO component resolves an `AbstractRedisHelper` from `@app/socket-io/redis-connection`.
661
+ - **Three dedicated connections.** `SocketIOServerHelper` calls `duplicateClient()` three times - once each for the pub channel, sub channel, and emitter adapter.
662
+ - **Any topology works.** Bind single, cluster, or Sentinel - the consumer code is identical:
663
+
664
+ ```typescript
665
+ import { AbstractRedisHelper, RedisSentinelHelper } from '@venizia/ignis-helpers';
666
+
667
+ // In your Application preConfigure:
668
+ const redis = new RedisSentinelHelper({ name: 'socket-redis', masterName: 'mymaster', sentinels, password });
669
+ this.bind<AbstractRedisHelper>({ key: '@app/socket-io/redis-connection' }).to(redis);
670
+ ```
671
+
672
+ See the Socket.IO component documentation for the full component registration and adapter setup.
673
+
674
+ ### WebSocket component (WebSocket server and emitter)
675
+
676
+ - **Resolved from a binding key.** The WebSocket component resolves an `AbstractRedisHelper` from `@app/websocket/redis-connection`.
677
+ - **Two dedicated connections.** `WebSocketServerHelper` calls `duplicateClient()` for its pub and sub connections. The WebSocket emitter calls `duplicateClient()` again for its own pub connection.
678
+ - **Any topology works.** Bind single, cluster, or Sentinel:
679
+
680
+ ```typescript
681
+ import { AbstractRedisHelper, RedisClusterHelper } from '@venizia/ignis-helpers';
682
+
683
+ const redis = new RedisClusterHelper({ name: 'ws-redis', nodes });
684
+ this.bind<AbstractRedisHelper>({ key: '@app/websocket/redis-connection' }).to(redis);
685
+ ```
686
+
687
+ See the WebSocket component documentation for the full component registration.
688
+
689
+ ### Casbin authorization enforcer
690
+
691
+ - **Configured via options.** The cached Casbin enforcer (`ICasbinEnforcerCachedRedis`) accepts `connection: IRedisHelper` inside its `options`.
692
+ - **Uses only the typed API.** It calls the helper's `get`, `set` (with `options.expiresIn`), and `del` to read, write, and invalidate cached policy keys. It never reaches the raw client.
693
+ - **Any topology works** as the `connection`:
694
+
695
+ ```typescript
696
+ import type { IRedisHelper } from '@venizia/ignis-helpers';
697
+
698
+ // Shape of the cached-enforcer config (see the Authorization component docs for full setup):
699
+ const cached = {
700
+ use: true,
701
+ driver: CasbinEnforcerCachedDrivers.REDIS,
702
+ options: {
703
+ connection: redis as IRedisHelper, // any RedisSingleHelper / RedisClusterHelper / RedisSentinelHelper
704
+ expiresIn: 300_000, // cached-policy TTL in milliseconds
705
+ keyFn: ({ user }) => `authz:policy:${user.id}`,
706
+ },
707
+ };
708
+ ```
709
+
710
+ ### Binding into an application
711
+
712
+ ```typescript
713
+ import { RedisSingleHelper } from '@venizia/ignis-helpers';
714
+ import { BaseApplication } from '@venizia/ignis';
715
+
716
+ class MyApp extends BaseApplication {
717
+ async preConfigure() {
718
+ const redis = new RedisSingleHelper({
719
+ name: 'cache',
720
+ host: process.env.REDIS_HOST!,
721
+ port: Number(process.env.REDIS_PORT),
722
+ password: process.env.REDIS_PASSWORD!,
723
+ });
724
+
725
+ // Bind so components and services can resolve it
726
+ this.bind<RedisSingleHelper>({ key: 'helpers.RedisCache' }).to(redis);
727
+ }
728
+ }
729
+ ```
730
+
731
+ ## Production Notes
732
+
733
+ - **Use a singleton per logical connection.** Construct the helper once at startup and share it. Do not construct a new helper per request - each construction opens a new connection pool.
734
+ - **`duplicateClient()` for Pub/Sub.** A subscribed ioredis connection cannot run normal commands. Always call `duplicateClient()` to get a dedicated subscriber connection. Keep the main client for data operations.
735
+ - **Sentinel for HA; Cluster for sharding.** These are different problems. Sentinel gives you automatic failover for a single logical master. Cluster shards data across nodes. They are not interchangeable.
736
+ - **TTL units differ by field** - see the [units table above](#general-notes-apply-to-all-families). Mixing them up causes unexpected key expirations.
737
+ - **Sentinel `sentinel auth-pass` is a server-side requirement.** If your data nodes require a password, configure `sentinel auth-pass <group> <password>` in `sentinel.conf`, in addition to the app-side `password` field. Missing it causes false failovers, even when `password` is correct.
738
+ - **Avoid `keys()` in production.** The Redis `KEYS` command scans the entire keyspace and blocks the server. Use `execute('SCAN', [...])` for production pattern matching.
739
+
740
+ ## Import Reference
741
+
742
+ ```typescript
743
+ import {
744
+ // Classes
745
+ AbstractRedisHelper,
746
+ RedisSingleHelper,
747
+ RedisClusterHelper,
748
+ RedisSentinelHelper,
749
+ // Factory
750
+ createRedisHelper,
751
+ RedisModes,
752
+ // Sentinel role const-class
753
+ RedisSentinelRoles,
754
+ } from '@venizia/ignis-helpers';
755
+
756
+ import type {
757
+ // Interface
758
+ IRedisHelper,
759
+ // Option types
760
+ IRedisSingleHelperOptions,
761
+ IRedisSingleHelperProps,
762
+ IRedisClusterHelperOptions,
763
+ IRedisClusterHelperProps,
764
+ IRedisSentinelHelperOptions,
765
+ IRedisSentinelHelperProps,
766
+ IRedisHelperCallbacks,
767
+ // Client + enum types
768
+ TRedisClient,
769
+ TRedisMode,
770
+ TRedisSentinelRole,
771
+ } from '@venizia/ignis-helpers';
772
+ ```
773
+
774
+ ## See also
775
+
776
+ - [Redis overview](/extensions/helpers/redis/) - introduction and the most common tasks
777
+ - [Queue Helper](/extensions/helpers/queue/) - `BullMQHelper` uses the Redis helper as its connection backend
778
+ - [Socket.IO Component](/extensions/components/socket-io/) - uses `duplicateClient` for pub/sub adapter
779
+ - [WebSocket Component](/extensions/components/websocket/) - uses `duplicateClient` for pub/sub channels
780
+ - [Authorization Component](/extensions/components/authorization/) - Casbin enforcer uses the helper's `del`/`get`/`set` for cache operations
781
+ - [Migrating to the new Redis Helper API](/guides/migrations/redis-helpers-migration) - renames and breaking changes from the pre-refactor API
782
+ - [ioredis documentation](https://github.com/redis/ioredis) - underlying Redis client
783
+ - [Redis Sentinel documentation](https://redis.io/docs/management/sentinel/) - Sentinel architecture reference
784
+ - [RedisJSON documentation](https://redis.io/docs/stack/json/) - required for `j*` methods