@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
@@ -1,59 +1,16 @@
1
- # Redis
2
-
3
- The Redis helper module provides a unified data API across three deployment topologies - single node, cluster, and Sentinel - via the `AbstractRedisHelper` base class. All three implement the same `IRedisHelper` interface, so a consumer written against `IRedisHelper` works with any topology and can be switched without changing application code.
4
-
5
- Backed by **ioredis** under the hood.
6
-
7
- ## 1. Overview
8
-
9
- ### When to use which topology
10
-
11
- | Topology | Class | Use when |
12
- |----------|-------|----------|
13
- | **Single** | `RedisSingleHelper` | One Redis node - development, simple production setups |
14
- | **Cluster** | `RedisClusterHelper` | Sharded data across multiple nodes - horizontal scale or large datasets |
15
- | **Sentinel** | `RedisSentinelHelper` | One logical master + replicas with automatic failover - high availability |
16
-
17
- All three share the full data API defined in `IRedisHelper` (`get`, `set`, `hSet`, `publish`, etc.). Only the constructor options differ.
18
-
19
- ## 2. Class and Interface Model
20
-
21
- ### Class hierarchy
22
-
23
- | Class | Extends | Notes |
24
- |-------|---------|-------|
25
- | `AbstractRedisHelper` | `BaseHelper` | Base class; implements `IRedisHelper`; never constructed directly |
26
- | `RedisSingleHelper` | `AbstractRedisHelper` | ioredis `Redis` client; returns `Redis` from `getClient()` |
27
- | `RedisClusterHelper` | `AbstractRedisHelper` | ioredis `Cluster` client; returns `Cluster` from `getClient()` |
28
- | `RedisSentinelHelper` | `AbstractRedisHelper` | ioredis `Redis` client (Sentinel mode); returns `Redis` from `getClient()` |
29
-
30
- > **Typing rule:** Declare parameters and bindings as `IRedisHelper`. Use `instanceof AbstractRedisHelper` for runtime topology checks. Never type-check against the concrete subclasses unless you need topology-specific behavior.
31
-
32
- ### TRedisClient
33
-
34
- ```typescript
35
- type TRedisClient = Redis | Cluster; // ioredis
36
- ```
37
-
38
- `RedisSingleHelper` and `RedisSentinelHelper` return a `Redis` instance. `RedisClusterHelper` returns a `Cluster` instance. Both satisfy `TRedisClient`.
1
+ ---
2
+ title: Redis
3
+ description: A single data API - get/set, hashes, sets, lists, pub/sub, RedisJSON - across single-node, cluster, and Sentinel topologies
4
+ difficulty: intermediate
5
+ ---
39
6
 
40
- ### IRedisHelper and its nine capability interfaces
7
+ # Redis
41
8
 
42
- `IRedisHelper` extends all nine of the following:
9
+ The Redis helper gives you one data API for Redis. Talk to a single node, a sharded cluster, or a Sentinel-managed high-availability pair - the calls you write stay the same.
43
10
 
44
- | Interface | Responsibility |
45
- |-----------|----------------|
46
- | `IRedisConnection` | Connection lifecycle - `connect`, `disconnect`, `ping`, `getClient`, `duplicateClient` |
47
- | `IRedisKey` | Key TTL and counters - `exists`, `expire`, `expireAt`, `ttl`, `persist`, `incr`/`decr` variants |
48
- | `IRedisKeyValue` | String key-value - `get`, `set`, `del`, `keys`, `getString(s)`, `getObject(s)`, `mSet`, `mGet` |
49
- | `IRedisHash` | Hash maps - `hSet`, `hGetAll`, `hGet`, `hDel`, `hExists`, `hKeys`, `hVals`, `hIncrBy`, `hLen` |
50
- | `IRedisSet` | Sets - `sAdd`, `sRem`, `sMembers`, `sIsMember`, `sCard` |
51
- | `IRedisList` | Lists - `lPush`, `rPush`, `lPop`, `rPop`, `lRange`, `lLen` |
52
- | `IRedisPubSub` | Pub/Sub with optional zlib compression - `publish`, `subscribe`, `unsubscribe` |
53
- | `IRedisJson` | RedisJSON module operations - `jSet`, `jGet`, `jDelete`, `jNumberIncreaseBy`, `jStringAppend`, `jPush`, `jPop` |
54
- | `IRedisCommand` | Raw escape hatch - `execute(command, params?)` |
11
+ ## In one example
55
12
 
56
- ## 3. Construction - Single
13
+ Construct a single-node helper and round-trip a value through it.
57
14
 
58
15
  ```typescript
59
16
  import { RedisSingleHelper } from '@venizia/ignis-helpers';
@@ -63,284 +20,64 @@ const redis = new RedisSingleHelper({
63
20
  host: 'localhost',
64
21
  port: 6379,
65
22
  password: 'secret',
66
- database: 0,
67
- autoConnect: true,
68
- maxRetry: 5,
69
-
70
- onInitialized: ({ name, helper }) => {
71
- console.log(`[${name}] initialized`);
72
- },
73
- onConnected: ({ name }) => {
74
- console.log(`[${name}] connected`);
75
- },
76
- onReady: ({ name }) => {
77
- console.log(`[${name}] ready`);
78
- },
79
- onError: ({ name, error }) => {
80
- console.error(`[${name}] error`, error);
81
- },
82
23
  });
83
- ```
84
-
85
- ### IRedisSingleHelperOptions
86
-
87
- Combines `IRedisSingleHelperProps` and `IRedisHelperCallbacks`.
88
-
89
- | Field | Type | Required | Default | Description |
90
- |-------|------|----------|---------|-------------|
91
- | `name` | `string` | Yes | - | Helper identifier used in logs and as `IRedisHelper.name` |
92
- | `host` | `string` | Yes | - | Redis server hostname |
93
- | `port` | `string \| number` | Yes | - | Redis server port |
94
- | `password` | `string` | Yes | - | Redis `requirepass` value |
95
- | `user` | `string` | No | - | Redis username (ACL authentication) |
96
- | `database` | `number` | No | `0` | Redis database index (0-15) |
97
- | `autoConnect` | `boolean` | No | `true` | Connect immediately; `false` uses ioredis `lazyConnect` |
98
- | `maxRetry` | `number` | No | `0` | Max reconnect attempts; `0` = unlimited; `-1` = no retry |
99
- | `onInitialized` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired synchronously after construction |
100
- | `onConnected` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired on TCP connection established |
101
- | `onReady` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired when client is ready for commands |
102
- | `onError` | `(opts: { name: string; helper: IRedisHelper; error: unknown }) => void` | No | - | Fired on connection or command errors |
103
-
104
- ### Retry strategy
105
24
 
