@venizia/ignis-docs 0.2.0 → 0.2.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) 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 +22 -11
  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 +26 -2
  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 +6 -2
  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 +182 -93
  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 +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -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 that works identically whether you are talking to a single node, a sharded cluster, or a Sentinel-managed high-availability pair.
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,46 @@ 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
24
 
85
- ### IRedisSingleHelperOptions
25
+ await redis.set({ key: 'session:42', value: { userId: 42, active: true } });
86
26
 
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
-
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.
107
-
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.
33
+ `autoConnect` defaults to `true`, so the client starts connecting inside the constructor - no explicit `connect()` call needed for this example.
139
34
 
140
- `duplicateClient()` on a cluster creates a **new** `Cluster` instance from the same startup nodes and options (because ioredis `Cluster` does not implement `.duplicate()`).
35
+ ## How it works
141
36
 
142
- ## 5. Construction - Sentinel
37
+ - **Three topologies, one interface.** `RedisSingleHelper`, `RedisClusterHelper`, and `RedisSentinelHelper` all extend `AbstractRedisHelper` and implement `IRedisHelper`. Only the constructor options differ - the data API (`get`, `hSet`, `publish`, ...) is identical, so code written against `IRedisHelper` works with any topology unchanged.
143
38
 
144
- ### What Redis Sentinel is
39
+ | Topology | Class | Fits |
40
+ |----------|-------|------|
41
+ | Single node | `RedisSingleHelper` | One Redis instance |
42
+ | Cluster | `RedisClusterHelper` | Data sharded across nodes |
43
+ | Sentinel | `RedisSentinelHelper` | Automatic failover for one logical master |
145
44
 
146
- Redis Sentinel is a high-availability (HA) architecture made of three process types:
45
+ - **Pick a topology via the factory.** `createRedisHelper({ mode })` picks a topology from configuration - it is overloaded to return the concrete class when `mode` is a literal (`RedisModes.SINGLE | CLUSTER | SENTINEL`).
46
+ - **Connection lifecycle is automatic.** With `autoConnect: true` (default) the ioredis client starts connecting in the constructor; with `autoConnect: false` you call `connect()` yourself.
47
+ - **Reconnects back off automatically.** The backoff grows with each attempt, capped between 1 and 5 seconds, up to `maxRetry` attempts (`0` = unlimited, the default; `-1` = no retry).
48
+ - **BullMQ compatibility differs by topology.** `maxRetriesPerRequest: null` is always set for single and Sentinel helpers, which is what makes them BullMQ-compatible out of the box. Cluster does not get this automatically - see the [Full reference](/extensions/helpers/redis/reference).
49
+ - **Values are JSON-serialized automatically.** `set`, `mSet`, `jSet`, and `jPush` call `JSON.stringify` before writing; `getObject`, `getObjects`, and `jGet` parse on read. Plain `get`/`mGet` return the raw stored string unless you pass a `transform` function - reach for `getObject`/`getObjects` when you wrote the value with `set`.
50
+ - **Every method is camelCase.** `hSet`, `lPush`, `sAdd` - even where the underlying ioredis/Redis command is lowercase (`hset`, `lpush`, `sadd`). There is no lowercase alias.
147
51
 
148
- ```
149
- +------------------+ monitors +--------------------+
150
- | Sentinel (1) |<------------------>| Master (primary) |
151
- +------------------+ +--------------------+
152
- +------------------+ replication |
153
- | Sentinel (2) | +--------------+
154
- +------------------+ v
155
- +------------------+ +--------------------+
156
- | Sentinel (3) | | Replica (standby) |
157
- +------------------+ +--------------------+
158
- ```
52
+ ## Common tasks
159
53
 
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.
167
-
168
- ### Constructor
169
-
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
- ```
189
-
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
- ```
54
+ ### Pick a topology
286
55
 
287
- Connect and verify failover:
288
-
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
- });
300
-
301
- await redis.set({ key: 'hello', value: 'world' });
302
- const val = await redis.get({ key: 'hello' });
303
- console.log(val); // "world"
304
-
305
- // Stop the master container - wait ~5 s - the replica is promoted.
306
- // The same redis instance will reconnect automatically and continue working.
307
- ```
308
-
309
- ## 6. Selecting a Topology - the Factory
310
-
311
- `createRedisHelper` is an overloaded factory that returns the concrete type when `mode` is a literal.
312
-
313
- ```typescript
314
- import { createRedisHelper, RedisModes } from '@venizia/ignis-helpers';
315
-
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
- ```
334
-
335
- Switch by environment:
56
+ Switch topology by changing `mode` and its matching options; the rest of your code does not change.
336
57
 
337
58
  ```typescript
338
59
  import { createRedisHelper, RedisModes, type IRedisHelper } from '@venizia/ignis-helpers';
339
60
 