106
- Backoff formula: `Math.max(Math.min(attempt * 2000, 5000), 1000)` - starting at 1 s, capped at 5 s. Reconnect stops when `attempt > maxRetry` (if `maxRetry > -1`). The framework always sets `maxRetriesPerRequest: null` internally, which is required for BullMQ compatibility.
25
+ await redis.set({ key: 'session:42', value: { userId: 42, active: true } });
107
26
 
108
- ## 4. Construction - Cluster
109
-
110
- ```typescript
111
- import { RedisClusterHelper } from '@venizia/ignis-helpers';
112
-
113
- const cluster = new RedisClusterHelper({
114
- name: 'cache-cluster',
115
- nodes: [
116
- { host: 'redis-node-1', port: 7000 },
117
- { host: 'redis-node-2', port: 7001 },
118
- { host: 'redis-node-3', port: 7002 },
119
- ],
120
- clusterOptions: {
121
- redisOptions: { password: 'cluster-password' },
122
- },
123
- onReady: ({ name }) => {
124
- console.log(`[${name}] cluster ready`);
125
- },
27
+ const session = await redis.getObject<{ userId: number; active: boolean }>({
28
+ key: 'session:42',
126
29
  });
30
+ console.log(session); // { userId: 42, active: true }
127
31
  ```
128
32
 
129
- ### IRedisClusterHelperOptions
130
-
131
- | Field | Type | Required | Default | Description |
132
- |-------|------|----------|---------|-------------|
133
- | `name` | `string` | Yes | - | Helper identifier |
134
- | `nodes` | `Array<{ host: string; port: string \| number; password?: string }>` | Yes | - | Startup nodes - ioredis discovers the rest |
135
- | `clusterOptions` | `ClusterOptions` | No | - | Passed verbatim to `new Cluster(nodes, clusterOptions)` |
136
- | `onInitialized` / `onConnected` / `onReady` / `onError` | callbacks | No | - | Same shape as single |
137
-
138
- > **No framework defaults injected.** 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.
139
-
140
- `duplicateClient()` on a cluster creates a **new** `Cluster` instance from the same startup nodes and options (because ioredis `Cluster` does not implement `.duplicate()`).
141
-
142
- ## 5. Construction - Sentinel
143
-
144
- ### What Redis Sentinel is
145
-
146
- Redis Sentinel is a high-availability (HA) architecture made of three process types:
147
-
148
- ```
149
- +------------------+ monitors +--------------------+
150
- | Sentinel (1) |<------------------>| Master (primary) |
151
- +------------------+ +--------------------+
152
- +------------------+ replication |
153
- | Sentinel (2) | +--------------+
154
- +------------------+ v
155
- +------------------+ +--------------------+
156
- | Sentinel (3) | | Replica (standby) |
157
- +------------------+ +--------------------+
158
- ```
159
-
160
- - **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).
161
- - **Master** accepts all writes.
162
- - **Replicas** replicate from master and serve reads when `role: 'slave'` is set.
163
-
164
- ### Why your app connects to sentinels, not the master
165
-
166
- The master's address can change after a failover. Instead of hard-coding a master host, you give ioredis the sentinel addresses and the monitored master group name (`masterName`). ioredis asks a sentinel for the current master, connects to it, and re-queries sentinels automatically after a failover. **Your application code does not change during a failover** - the only observable effect is a few seconds of transient command failures, which the retry strategy handles.
33
+ `autoConnect` defaults to `true`, so the client starts connecting inside the constructor - no explicit `connect()` call needed for this example.
167
34
 
168
- ### Constructor
35
+ ## How it works
169
36
 
170
- ```typescript
171
- import { RedisSentinelHelper } from '@venizia/ignis-helpers';
172
-
173
- const redis = new RedisSentinelHelper({
174
- name: 'ha-cache', // helper identifier (logging/scope)
175
- masterName: 'mymaster', // group name in sentinel.conf "sentinel monitor mymaster ..."
176
- sentinels: [
177
- { host: '10.0.0.1', port: 26379 },
178
- { host: '10.0.0.2', port: 26379 },
179
- { host: '10.0.0.3' }, // port defaults to 26379
180
- ],
181
- role: 'master', // 'master' (default) or 'slave'
182
- password: 'data-secret', // master/replica requirepass
183
- sentinelPassword: 'sentinel-secret', // sentinel.conf requirepass
184
- database: 0,
185
- autoConnect: true,
186
- maxRetry: 5,
187
- });
188
- ```
37
+ - **Three topologies, one interface.** `RedisSingleHelper`, `RedisClusterHelper`, and `RedisSentinelHelper` all extend `AbstractRedisHelper` and implement `IRedisHelper`. Only the constructor options differ.
38
+ - **The data API never changes.** `get`, `hSet`, `publish`, and every other method are identical across all three. Code written against `IRedisHelper` works unchanged, whichever topology backs it.
189
39
 
190
- ### IRedisSentinelHelperOptions
191
-
192
- | Field | Type | Required | Default | Description |
193
- |-------|------|----------|---------|-------------|
194
- | `name` | `string` | Yes | - | Helper identifier (logs, `IRedisHelper.name`) |
195
- | `masterName` | `string` | Yes | - | Sentinel-monitored master group name - maps to ioredis `name` |
196
- | `sentinels` | `Array<{ host: string; port?: string \| number }>` | Yes | - | Sentinel process addresses |
197
- | `role` | `TRedisSentinelRole` (`RedisSentinelRoles.MASTER \| SLAVE`) | No | `RedisSentinelRoles.MASTER` | Connect to master (writes) or a replica (reads) |
198
- | `password` | `string` | No | - | Data-node `requirepass` (master and replicas) |
199
- | `sentinelPassword` | `string` | No | - | Sentinel-process `requirepass` |
200
- | `sentinelUsername` | `string` | No | - | Sentinel ACL username |
201
- | `database` | `number` | No | `0` | Redis database index |
202
- | `autoConnect` | `boolean` | No | `true` | Connect immediately |
203
- | `maxRetry` | `number` | No | `0` | Max reconnect attempts |
204
- | `redisOptions` | `Partial<RedisOptions>` | No | - | Extra ioredis options; first-class fields above always override matching keys here |
205
- | `onInitialized` / `onConnected` / `onReady` / `onError` | callbacks | No | - | Same shape as single |
206
-
207
- ### Field clarity - name vs masterName
208
-
209
- This is the most common source of confusion:
210
-
211
- | Field | What it is | Where it goes |
212
- |-------|-----------|---------------|
213
- | `name` | The helper's own identifier used for logging and `IRedisHelper.name` | `AbstractRedisHelper` identifier |
214
- | `masterName` | The master group being monitored by Sentinel processes, matching `sentinel monitor <name> ...` in sentinel.conf | Maps to ioredis `name` option |
215
-
216
- They are independent. You can name the helper `'ha-cache'` while `masterName` is `'mymaster'`.
217
-
218
- ### The authentication model - 4 relationships
219
-
220
- Sentinel deployments have four separate authentication relationships. The app only configures two of them; the other two are server-side.
221
-
222
- | # | From | To | App field | Server config |
223
- |---|------|----|-----------|---------------|
224
- | 1 | **App** | **Sentinel processes** | `sentinelPassword` | `requirepass` in sentinel.conf |
225
- | 2 | **App** | **Data nodes** (master/replica) | `password` | `requirepass` in redis.conf |
226
- | 3 | **Sentinel** | **Data nodes** | (not set by app) | `sentinel auth-pass <group> <pass>` in sentinel.conf |
227
- | 4 | **Replica** | **Master** (replication) | (not set by app) | `masterauth` in redis.conf |
228
-
229
- **Relationship 3 is the silent failure.** If the data nodes require a password but sentinel.conf does not have `sentinel auth-pass`, the sentinel processes cannot check master health and will trigger false failovers. Configure it on the server side even when your app only sets `password`.
230
-
231
- **Common cases:**
232
- - Private network, sentinels unauthenticated: set only `password`.
233
- - Fully secured cluster: set both `password` and `sentinelPassword`; configure relationships 3 and 4 on the servers.
234
-
235
- ### Failover behavior
236
-
237
- When the master fails:
238
-
239
- 1. Sentinels reach quorum and promote a replica.
240
- 2. ioredis detects the master change via sentinel notification.
241
- 3. ioredis reconnects to the new master transparently.
242
- 4. Any in-flight commands during the few-second window may fail transiently - the retry strategy retries them.
243
- 5. **Your application code does not change.** The same `RedisSentinelHelper` instance continues to work after failover.
244
-
245
- ### Local testing with Docker Compose
246
-
247
- The following Compose file starts a master, one replica, and one sentinel with full authentication. It demonstrates all four relationships.
248
-
249
- ```yaml
250
- version: '3.8'
251
-
252
- services:
253
- redis-master:
254
- image: redis:7-alpine
255
- command: redis-server --requirepass data-secret --masterauth data-secret
256
- ports:
257
- - '6379:6379'
258
-
259
- redis-replica:
260
- image: redis:7-alpine
261
- command: >
262
- redis-server
263
- --requirepass data-secret
264
- --masterauth data-secret
265
- --replicaof redis-master 6379
266
- depends_on:
267
- - redis-master
268
-
269
- redis-sentinel:
270
- image: redis:7-alpine
271
- command: >
272
- sh -c "
273
- echo 'sentinel monitor mymaster redis-master 6379 1' > /sentinel.conf &&
274
- echo 'sentinel auth-pass mymaster data-secret' >> /sentinel.conf &&
275
- echo 'sentinel down-after-milliseconds mymaster 3000' >> /sentinel.conf &&
276
- echo 'sentinel failover-timeout mymaster 10000' >> /sentinel.conf &&
277
- echo 'requirepass sentinel-secret' >> /sentinel.conf &&
278
- redis-sentinel /sentinel.conf
279
- "
280
- ports:
281
- - '26379:26379'
282
- depends_on:
283
- - redis-master
284
- - redis-replica
285
- ```
40
+ | Topology | Class | Fits |
41
+ |----------|-------|------|
42
+ | Single node | `RedisSingleHelper` | One Redis instance |
43
+ | Cluster | `RedisClusterHelper` | Data sharded across nodes |
44
+ | Sentinel | `RedisSentinelHelper` | Automatic failover for one logical master |
286
45
 