340
- const mode = (process.env.REDIS_MODE ?? RedisModes.SINGLE) as typeof RedisModes.SINGLE;
341
-
342
61
  const redis: IRedisHelper = createRedisHelper({
343
- mode,
62
+ mode: RedisModes.SINGLE, // or RedisModes.CLUSTER / RedisModes.SENTINEL
344
63
  name: 'app-cache',
345
64
  host: process.env.REDIS_HOST!,
346
65
  port: Number(process.env.REDIS_PORT),
@@ -348,366 +67,75 @@ const redis: IRedisHelper = createRedisHelper({
348
67
  });
349
68
  ```
350
69
 
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`.
358
-
359
- ## 7. Lifecycle and Events
70
+ See [Full reference](/extensions/helpers/redis/reference) for the cluster (`nodes`) and Sentinel (`sentinels`, `masterName`) constructor shapes.
360
71
 
361
- ### autoConnect and manual connect
72
+ ### Cache a value with a TTL
362
73
 
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.
74
+ `options.expiresIn` on `set` is in **milliseconds**, but `ttl()` reports remaining time in **seconds** - mixing the two up is the most common bug with this helper.
364
75
 
365
76
  ```typescript
366
- // Lazy connect
367
- const redis = new RedisSingleHelper({
368
- name: 'cache',
369
- host: 'localhost',
370
- port: 6379,
371
- password: 'secret',
372
- autoConnect: false,
77
+ await redis.set({
78
+ key: 'session:42',
79
+ value: { userId: 42 },
80
+ options: { expiresIn: 60_000 }, // 60 seconds
373
81
  });
374
82
 
375
- await redis.connect(); // resolves true when status === 'ready'
83
+ const remaining = await redis.ttl({ key: 'session:42' }); // seconds; -1 = no expiry, -2 = missing
376
84
  ```
377
85
 
378
- ### Connection methods
86
+ ### Publish and subscribe
379
87
 
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'` |
385
-
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
88
+ A subscribed ioredis connection cannot run regular commands, so use a second helper instance for subscribing and keep the first for data operations.
400
89
 
401
90
  ```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)
91
+ const subscriber = new RedisSingleHelper({ name: 'sub', host: 'localhost', port: 6379, password: 'secret' });
410
92
 
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.
416
-
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);
524
- });
525
-
526
- await dataClient.set({ key: 'foo', value: 'bar' });
527
- ```
528
-
529
- ---
530
-
531
- ### IRedisJson - RedisJSON module
532
-
533
- Requires the [RedisJSON module](https://redis.io/docs/stack/json/) on the server.
534
-
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)` |
552
-
553
- ```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
565
-
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,
93
+ subscriber.subscribe({ topic: 'events' });
94
+ subscriber.getClient().on('message', (channel, message) => {
95
+ console.log(channel, JSON.parse(message.toString()));
586
96
  });
587
- ```
588
-
589
- ### Socket.IO component (SocketIOServerHelper)
590
97
 
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:
592
-
593
- ```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);
98
+ await redis.publish({ topics: ['events'], payload: { type: 'user.created' } });
599
99
  ```
600
100
 
601
- See the Socket.IO component documentation for the full component registration and adapter setup.
101
+ ### Hashes, sets, and lists
602
102
 
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:
103
+ Same key convention, one method family per data structure.
606
104
 
607
105
  ```typescript
608
- import { AbstractRedisHelper, RedisClusterHelper } from '@venizia/ignis-helpers';
106
+ await redis.hSet({ key: 'user:42', value: { name: 'Ada', role: 'admin' } });
107
+ const role = await redis.hGet({ key: 'user:42', field: 'role' });
609
108
 
610
- const redis = new RedisClusterHelper({ name: 'ws-redis', nodes });
611
- this.bind<AbstractRedisHelper>({ key: '@app/websocket/redis-connection' }).to(redis);
109
+ await redis.sAdd({ key: 'tags:42', members: ['vip', 'beta'] });
110
+ await redis.lPush({ key: 'queue:emails', values: ['welcome@example.com'] });
612
111
  ```
613
112
 
614
- See the WebSocket component documentation for the full component registration.
615
-
616
- ### Casbin authorization enforcer
113
+ ### Drop to a raw command
617
114
 
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`:
115
+ `execute` calls any ioredis command directly - useful for commands the typed API does not cover, like `SCAN` (prefer it over `keys()` in production, which blocks the server on large keyspaces).
619
116
 
620
117
  ```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
- }
118
+ const [cursor, matched] = await redis.execute<[string, string[]]>('SCAN', [
119
+ 0,
120
+ 'MATCH',
121
+ 'user:*',
122
+ 'COUNT',
123
+ 100,
124
+ ]);
654
125
  ```
655
126
 
656
- ## 10. Production Notes
127
+ ## See also
657
128
 
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
- ```
129
+ - [Full reference](/extensions/helpers/redis/reference) - every method group, topology option, and lifecycle event
130
+ - [Queue Helper](/extensions/helpers/queue/) - `BullMQHelper` uses a Redis helper as its connection backend
131
+ - [Socket.IO Component](/extensions/components/socket-io/) - resolves an `AbstractRedisHelper` and calls `duplicateClient()` for its pub/sub adapter
132
+ - [Authorization Component](/extensions/components/authorization/) - the cached Casbin enforcer uses the helper's `get`/`set`/`del`
133
+ - [Migrating to the new Redis Helper API](/guides/migrations/redis-helpers-migration) - renames and breaking changes from the pre-refactor API
703
134
 
704
- ## See Also
135
+ **Files:**
705
136
 
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
137
+ - [`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
138
+ - [`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`
139
+ - [`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`
140
+ - [`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`
141
+ - [`packages/helpers/src/modules/redis/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/factory.ts) - `createRedisHelper` factory