287
- Connect and verify failover:
46
+ - **Pick a topology via the factory.** `createRedisHelper({ mode })` picks a topology from configuration. Pass a literal `mode` (`RedisModes.SINGLE | CLUSTER | SENTINEL`) and it returns the concrete class, not the generic `IRedisHelper`.
47
+ - **Connection lifecycle is automatic.** With `autoConnect: true` (the default), the ioredis client starts connecting inside the constructor. Set `autoConnect: false` and call `connect()` yourself instead.
48
+ - **Reconnect backoff grows with each attempt:** 2 s, then 4 s, then 5 s for every attempt after that. A successful reconnect resets it.
49
+ - **The default does not reconnect at all.** `maxRetry` defaults to `0`, which gives up after the first failed attempt. Pass `-1` to reconnect forever.
288
50
 
289
- ```typescript
290
- import { RedisSentinelHelper } from '@venizia/ignis-helpers';
291
-
292
- const redis = new RedisSentinelHelper({
293
- name: 'test',
294
- masterName: 'mymaster',
295
- sentinels: [{ host: 'localhost', port: 26379 }],
296
- password: 'data-secret',
297
- sentinelPassword: 'sentinel-secret',
298
- onReady: ({ name }) => console.log(`[${name}] ready`),
299
- });
51
+ | `maxRetry` | Behavior |
52
+ |---|---|
53
+ | `0` (default) | No reconnect - the client gives up after the first failed attempt |
54
+ | `-1` | Reconnect forever |
55
+ | A positive number | Stop reconnecting after that many attempts |
300
56
 
301
- await redis.set({ key: 'hello', value: 'world' });
302
- const val = await redis.get({ key: 'hello' });
303
- console.log(val); // "world"
57
+ - **BullMQ compatibility differs by topology.** Single and Sentinel helpers always set `maxRetriesPerRequest: null`, which is what BullMQ requires. Cluster does not get this automatically - see the [Full reference](/extensions/helpers/redis/reference).
58
+ - **Values are JSON-serialized automatically.**
304
59
 
305
- // Stop the master container - wait ~5 s - the replica is promoted.
306
- // The same redis instance will reconnect automatically and continue working.
307
- ```
60
+ | Method group | Behavior |
61
+ |---|---|
62
+ | `set`, `mSet`, `jSet`, `jPush` | `JSON.stringify` before writing |
63
+ | `getObject`, `getObjects`, `jGet` | `JSON.parse` on read |
64
+ | `get`, `mGet` | Return the raw stored string, unless you pass `transform` |
308
65
 
309
- ## 6. Selecting a Topology - the Factory
66
+ Wrote a value with `set`? Read it back with `getObject`/`getObjects`.
310
67
 
311
- `createRedisHelper` is an overloaded factory that returns the concrete type when `mode` is a literal.
68
+ - **Every method is camelCase.** `hSet`, `lPush`, `sAdd` - even where the underlying ioredis/Redis command is lowercase (`hset`, `lpush`, `sadd`). There is no lowercase alias.
312
69
 
313
- ```typescript
314
- import { createRedisHelper, RedisModes } from '@venizia/ignis-helpers';
70
+ ## Common tasks
315
71
 
316
- // Type inferred as RedisSingleHelper
317
- const single = createRedisHelper({
318
- mode: RedisModes.SINGLE,
319
- name: 'cache',
320
- host: process.env.REDIS_HOST!,
321
- port: Number(process.env.REDIS_PORT),
322
- password: process.env.REDIS_PASSWORD!,
323
- });
324
-
325
- // Type inferred as RedisSentinelHelper
326
- const sentinel = createRedisHelper({
327
- mode: RedisModes.SENTINEL,
328
- name: 'cache-ha',
329
- masterName: 'mymaster',
330
- sentinels: [{ host: process.env.SENTINEL_HOST! }],
331
- password: process.env.REDIS_PASSWORD!,
332
- });
333
- ```
72
+ ### Pick a topology
334
73
 
335
- Switch by environment:
74
+ Switch topology by changing `mode` and its matching options. The rest of your code does not change.
336
75
 
337
76
  ```typescript
338
77
  import { createRedisHelper, RedisModes, type IRedisHelper } from '@venizia/ignis-helpers';
339
78
 
340
- const mode = (process.env.REDIS_MODE ?? RedisModes.SINGLE) as typeof RedisModes.SINGLE;
341
-
342
79
  const redis: IRedisHelper = createRedisHelper({
343
- mode,
80
+ mode: RedisModes.SINGLE, // or RedisModes.CLUSTER / RedisModes.SENTINEL
344
81
  name: 'app-cache',
345
82
  host: process.env.REDIS_HOST!,
346
83
  port: Number(process.env.REDIS_PORT),
@@ -348,366 +85,75 @@ const redis: IRedisHelper = createRedisHelper({
348
85
  });
349
86
  ```
350
87
 
351
- | `mode` value | Returns |
352
- |---|---|
353
- | `RedisModes.SINGLE` (`'single'`) | `RedisSingleHelper` |
354
- | `RedisModes.CLUSTER` (`'cluster'`) | `RedisClusterHelper` |
355
- | `RedisModes.SENTINEL` (`'sentinel'`) | `RedisSentinelHelper` |
356
-
357
- Unknown mode throws an `ApplicationError`.
88
+ See [Full reference](/extensions/helpers/redis/reference) for the cluster (`nodes`) and Sentinel (`sentinels`, `masterName`) constructor shapes.
358
89
 
359
- ## 7. Lifecycle and Events
90
+ ### Cache a value with a TTL
360
91
 
361
- ### autoConnect and manual connect
362
-
363
- 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.
92
+ `options.expiresIn` on `set` is in **milliseconds**. `ttl()` reports remaining time in **seconds**. Mixing the two up is the most common bug with this helper.
364
93
 
365
94
  ```typescript
366
- // Lazy connect
367
- const redis = new RedisSingleHelper({
368
- name: 'cache',
369
- host: 'localhost',
370
- port: 6379,
371
- password: 'secret',
372
- autoConnect: false,
95
+ await redis.set({
96
+ key: 'session:42',
97
+ value: { userId: 42 },
98
+ options: { expiresIn: 60_000 }, // 60 seconds
373
99
  });
374
100
 
375
- await redis.connect(); // resolves true when status === 'ready'
101
+ const remaining = await redis.ttl({ key: 'session:42' }); // seconds; -1 = no expiry, -2 = missing
376
102
  ```
377
103
 
378
- ### Connection methods
379
-
380
- | Method | Behavior |
381
- |--------|----------|
382
- | `connect()` | Resolves `false` (no-op) if status is `ready`, `reconnecting`, or `connecting`; otherwise calls ioredis `connect()` and resolves `true` when status reaches `ready` |
383
- | `disconnect()` | Resolves `false` (no-op) if status is `end` or `close`; otherwise sends `QUIT` and resolves `true` on `'OK'` |
384
- | `ping()` | Sends `PING`; returns `'PONG'` |
104
+ ### Publish and subscribe
385
105
 
386
- ### The four lifecycle callbacks
387
-
388
- All helpers accept `IRedisHelperCallbacks`:
389
-
390
- | Callback | Fired when | ioredis event |
391
- |----------|-----------|---------------|
392
- | `onInitialized` | Synchronously at the end of the constructor | - |
393
- | `onConnected` | TCP connection is established | `connect` |
394
- | `onReady` | Client is ready to accept commands | `ready` |
395
- | `onError` | A connection or command error occurs | `error` |
396
-
397
- The client also logs a `WARN` internally on `reconnecting` events (not surfaced as a callback).
398
-
399
- ### duplicateClient
106
+ A subscribed ioredis connection cannot run regular commands. Use a second helper instance for subscribing, and keep the first for data operations.
400
107
 
401
108
  ```typescript
402
- const dedicated = redis.duplicateClient();
403
- ```
404
-
405
- Creates an **independent** ioredis connection from the same configuration. The duplicate starts disconnected - ioredis auto-connects it on first use. Use this whenever a consumer needs its own dedicated connection (Pub/Sub, BullMQ, Socket.IO adapters). For `RedisClusterHelper`, `duplicateClient()` constructs a new `Cluster` from the same startup nodes and options.
406
-
407
- ## 8. Full Method Reference
408
-
409
- ### General notes (apply to all families)
410
-
411
- - **Empty-input no-ops:** Methods that accept an array (`keys`, `members`, `values`, `fields`, `payload`) return `0` or `[]` immediately when the array is empty, without calling ioredis. This prevents the "wrong number of arguments" error ioredis throws on empty varargs.
412
- - **Boolean mapping:** `expire`, `expireAt`, `persist`, `hExists`, `sIsMember` map ioredis numeric replies to `true` (`=== 1`) / `false`.
413
- - **JSON auto-serialization:** `set`, `mSet`, `jSet`, `jPush` call `JSON.stringify` before writing. `getObject`, `getObjects`, `jGet` parse on read. `get` and `mGet` return raw strings unless you pass `transform`.
414
- - **TTL units:** `set.options.expiresIn` is **milliseconds** (ioredis `PX`). `expire.seconds` is **seconds** (ioredis `EXPIRE`). `expireAt.atEpochSeconds` is a Unix epoch **in seconds** (ioredis `EXPIREAT`).
415
- - **Pub/Sub errors:** On subscription callback error, the helper **logs** the error and returns without throwing, because throwing inside an ioredis async callback would be an unhandled rejection.
109
+ const subscriber = new RedisSingleHelper({ name: 'sub', host: 'localhost', port: 6379, password: 'secret' });
416
110
 
417
- ---
418
-
419
- ### IRedisConnection - connection lifecycle
420
-
421
- | Method | Signature | Behavior |
422
- |--------|-----------|----------|
423
- | `getClient` | `(): TRedisClient` | Returns the raw ioredis `Redis` or `Cluster` instance |
424
- | `duplicateClient` | `(): TRedisClient` | Creates an independent ioredis connection (same config, separate socket) |
425
- | `ping` | `(): Promise<string>` | Sends `PING`; returns `'PONG'` on success |
426
- | `connect` | `(): Promise<boolean>` | Connects the client; resolves `false` if already connected/connecting/ready |
427
- | `disconnect` | `(): Promise<boolean>` | Graceful `QUIT`; resolves `false` if already ended/closed |
428
-
429
- ---
430
-
431
- ### IRedisKey - key lifecycle and counters
432
-
433
- | Method | Signature | Behavior |
434
- |--------|-----------|----------|
435
- | `exists` | `(opts: { keys: string[] }): Promise<number>` | Returns the count of keys that exist; empty `keys` returns `0` without calling Redis |
436
- | `expire` | `(opts: { key: string; seconds: number }): Promise<boolean>` | Sets expiry in seconds; `true` if applied |
437
- | `expireAt` | `(opts: { key: string; atEpochSeconds: number }): Promise<boolean>` | Sets expiry at an epoch second timestamp; `true` if applied |
438
- | `ttl` | `(opts: { key: string }): Promise<number>` | Returns remaining TTL in seconds; `-1` = no expiry; `-2` = key missing |
439
- | `persist` | `(opts: { key: string }): Promise<boolean>` | Removes expiry; `true` if the timeout was removed |
440
- | `incr` | `(opts: { key: string }): Promise<number>` | Atomically increments integer at key by 1; returns new value |
441
- | `decr` | `(opts: { key: string }): Promise<number>` | Atomically decrements integer at key by 1; returns new value |
442
- | `incrBy` | `(opts: { key: string; value: number }): Promise<number>` | Atomically increments by `value`; returns new value |
443
- | `decrBy` | `(opts: { key: string; value: number }): Promise<number>` | Atomically decrements by `value`; returns new value |
444
-
445
- ---
446
-
447
- ### IRedisKeyValue - string key-value
448
-
449
- | Method | Signature | Behavior |
450
- |--------|-----------|----------|
451
- | `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 |
452
- | `get<T>` | `(opts: { key: string; transform?: (input: string) => T }): Promise<T \| null>` | Returns raw string or applies `transform`; `null` if key missing |
453
- | `del` | `(opts: { keys: string[] }): Promise<number>` | Deletes one or more keys; empty `keys` returns `0` |
454
- | `keys` | `(opts: { key: string }): Promise<string[]>` | Runs Redis `KEYS` with a glob pattern; avoid on large keyspaces - prefer `SCAN` via `execute` |
455
- | `getString` | `(opts: { key: string }): Promise<string \| null>` | Alias for `get` without transform |
456
- | `getStrings` | `(opts: { keys: string[] }): Promise<(string \| null)[]>` | Alias for `mGet` without transform |
457
- | `getObject<T>` | `(opts: { key: string }): Promise<T \| null>` | `get` with `JSON.parse` transform |
458
- | `getObjects` | `(opts: { keys: string[] }): Promise<(unknown \| null)[]>` | `mGet` with `JSON.parse` transform |
459
- | `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 |
460
- | `mGet<T>` | `(opts: { keys: string[]; transform?: (input: string) => T }): Promise<(T \| null)[]>` | Bulk read with optional transform; empty `keys` returns `[]` |
461
-
462
- ---
463
-
464
- ### IRedisHash - hash maps
465
-
466
- | Method | Signature | Behavior |
467
- |--------|-----------|----------|
468
- | `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 |
469
- | `hGetAll` | `(opts: { key: string; transform?: <T, R>(input: T) => R }): Promise<unknown>` | Returns all fields and values of a hash; applies optional transform |
470
- | `hGet` | `(opts: { key: string; field: string }): Promise<string \| null>` | Returns the value of a single hash field |
471
- | `hDel` | `(opts: { key: string; fields: string[] }): Promise<number>` | Deletes one or more hash fields; empty `fields` returns `0` |
472
- | `hExists` | `(opts: { key: string; field: string }): Promise<boolean>` | `true` if the field exists in the hash |
473
- | `hKeys` | `(opts: { key: string }): Promise<string[]>` | Returns all field names in the hash |
474
- | `hVals` | `(opts: { key: string }): Promise<string[]>` | Returns all field values in the hash |
475
- | `hIncrBy` | `(opts: { key: string; field: string; value: number }): Promise<number>` | Atomically increments a numeric hash field; returns new value |
476
- | `hLen` | `(opts: { key: string }): Promise<number>` | Returns the number of fields in the hash |
477
-
478
- ---
479
-
480
- ### IRedisSet - sets
481
-
482
- | Method | Signature | Behavior |
483
- |--------|-----------|----------|
484
- | `sAdd` | `(opts: { key: string; members: Array<string \| number> }): Promise<number>` | Adds members to a set; returns count of new members; empty `members` returns `0` |
485
- | `sRem` | `(opts: { key: string; members: Array<string \| number> }): Promise<number>` | Removes members from a set; returns count removed; empty `members` returns `0` |
486
- | `sMembers` | `(opts: { key: string }): Promise<string[]>` | Returns all members of the set |
487
- | `sIsMember` | `(opts: { key: string; member: string \| number }): Promise<boolean>` | `true` if `member` belongs to the set |
488
- | `sCard` | `(opts: { key: string }): Promise<number>` | Returns the number of members in the set |
489
-
490
- ---
491
-
492
- ### IRedisList - lists
493
-
494
- | Method | Signature | Behavior |
495
- |--------|-----------|----------|
496
- | `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` |
497
- | `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` |
498
- | `lPop` | `(opts: { key: string }): Promise<string \| null>` | Removes and returns the head element; `null` if list is empty |
499
- | `rPop` | `(opts: { key: string }): Promise<string \| null>` | Removes and returns the tail element; `null` if list is empty |
500
- | `lRange` | `(opts: { key: string; start: number; stop: number }): Promise<string[]>` | Returns elements from `start` to `stop` (inclusive, 0-indexed, `-1` = last) |
501
- | `lLen` | `(opts: { key: string }): Promise<number>` | Returns the number of elements in the list |
502
-
503
- ---
504
-
505
- ### IRedisPubSub - publish/subscribe
506
-
507
- | Method | Signature | Behavior |
508
- |--------|-----------|----------|
509
- | `publish<T>` | `(opts: { topics: string[]; payload: T; useCompress?: boolean }): Promise<void>` | JSON-serializes `payload` into a `Buffer`; optionally zlib-deflates it; publishes to each topic; skips empty/blank topics (logs error, does not throw) |
510
- | `subscribe` | `(opts: { topic: string }): void` | Subscribes the client to `topic`; on subscription error the helper **logs** it and does not throw |
511
- | `unsubscribe` | `(opts: { topic: string }): void` | Unsubscribes from `topic`; on error the helper **logs** it and does not throw |
512
-
513
- > **Important:** A subscribed ioredis connection enters subscriber mode and cannot run regular commands. Use `duplicateClient()` to obtain a separate connection for pub/sub.
514
-
515
- ```typescript
516
- // Correct pattern - separate connections for data and pub/sub
517
- const dataClient = new RedisSingleHelper({ name: 'data', host, port, password });
518
- const subClient = new RedisSingleHelper({ name: 'sub', host, port, password });
519
-
520
- subClient.subscribe({ topic: 'events' });
521
- subClient.getClient().on('message', (channel, message) => {
522
- const parsed = JSON.parse(message.toString());
523
- console.log(channel, parsed);
111
+ subscriber.subscribe({ topic: 'events' });
112
+ subscriber.getClient().on('message', (channel, message) => {
113
+ console.log(channel, JSON.parse(message.toString()));
524
114
  });
525
115
 
526
- await dataClient.set({ key: 'foo', value: 'bar' });
116
+ await redis.publish({ topics: ['events'], payload: { type: 'user.created' } });
527
117
  ```
528
118
 
529
- ---
530
-
531
- ### IRedisJson - RedisJSON module
532
-
533
- Requires the [RedisJSON module](https://redis.io/docs/stack/json/) on the server.
119
+ ### Hashes, sets, and lists
534
120
 
535
- | Method | Signature | Behavior |
536
- |--------|-----------|----------|
537
- | `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 |
538
- | `jGet<T>` | `(opts: { key: string; path?: string }): Promise<T \| null>` | Retrieves the document or sub-path (`JSON.GET`); `path` defaults to `'$'` |
539
- | `jDelete` | `(opts: { key: string; path?: string }): Promise<number>` | Deletes the document or sub-path (`JSON.DEL`); `path` defaults to `'$'`; returns count deleted |
540
- | `jNumberIncreaseBy` | `(opts: { key: string; path: string; value: number }): Promise<string \| null>` | Increments a numeric field by `value` (`JSON.NUMINCRBY`) |
541
- | `jStringAppend` | `(opts: { key: string; path: string; value: string }): Promise<number[] \| null>` | Appends `value` to a string field (`JSON.STRAPPEND`); returns new string lengths |
542
- | `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 |
543
- | `jPop<T>` | `(opts: { key: string; path: string }): Promise<T \| null>` | Pops the last element from an array field (`JSON.ARRPOP`) |
544
-
545
- ---
546
-
547
- ### IRedisCommand - raw escape hatch
548
-
549
- | Method | Signature | Behavior |
550
- |--------|-----------|----------|
551
- | `execute<R>` | `(command: string, parameters?: Array<string \| number \| Buffer>): Promise<R>` | Calls any ioredis command directly via `client.call(command, parameters)` |
121
+ Same key convention, one method family per data structure.
552
122
 
553
123
  ```typescript
554
- // Use SCAN instead of KEYS on large keyspaces
555
- const [cursor, keys] = await redis.execute<[string, string[]]>(
556
- 'SCAN',
557
- [0, 'MATCH', 'user:*', 'COUNT', 100],
558
- );
559
-
560
- // Fetch server info
561
- const info = await redis.execute<string>('INFO');
562
- ```
563
-
564
- ## 9. Using the Helper Across IGNIS
124
+ await redis.hSet({ key: 'user:42', value: { name: 'Ada', role: 'admin' } });
125
+ const role = await redis.hGet({ key: 'user:42', field: 'role' });
565
126
 
566
- 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.
567
-
568
- ### BullMQ (BullMQHelper)
569
-
570
- `BullMQHelper` accepts a `redisConnection: IRedisHelper`. Internally it:
571
- - Calls `redisConnection.duplicateClient()` to create dedicated `Queue` and `Worker` connections (BullMQ requires separate connections per role).
572
- - Calls `redisConnection.getClient() instanceof Cluster` to detect cluster topology and adjust BullMQ configuration.
573
-
574
- `RedisSingleHelper` (and `RedisSentinelHelper`) are BullMQ-compatible out of the box because the framework sets `maxRetriesPerRequest: null` in `buildDefaultOpts`.
575
-
576
- ```typescript
577
- import { BullMQHelper } from '@venizia/ignis-helpers';
578
- import { RedisSingleHelper } from '@venizia/ignis-helpers';
579
-
580
- const redis = new RedisSingleHelper({ name: 'queue-redis', host, port, password });
581
-
582
- const queue = BullMQHelper.newInstance({
583
- queueName: 'email',
584
- role: 'queue',
585
- redisConnection: redis,
586
- });
127
+ await redis.sAdd({ key: 'tags:42', members: ['vip', 'beta'] });
128
+ await redis.lPush({ key: 'queue:emails', values: ['welcome@example.com'] });
587
129
  ```
588
130
 
589
- ### Socket.IO component (SocketIOServerHelper)
131
+ ### Drop to a raw command
590
132
 
591
- The Socket.IO component resolves an `AbstractRedisHelper` from its Redis-connection binding key (value `@app/socket-io/redis-connection`). Internally `SocketIOServerHelper` calls `duplicateClient()` three times - once each for the pub channel, sub channel, and emitter adapter. Bind any topology (single/cluster/sentinel); the consumer code is identical:
133
+ `execute` calls any ioredis command directly. Reach for it when the typed API doesn't cover a command, like `SCAN`. Prefer `SCAN` over `keys()` in production - `keys()` blocks the server on large keyspaces.
592
134
 
593
135
  ```typescript
594
- import { AbstractRedisHelper, RedisSentinelHelper } from '@venizia/ignis-helpers';
595
-
596
- // In your Application preConfigure:
597
- const redis = new RedisSentinelHelper({ name: 'socket-redis', masterName: 'mymaster', sentinels, password });
598
- this.bind<AbstractRedisHelper>({ key: '@app/socket-io/redis-connection' }).to(redis);
136
+ const [cursor, matched] = await redis.execute<[string, string[]]>('SCAN', [
137
+ 0,
138
+ 'MATCH',
139
+ 'user:*',
140
+ 'COUNT',
141
+ 100,
142
+ ]);
599
143
  ```
600
144
 
601
- See the Socket.IO component documentation for the full component registration and adapter setup.
602
-
603
- ### WebSocket component (WebSocket server and emitter)
604
-
605
- The WebSocket component resolves an `AbstractRedisHelper` from its Redis-connection binding key (value `@app/websocket/redis-connection`). The `WebSocketServerHelper` calls `duplicateClient()` for its pub and sub connections; the WebSocket emitter calls `duplicateClient()` for its pub connection. Bind any topology:
606
-
607
- ```typescript
608
- import { AbstractRedisHelper, RedisClusterHelper } from '@venizia/ignis-helpers';
609
-
610
- const redis = new RedisClusterHelper({ name: 'ws-redis', nodes });
611
- this.bind<AbstractRedisHelper>({ key: '@app/websocket/redis-connection' }).to(redis);
612
- ```
613
-
614
- See the WebSocket component documentation for the full component registration.
615
-
616
- ### Casbin authorization enforcer
617
-
618
- The cached Casbin enforcer (`ICasbinEnforcerCachedRedis`) accepts `connection: IRedisHelper` inside its `options`. It calls the helper's direct methods - `get`, `set` (with `options.expiresIn`), and `del` - to read, write, and invalidate cached policy keys; it does not reach the raw client. Any topology works as the `connection`:
145
+ ## See also
619
146
 
620
- ```typescript
621
- import type { IRedisHelper } from '@venizia/ignis-helpers';
622
-
623
- // Shape of the cached-enforcer config (see the Authorization component docs for full setup):
624
- const cached = {
625
- use: true,
626
- driver: CasbinEnforcerCachedDrivers.REDIS,
627
- options: {
628
- connection: redis as IRedisHelper, // any RedisSingleHelper / RedisClusterHelper / RedisSentinelHelper
629
- expiresIn: 300_000, // cached-policy TTL in milliseconds
630
- keyFn: ({ user }) => `authz:policy:${user.id}`,
631
- },
632
- };
633
- ```
634
-
635
- ### Binding into an application
636
-
637
- ```typescript
638
- import { RedisSingleHelper } from '@venizia/ignis-helpers';
639
- import { BaseApplication } from '@venizia/ignis';
640
-
641
- class MyApp extends BaseApplication {
642
- async preConfigure() {
643
- const redis = new RedisSingleHelper({
644
- name: 'cache',
645
- host: process.env.REDIS_HOST!,
646
- port: Number(process.env.REDIS_PORT),
647
- password: process.env.REDIS_PASSWORD!,
648
- });
649
-
650
- // Bind so components and services can resolve it
651
- this.bind<RedisSingleHelper>({ key: 'helpers.RedisCache' }).to(redis);
652
- }
653
- }
654
- ```
655
-
656
- ## 10. Production Notes
657
-
658
- - **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.
659
-
660
- - **`duplicateClient()` for Pub/Sub.** A subscribed ioredis connection cannot run normal commands. Always call `duplicateClient()` to get a dedicated subscriber connection and keep the main client for data operations.
661
-
662
- - **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.
663
-
664
- - **TTL unit reminder:** `set.options.expiresIn` is **milliseconds**; `expire.seconds` is **seconds**; `expireAt.atEpochSeconds` is a Unix epoch in **seconds**. Mixing these up causes unexpected key expirations.
665
-
666
- - **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.
667
-
668
- - **Avoid `keys()` in production.** The Redis `KEYS` command scans the entire keyspace and blocks the server. Use `execute('SCAN', [...])` for production pattern matching.
669
-
670
- ## Import Reference
671
-
672
- ```typescript
673
- import {
674
- // Classes
675
- AbstractRedisHelper,
676
- RedisSingleHelper,
677
- RedisClusterHelper,
678
- RedisSentinelHelper,
679
- // Factory
680
- createRedisHelper,
681
- RedisModes,
682
- // Sentinel role const-class
683
- RedisSentinelRoles,
684
- } from '@venizia/ignis-helpers';
685
-
686
- import type {
687
- // Interface
688
- IRedisHelper,
689
- // Option types
690
- IRedisSingleHelperOptions,
691
- IRedisSingleHelperProps,
692
- IRedisClusterHelperOptions,
693
- IRedisClusterHelperProps,
694
- IRedisSentinelHelperOptions,
695
- IRedisSentinelHelperProps,
696
- IRedisHelperCallbacks,
697
- // Client + enum types
698
- TRedisClient,
699
- TRedisMode,
700
- TRedisSentinelRole,
701
- } from '@venizia/ignis-helpers';
702
- ```
147
+ - [Full reference](/extensions/helpers/redis/reference) - every method group, topology option, and lifecycle event
148
+ - [Queue Helper](/extensions/helpers/queue/) - `BullMQHelper` uses a Redis helper as its connection backend
149
+ - [Socket.IO Component](/extensions/components/socket-io/) - resolves an `AbstractRedisHelper` and calls `duplicateClient()` for its pub/sub adapter
150
+ - [Authorization Component](/extensions/components/authorization/) - the cached Casbin enforcer uses the helper's `get`/`set`/`del`
151
+ - [Migrating to the new Redis Helper API](/guides/migrations/redis-helpers-migration) - renames and breaking changes from the pre-refactor API
703
152
 
704
- ## See Also
153
+ **Files:**
705
154
 
706
- - [Helpers Index](../index) - All available helpers
707
- - [Queue Helper](../queue/) - BullMQ uses the Redis helper as its connection backend
708
- - [Socket.IO Component](/extensions/components/socket-io/) - Uses `duplicateClient` for pub/sub adapter
709
- - [WebSocket Component](/extensions/components/websocket/) - Uses `duplicateClient` for pub/sub channels
710
- - [Authorization Component](/extensions/components/authorization/) - Casbin enforcer uses the helper's `del`/`get`/`set` for cache operations
711
- - [ioredis documentation](https://github.com/redis/ioredis) - Underlying Redis client
712
- - [Redis Sentinel documentation](https://redis.io/docs/management/sentinel/) - Sentinel architecture reference
713
- - [RedisJSON documentation](https://redis.io/docs/stack/json/) - Required for `j*` methods
155
+ - [`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`, the shared data API
156
+ - [`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`
157
+ - [`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`
158
+ - [`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`
159
+ - [`packages/helpers/src/modules/redis/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/factory.ts) - `createRedisHelper` factory