@venizia/ignis-docs 0.0.8 → 0.2.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 (180) hide show
  1. package/README.md +7 -7
  2. package/content/best-practices/api-usage-examples.md +15 -12
  3. package/content/best-practices/architectural-patterns.md +70 -78
  4. package/content/best-practices/architecture-decisions.md +91 -60
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/content/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/content/best-practices/code-style-standards/documentation.md +13 -13
  9. package/content/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/content/best-practices/code-style-standards/index.md +1 -1
  11. package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/content/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/content/best-practices/code-style-standards/tooling.md +8 -5
  14. package/content/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/content/best-practices/common-pitfalls.md +56 -37
  16. package/content/best-practices/contribution-workflow.md +13 -14
  17. package/content/best-practices/data-modeling.md +46 -22
  18. package/content/best-practices/deployment-strategies.md +28 -27
  19. package/content/best-practices/error-handling.md +48 -24
  20. package/content/best-practices/index.md +5 -5
  21. package/content/best-practices/performance-optimization.md +40 -31
  22. package/content/best-practices/security-guidelines.md +52 -23
  23. package/content/best-practices/testing-strategies.md +65 -51
  24. package/content/best-practices/troubleshooting-tips.md +24 -24
  25. package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
  26. package/content/extensions/components/authentication/api.md +19 -19
  27. package/content/extensions/components/authentication/errors.md +7 -7
  28. package/content/extensions/components/authentication/index.md +10 -8
  29. package/content/extensions/components/authentication/usage.md +101 -6
  30. package/content/extensions/components/authorization/api.md +45 -25
  31. package/content/extensions/components/authorization/errors.md +6 -6
  32. package/content/extensions/components/authorization/index.md +11 -10
  33. package/content/extensions/components/authorization/usage.md +21 -21
  34. package/content/extensions/components/health-check.md +1 -1
  35. package/content/extensions/components/index.md +5 -5
  36. package/content/extensions/components/mail/errors.md +15 -15
  37. package/content/extensions/components/mail/index.md +1 -2
  38. package/content/extensions/components/mail/usage.md +1 -1
  39. package/content/extensions/components/request-tracker.md +1 -1
  40. package/content/extensions/components/socket-io/api.md +9 -9
  41. package/content/extensions/components/socket-io/errors.md +5 -5
  42. package/content/extensions/components/socket-io/index.md +8 -8
  43. package/content/extensions/components/socket-io/usage.md +1 -1
  44. package/content/extensions/components/static-asset/api.md +17 -4
  45. package/content/extensions/components/static-asset/errors.md +4 -4
  46. package/content/extensions/components/static-asset/index.md +26 -28
  47. package/content/extensions/components/static-asset/usage.md +13 -12
  48. package/content/extensions/components/template/index.md +2 -2
  49. package/content/extensions/components/template/setup-page.md +1 -1
  50. package/content/extensions/components/websocket/api.md +3 -3
  51. package/content/extensions/components/websocket/errors.md +5 -5
  52. package/content/extensions/components/websocket/index.md +5 -5
  53. package/content/extensions/components/websocket/usage.md +3 -3
  54. package/content/extensions/helpers/cron/index.md +2 -2
  55. package/content/extensions/helpers/crypto/index.md +1 -1
  56. package/content/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +81 -25
  58. package/content/extensions/helpers/index.md +2 -3
  59. package/content/extensions/helpers/inversion/index.md +15 -7
  60. package/content/extensions/helpers/kafka/compile-binary.md +92 -0
  61. package/content/extensions/helpers/kafka/examples.md +1 -1
  62. package/content/extensions/helpers/kafka/index.md +3 -0
  63. package/content/extensions/helpers/logger/index.md +32 -2
  64. package/content/extensions/helpers/network/index.md +6 -0
  65. package/content/extensions/helpers/queue/index.md +14 -17
  66. package/content/extensions/helpers/redis/index.md +548 -323
  67. package/content/extensions/helpers/socket-io/index.md +14 -10
  68. package/content/extensions/helpers/storage/api.md +44 -8
  69. package/content/extensions/helpers/storage/index.md +43 -7
  70. package/content/extensions/helpers/template/index.md +6 -3
  71. package/content/extensions/helpers/types/index.md +11 -8
  72. package/content/extensions/helpers/websocket/api.md +9 -9
  73. package/content/extensions/helpers/websocket/index.md +7 -7
  74. package/content/extensions/helpers/worker-thread/index.md +2 -2
  75. package/content/extensions/index.md +3 -4
  76. package/content/extensions/src-details/mcp-server.md +18 -24
  77. package/content/guides/core-concepts/application/bootstrapping.md +11 -14
  78. package/content/guides/core-concepts/application/index.md +3 -3
  79. package/content/guides/core-concepts/components.md +19 -10
  80. package/content/guides/core-concepts/dependency-injection.md +6 -3
  81. package/content/guides/core-concepts/grpc-controllers.md +6 -5
  82. package/content/guides/core-concepts/persistent/datasources.md +42 -43
  83. package/content/guides/core-concepts/persistent/index.md +16 -7
  84. package/content/guides/core-concepts/persistent/models.md +24 -20
  85. package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
  86. package/content/guides/core-concepts/persistent/repositories.md +40 -23
  87. package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
  88. package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
  89. package/content/guides/core-concepts/persistent/transactions.md +61 -25
  90. package/content/guides/core-concepts/rest-controllers.md +12 -9
  91. package/content/guides/core-concepts/services.md +330 -60
  92. package/content/guides/get-started/5-minute-quickstart.md +15 -15
  93. package/content/guides/get-started/philosophy.md +36 -36
  94. package/content/guides/get-started/setup.md +3 -3
  95. package/content/guides/index.md +3 -3
  96. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  97. package/content/guides/migrations/scoped-rbac-migration.md +17 -17
  98. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  99. package/content/guides/reference/glossary.md +19 -12
  100. package/content/guides/reference/mcp-docs-server.md +22 -18
  101. package/content/guides/tutorials/building-a-crud-api.md +37 -44
  102. package/content/guides/tutorials/complete-installation.md +17 -17
  103. package/content/guides/tutorials/ecommerce-api.md +163 -124
  104. package/content/guides/tutorials/realtime-chat.md +181 -135
  105. package/content/guides/tutorials/testing.md +65 -523
  106. package/content/index.md +2 -180
  107. package/content/public/apple-touch-icon.png +0 -0
  108. package/content/public/og-image.png +0 -0
  109. package/content/public/site.webmanifest +11 -0
  110. package/content/references/base/application.md +4 -5
  111. package/content/references/base/bootstrapping.md +18 -5
  112. package/content/references/base/components.md +149 -120
  113. package/content/references/base/connectors.md +178 -0
  114. package/content/references/base/controllers.md +41 -30
  115. package/content/references/base/datasources.md +163 -92
  116. package/content/references/base/dependency-injection.md +34 -22
  117. package/content/references/base/filter-system/application-usage.md +17 -14
  118. package/content/references/base/filter-system/array-operators.md +7 -2
  119. package/content/references/base/filter-system/comparison-operators.md +3 -0
  120. package/content/references/base/filter-system/default-filter.md +89 -71
  121. package/content/references/base/filter-system/fields-order-pagination.md +22 -22
  122. package/content/references/base/filter-system/index.md +6 -3
  123. package/content/references/base/filter-system/json-filtering.md +20 -1
  124. package/content/references/base/filter-system/list-operators.md +1 -1
  125. package/content/references/base/filter-system/logical-operators.md +33 -1
  126. package/content/references/base/filter-system/null-operators.md +30 -1
  127. package/content/references/base/filter-system/quick-reference.md +23 -4
  128. package/content/references/base/filter-system/tips.md +5 -5
  129. package/content/references/base/filter-system/use-cases.md +12 -12
  130. package/content/references/base/grpc-controllers.md +13 -13
  131. package/content/references/base/index.md +24 -12
  132. package/content/references/base/middlewares.md +265 -327
  133. package/content/references/base/models.md +63 -49
  134. package/content/references/base/providers.md +136 -130
  135. package/content/references/base/repositories/advanced.md +59 -58
  136. package/content/references/base/repositories/index.md +115 -91
  137. package/content/references/base/repositories/mixins.md +55 -291
  138. package/content/references/base/repositories/relations.md +54 -64
  139. package/content/references/base/repositories/soft-deletable.md +31 -30
  140. package/content/references/base/services.md +296 -93
  141. package/content/references/configuration/environment-variables.md +49 -31
  142. package/content/references/configuration/index.md +6 -6
  143. package/content/references/index.md +17 -12
  144. package/content/references/quick-reference.md +65 -106
  145. package/content/references/utilities/crypto.md +65 -23
  146. package/content/references/utilities/index.md +3 -3
  147. package/content/references/utilities/jsx.md +6 -4
  148. package/content/references/utilities/module.md +68 -20
  149. package/content/references/utilities/parse.md +4 -14
  150. package/content/references/utilities/promise.md +9 -7
  151. package/content/references/utilities/schema.md +5 -3
  152. package/dist/mcp-server/common/guards.d.ts +8 -0
  153. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  154. package/dist/mcp-server/common/guards.js +14 -0
  155. package/dist/mcp-server/common/guards.js.map +1 -0
  156. package/dist/mcp-server/common/index.d.ts +1 -0
  157. package/dist/mcp-server/common/index.d.ts.map +1 -1
  158. package/dist/mcp-server/common/index.js +1 -0
  159. package/dist/mcp-server/common/index.js.map +1 -1
  160. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  162. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  163. package/dist/mcp-server/helpers/github.helper.js +1 -1
  164. package/dist/mcp-server/index.js +7 -2
  165. package/dist/mcp-server/index.js.map +1 -1
  166. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  167. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  168. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  169. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  175. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  178. package/package.json +9 -9
  179. package/content/extensions/helpers/testing/index.md +0 -510
  180. package/content/references/base/middleware.md +0 -347
@@ -1,46 +1,65 @@
1
1
  # Redis
2
2
 
3
- Powerful Redis abstraction supporting single instances and clusters via `ioredis`, with automatic JSON serialization, connection lifecycle callbacks, Pub/Sub with optional zlib compression, RedisJSON operations, and raw command execution.
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
4
 
5
- ## Quick Reference
5
+ Backed by **ioredis** under the hood.
6
6
 
7
- | Class | Extends | Use Case |
8
- |-------|---------|----------|
9
- | **`DefaultRedisHelper`** | `BaseHelper` | Base class with unified API for all Redis operations |
10
- | **`RedisHelper`** | `DefaultRedisHelper` | Single Redis instance with auto-connect and retry strategy |
11
- | **`RedisClusterHelper`** | `DefaultRedisHelper` | Redis cluster with multi-node support |
7
+ ## 1. Overview
12
8
 
13
- #### Import Paths
9
+ ### When to use which topology
14
10
 
15
- ```typescript
16
- // From the helpers package
17
- import {
18
- DefaultRedisHelper,
19
- RedisHelper,
20
- RedisClusterHelper,
21
- } from '@venizia/ignis-helpers';
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 |
22
16
 
23
- // Types are also available from the core package (via type-only re-export)
24
- import type {
25
- IRedisHelperOptions,
26
- IRedisClusterHelperOptions,
27
- IRedisHelperCallbacks,
28
- IRedisHelperProps,
29
- IRedisClusterHelperProps,
30
- } from '@venizia/ignis-helpers'; // or from '@venizia/ignis'
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
31
36
  ```
32
37
 
33
- ## Creating an Instance
38
+ `RedisSingleHelper` and `RedisSentinelHelper` return a `Redis` instance. `RedisClusterHelper` returns a `Cluster` instance. Both satisfy `TRedisClient`.
39
+
40
+ ### IRedisHelper and its nine capability interfaces
41
+
42
+ `IRedisHelper` extends all nine of the following:
34
43
 
35
- ### Single Instance
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?)` |
36
55
 
37
- Use `RedisHelper` for connecting to a single Redis server.
56
+ ## 3. Construction - Single
38
57
 
39
58
  ```typescript
40
- import { RedisHelper } from '@venizia/ignis-helpers';
59
+ import { RedisSingleHelper } from '@venizia/ignis-helpers';
41
60
 
42
- const redis = new RedisHelper({
43
- name: 'my-redis',
61
+ const redis = new RedisSingleHelper({
62
+ name: 'cache',
44
63
  host: 'localhost',
45
64
  port: 6379,
46
65
  password: 'secret',
@@ -49,440 +68,646 @@ const redis = new RedisHelper({
49
68
  maxRetry: 5,
50
69
 
51
70
  onInitialized: ({ name, helper }) => {
52
- console.log(`Redis "${name}" initialized`);
71
+ console.log(`[${name}] initialized`);
53
72
  },
54
73
  onConnected: ({ name }) => {
55
- console.log(`Redis "${name}" connected`);
74
+ console.log(`[${name}] connected`);
56
75
  },
57
76
  onReady: ({ name }) => {
58
- console.log(`Redis "${name}" ready`);
77
+ console.log(`[${name}] ready`);
59
78
  },
60
79
  onError: ({ name, error }) => {
61
- console.error(`Redis "${name}" error:`, error);
80
+ console.error(`[${name}] error`, error);
62
81
  },
63
82
  });
64
83
  ```
65
84
 
66
- #### `IRedisHelperOptions`
85
+ ### IRedisSingleHelperOptions
67
86
 
68
- | Option | Type | Required | Default | Description |
69
- |--------|------|----------|---------|-------------|
70
- | `name` | `string` | Yes | -- | Unique identifier for this client instance |
71
- | `host` | `string` | Yes | -- | Redis server hostname |
72
- | `port` | `string \| number` | Yes | -- | Redis server port |
73
- | `password` | `string` | Yes | -- | Redis password |
74
- | `user` | `string` | No | -- | Redis username (ACL authentication) |
75
- | `database` | `number` | No | `0` | Redis database index |
76
- | `autoConnect` | `boolean` | No | `true` | Connect immediately on creation. When `false`, uses ioredis `lazyConnect` mode |
77
- | `maxRetry` | `number` | No | `0` | Maximum reconnection attempts. `0` = unlimited retries. Values below `0` disable retry entirely |
78
- | `onInitialized` | `(opts: { name: string; helper: DefaultRedisHelper }) => void` | No | -- | Called synchronously immediately after client construction |
79
- | `onConnected` | `(opts: { name: string; helper: DefaultRedisHelper }) => void` | No | -- | Called when the TCP connection is established |
80
- | `onReady` | `(opts: { name: string; helper: DefaultRedisHelper }) => void` | No | -- | Called when the client is ready to accept commands |
81
- | `onError` | `(opts: { name: string; helper: DefaultRedisHelper; error: any }) => void` | No | -- | Called on connection or command errors |
87
+ Combines `IRedisSingleHelperProps` and `IRedisHelperCallbacks`.
82
88
 
83
- **Retry strategy:** Exponential backoff clamped between 1s and 5s: `Math.max(Math.min(attempt * 2000, 5000), 1000)`. The ioredis option `maxRetriesPerRequest` is set to `null` internally, which is required for compatibility with BullMQ.
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 |
84
103
 
85
- **ioredis configuration:** `RedisHelper` creates an ioredis `Redis` instance with `showFriendlyErrorStack: true` for better error diagnostics.
104
+ ### Retry strategy
86
105
 
87
- ### Cluster
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.
88
107
 
89
- Use `RedisClusterHelper` for connecting to a Redis cluster.
108
+ ## 4. Construction - Cluster
90
109
 
91
110
  ```typescript
92
111
  import { RedisClusterHelper } from '@venizia/ignis-helpers';
93
112
 
94
113
  const cluster = new RedisClusterHelper({
95
- name: 'my-cluster',
114
+ name: 'cache-cluster',
96
115
  nodes: [
97
116
  { host: 'redis-node-1', port: 7000 },
98
- { host: 'redis-node-2', port: 7001, password: 'node-specific-pass' },
117
+ { host: 'redis-node-2', port: 7001 },
99
118
  { host: 'redis-node-3', port: 7002 },
100
119
  ],
101
120
  clusterOptions: {
102
121
  redisOptions: { password: 'cluster-password' },
103
122
  },
104
-
105
123
  onReady: ({ name }) => {
106
- console.log(`Cluster "${name}" ready`);
124
+ console.log(`[${name}] cluster ready`);
107
125
  },
108
126
  });
109
127
  ```
110
128
 
111
- #### `IRedisClusterHelperOptions`
129
+ ### IRedisClusterHelperOptions
112
130
 
113
- | Option | Type | Required | Default | Description |
114
- |--------|------|----------|---------|-------------|
115
- | `name` | `string` | Yes | -- | Unique identifier for this cluster client |
116
- | `nodes` | `Array<{ host: string; port: string \| number; password?: string }>` | Yes | -- | List of cluster node addresses |
117
- | `clusterOptions` | `ClusterOptions` | No | -- | ioredis `ClusterOptions` passed directly to the `Cluster` constructor |
118
- | `onInitialized` | `(opts: { name: string; helper: DefaultRedisHelper }) => void` | No | -- | Called synchronously immediately after cluster client construction |
119
- | `onConnected` | `(opts: { name: string; helper: DefaultRedisHelper }) => void` | No | -- | Called when connected |
120
- | `onReady` | `(opts: { name: string; helper: DefaultRedisHelper }) => void` | No | -- | Called when ready |
121
- | `onError` | `(opts: { name: string; helper: DefaultRedisHelper; error: any }) => void` | No | -- | Called on errors |
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 |
122
137
 
123
- ### Lifecycle Callbacks
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.
124
139
 
125
- All Redis helpers accept the same lifecycle callbacks via `IRedisHelperCallbacks`:
140
+ `duplicateClient()` on a cluster creates a **new** `Cluster` instance from the same startup nodes and options (because ioredis `Cluster` does not implement `.duplicate()`).
126
141
 
127
- ```typescript
128
- interface IRedisHelperCallbacks {
129
- onInitialized?: (opts: { name: string; helper: DefaultRedisHelper }) => void;
130
- onConnected?: (opts: { name: string; helper: DefaultRedisHelper }) => void;
131
- onReady?: (opts: { name: string; helper: DefaultRedisHelper }) => void;
132
- onError?: (opts: { name: string; helper: DefaultRedisHelper; error: any }) => void;
133
- }
134
- ```
135
-
136
- | Callback | Redis Event | When |
137
- |----------|-------------|------|
138
- | `onInitialized` | -- | Immediately after client construction (synchronous) |
139
- | `onConnected` | `connect` | TCP connection established |
140
- | `onReady` | `ready` | Client ready to accept commands |
141
- | `onError` | `error` | Connection or command error |
142
+ ## 5. Construction - Sentinel
142
143
 
143
- Additionally, the client internally logs a warning on `reconnecting` events.
144
+ ### What Redis Sentinel is
144
145
 
145
- ## Usage
146
+ Redis Sentinel is a high-availability (HA) architecture made of three process types:
146
147
 
147
- All operations below are available on both `RedisHelper` and `RedisClusterHelper` via the shared `DefaultRedisHelper` base class.
148
+ ```
149
+ +------------------+ monitors +--------------------+
150
+ | Sentinel (1) |<------------------>| Master (primary) |
151
+ +------------------+ +--------------------+
152
+ +------------------+ replication |
153
+ | Sentinel (2) | +--------------+
154
+ +------------------+ v
155
+ +------------------+ +--------------------+
156
+ | Sentinel (3) | | Replica (standby) |
157
+ +------------------+ +--------------------+
158
+ ```
148
159
 
149
- ### Connection Management
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.
150
163
 
151
- ```typescript
152
- // Manual connect (when autoConnect is false)
153
- const connected = await redis.connect();
154
- // => true if status becomes 'ready', false if already connected/connecting
164
+ ### Why your app connects to sentinels, not the master
155
165
 
156
- // Disconnect gracefully (sends QUIT command)
157
- const disconnected = await redis.disconnect();
158
- // => true if quit succeeded
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.
159
167
 
160
- // Health check
161
- const pong = await redis.ping();
162
- // => 'PONG'
168
+ ### Constructor
163
169
 
164
- // Access underlying ioredis client
165
- const ioredisClient = redis.getClient();
166
- // RedisHelper returns Redis, RedisClusterHelper returns Cluster
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
+ });
167
188
  ```
168
189
 
169
- > [!NOTE]
170
- > `connect()` resolves to `false` without action if the client status is already `ready`, `reconnecting`, or `connecting`. Similarly, `disconnect()` resolves to `false` if the status is `end` or `close`.
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
+ ```
171
286
 
172
- ### Key-Value Operations
287
+ Connect and verify failover:
173
288
 
174
289
  ```typescript
175
- // Set a value (auto-serialized to JSON)
176
- await redis.set({ key: 'user:1', value: { name: 'Alice', age: 30 } });
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
+ });
177
300
 
178
- // Set with logging enabled
179
- await redis.set({ key: 'user:1', value: { name: 'Alice' }, options: { log: true } });
301
+ await redis.set({ key: 'hello', value: 'world' });
302
+ const val = await redis.get({ key: 'hello' });
303
+ console.log(val); // "world"
180
304
 
181
- // Get raw string value
182
- const raw = await redis.get({ key: 'user:1' });
183
- // => '{"name":"Alice","age":30}'
305
+ // Stop the master container - wait ~5 s - the replica is promoted.
306
+ // The same redis instance will reconnect automatically and continue working.
307
+ ```
184
308
 
185
- // Get with custom transform
186
- const parsed = await redis.get({
187
- key: 'user:1',
188
- transform: (input) => JSON.parse(input),
189
- });
309
+ ## 6. Selecting a Topology - the Factory
190
310
 
191
- // Convenience: get as string (alias for get)
192
- const str = await redis.getString({ key: 'user:1' });
311
+ `createRedisHelper` is an overloaded factory that returns the concrete type when `mode` is a literal.
193
312
 
194
- // Convenience: get as parsed JSON object
195
- const user = await redis.getObject({ key: 'user:1' });
196
- // => { name: 'Alice', age: 30 }
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
+ });
197
324
 
198
- // Delete keys
199
- await redis.del({ keys: ['user:1', 'user:2'] });
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
+ });
200
333
  ```
201
334
 
202
- ### Multi-Key Operations
335
+ Switch by environment:
203
336
 
204
337
  ```typescript
205
- // Set multiple keys at once
206
- await redis.mset({
207
- payload: [
208
- { key: 'user:1', value: { name: 'Alice' } },
209
- { key: 'user:2', value: { name: 'Bob' } },
210
- ],
211
- });
338
+ import { createRedisHelper, RedisModes, type IRedisHelper } from '@venizia/ignis-helpers';
212
339
 
213
- // Get multiple raw values
214
- const values = await redis.mget({ keys: ['user:1', 'user:2'] });
215
- // => ['{"name":"Alice"}', '{"name":"Bob"}']
340
+ const mode = (process.env.REDIS_MODE ?? RedisModes.SINGLE) as typeof RedisModes.SINGLE;
216
341
 
217
- // Get multiple with transform
218
- const users = await redis.mget({
219
- keys: ['user:1', 'user:2'],
220
- transform: (el) => JSON.parse(el),
342
+ const redis: IRedisHelper = createRedisHelper({
343
+ mode,
344
+ name: 'app-cache',
345
+ host: process.env.REDIS_HOST!,
346
+ port: Number(process.env.REDIS_PORT),
347
+ password: process.env.REDIS_PASSWORD!,
221
348
  });
349
+ ```
222
350
 
223
- // Convenience: get multiple strings
224
- const strings = await redis.getStrings({ keys: ['key1', 'key2'] });
351
+ | `mode` value | Returns |
352
+ |---|---|
353
+ | `RedisModes.SINGLE` (`'single'`) | `RedisSingleHelper` |
354
+ | `RedisModes.CLUSTER` (`'cluster'`) | `RedisClusterHelper` |
355
+ | `RedisModes.SENTINEL` (`'sentinel'`) | `RedisSentinelHelper` |
225
356
 
226
- // Convenience: get multiple parsed objects
227
- const objects = await redis.getObjects({ keys: ['user:1', 'user:2'] });
228
- ```
357
+ Unknown mode throws an `ApplicationError`.
358
+
359
+ ## 7. Lifecycle and Events
229
360
 
230
- > [!TIP]
231
- > `mSet()`, `mGet()`, `hSet()`, and `hGetAll()` are camelCase aliases for `mset()`, `mget()`, `hset()`, and `hgetall()` respectively. Both forms are valid.
361
+ ### autoConnect and manual connect
232
362
 
233
- ### Hash Operations
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.
234
364
 
235
365
  ```typescript
236
- // Set hash fields
237
- await redis.hset({
238
- key: 'session:abc',
239
- value: { userId: 'u1', token: 'tok123', createdAt: '2025-01-01' },
366
+ // Lazy connect
367
+ const redis = new RedisSingleHelper({
368
+ name: 'cache',
369
+ host: 'localhost',
370
+ port: 6379,
371
+ password: 'secret',
372
+ autoConnect: false,
240
373
  });
241
374
 
242
- // Set hash fields with logging
243
- await redis.hset({
244
- key: 'session:abc',
245
- value: { userId: 'u1' },
246
- options: { log: true },
247
- });
375
+ await redis.connect(); // resolves true when status === 'ready'
376
+ ```
248
377
 
249
- // Get all hash fields
250
- const session = await redis.hgetall({ key: 'session:abc' });
251
- // => { userId: 'u1', token: 'tok123', createdAt: '2025-01-01' }
378
+ ### Connection methods
252
379
 
253
- // Get all hash fields with transform
254
- const transformed = await redis.hgetall({
255
- key: 'session:abc',
256
- transform: (input) => ({ ...input, extra: true }),
257
- });
258
- ```
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'` |
259
385
 
260
- ### Key Scanning
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
261
400
 
262
401
  ```typescript
263
- // Find keys matching a pattern
264
- const matchingKeys = await redis.keys({ key: 'user:*' });
265
- // => ['user:1', 'user:2', ...]
402
+ const dedicated = redis.duplicateClient();
266
403
  ```
267
404
 
268
- > [!WARNING]
269
- > `keys()` uses the Redis `KEYS` command, which scans the entire keyspace. Avoid using it in production on large databases -- prefer `SCAN` via `execute()` instead.
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.
270
406
 
271
- ### RedisJSON Operations
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.
272
416
 
273
- If your Redis server has the [RedisJSON](https://redis.io/docs/stack/json/) module installed, you can use the `j*` methods for native JSON document operations.
417
+ ---
274
418
 
275
- ```typescript
276
- // Set a JSON document
277
- await redis.jSet({ key: 'doc:1', path: '$', value: { name: 'Alice', scores: [10, 20] } });
419
+ ### IRedisConnection - connection lifecycle
278
420
 
279
- // Get entire document (path defaults to '$')
280
- const doc = await redis.jGet({ key: 'doc:1' });
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 |
281
428
 
282
- // Get a nested path
283
- const scores = await redis.jGet({ key: 'doc:1', path: '$.scores' });
429
+ ---
284
430
 
285
- // Delete a JSON path
286
- await redis.jDelete({ key: 'doc:1', path: '$.scores' });
431
+ ### IRedisKey - key lifecycle and counters
287
432
 
288
- // Delete entire document (path defaults to '$')
289
- await redis.jDelete({ key: 'doc:1' });
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 |
290
444
 
291
- // Increment a number at a path
292
- await redis.jNumberIncreaseBy({ key: 'doc:1', path: '$.counter', value: 5 });
445
+ ---
293
446
 
294
- // Append to a string at a path
295
- await redis.jStringAppend({ key: 'doc:1', path: '$.name', value: ' Smith' });
447
+ ### IRedisKeyValue - string key-value
296
448
 
297
- // Push to an array at a path
298
- await redis.jPush({ key: 'doc:1', path: '$.tags', value: 'new-tag' });
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 `[]` |
299
461
 
300
- // Pop from an array at a path
301
- const popped = await redis.jPop({ key: 'doc:1', path: '$.tags' });
302
- ```
462
+ ---
303
463
 
304
- #### RedisJSON Method Signatures
464
+ ### IRedisHash - hash maps
305
465
 
306
- | Method | Parameters | Returns | Redis Command |
307
- |--------|-----------|---------|---------------|
308
- | `jSet<T>()` | `{ key: string; path: string; value: T }` | `Promise<string \| null>` | `JSON.SET` |
309
- | `jGet<T>()` | `{ key: string; path?: string }` | `Promise<T \| null>` | `JSON.GET` |
310
- | `jDelete()` | `{ key: string; path?: string }` | `Promise<number>` | `JSON.DEL` |
311
- | `jNumberIncreaseBy()` | `{ key: string; path: string; value: number }` | `Promise<string \| null>` | `JSON.NUMINCRBY` |
312
- | `jStringAppend()` | `{ key: string; path: string; value: string }` | `Promise<number[] \| null>` | `JSON.STRAPPEND` |
313
- | `jPush<T>()` | `{ key: string; path: string; value: T }` | `Promise<number[] \| null>` | `JSON.ARRAPPEND` |
314
- | `jPop<T>()` | `{ key: string; path: string }` | `Promise<T \| null>` | `JSON.ARRPOP` |
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 |
315
477
 
316
- `jGet` and `jDelete` default `path` to `'$'` (root) when omitted. All other `j*` methods require `path` explicitly.
478
+ ---
317
479
 
318
- ### Pub/Sub
480
+ ### IRedisSet - sets
319
481
 
320
- The helper supports Redis Pub/Sub for real-time messaging with optional zlib compression.
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 |
321
489
 
322
- ```typescript
323
- // Subscribe to a topic
324
- redis.subscribe({ topic: 'events' });
490
+ ---
325
491
 
326
- // Listen for messages on the underlying ioredis client
327
- redis.getClient().on('message', (channel, message) => {
328
- if (channel === 'events') {
329
- console.log('Received:', JSON.parse(message));
330
- }
331
- });
492
+ ### IRedisList - lists
332
493
 
333
- // Publish to one or more topics
334
- await redis.publish({
335
- topics: ['events', 'audit-log'],
336
- payload: { action: 'user.created', userId: 'u1' },
337
- });
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 });
338
519
 
339
- // Publish with zlib compression
340
- await redis.publish({
341
- topics: ['compressed-channel'],
342
- payload: { large: 'dataset' },
343
- useCompress: true,
520
+ subClient.subscribe({ topic: 'events' });
521
+ subClient.getClient().on('message', (channel, message) => {
522
+ const parsed = JSON.parse(message.toString());
523
+ console.log(channel, parsed);
344
524
  });
345
525
 
346
- // Unsubscribe from a topic
347
- redis.unsubscribe({ topic: 'events' });
526
+ await dataClient.set({ key: 'foo', value: 'bar' });
348
527
  ```
349
528
 
350
- > [!IMPORTANT]
351
- > When using Pub/Sub, the subscribing client enters subscriber mode and can only execute `SUBSCRIBE`, `PSUBSCRIBE`, `UNSUBSCRIBE`, `PUNSUBSCRIBE`, `PING`, and `QUIT` commands. Use a separate `RedisHelper` instance for Pub/Sub if you also need to perform regular data operations.
529
+ ---
352
530
 
353
- ### Raw Command Execution
531
+ ### IRedisJson - RedisJSON module
354
532
 
355
- For commands not wrapped by the helper, use `execute()` to call any Redis command directly.
533
+ Requires the [RedisJSON module](https://redis.io/docs/stack/json/) on the server.
356
534
 
357
- ```typescript
358
- // Execute any Redis command
359
- const result = await redis.execute<string>('SET', ['mykey', 'myvalue', 'EX', 60]);
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`) |
360
544
 
361
- // Command without parameters
362
- const info = await redis.execute<string>('INFO');
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)` |
363
552
 
364
- // SCAN instead of KEYS for production use
553
+ ```typescript
554
+ // Use SCAN instead of KEYS on large keyspaces
365
555
  const [cursor, keys] = await redis.execute<[string, string[]]>(
366
- 'SCAN', [0, 'MATCH', 'user:*', 'COUNT', 100],
556
+ 'SCAN',
557
+ [0, 'MATCH', 'user:*', 'COUNT', 100],
367
558
  );
559
+
560
+ // Fetch server info
561
+ const info = await redis.execute<string>('INFO');
368
562
  ```
369
563
 
370
- ## API Summary
564
+ ## 9. Using the Helper Across IGNIS
371
565
 
372
- | Method | Returns | Description |
373
- |--------|---------|-------------|
374
- | **Connection** | | |
375
- | `connect()` | `Promise<boolean>` | Manual connect (no-op if already connected/connecting/ready) |
376
- | `disconnect()` | `Promise<boolean>` | Graceful disconnect via `QUIT` (no-op if already ended/closed) |
377
- | `ping()` | `Promise<string>` | Health check, returns `'PONG'` |
378
- | `getClient()` | `Redis \| Cluster` | Access the underlying ioredis client |
379
- | **Key-Value** | | |
380
- | `set<T>(opts)` | `Promise<void>` | Set a key with JSON-serialized value. Options: `{ key, value, options?: { log } }` |
381
- | `get<T>(opts)` | `Promise<T \| null>` | Get raw value with optional transform. Options: `{ key, transform? }` |
382
- | `getString(opts)` | `Promise<string \| null>` | Get raw string value. Options: `{ key }` |
383
- | `getObject(opts)` | `Promise<object \| null>` | Get value parsed as JSON. Options: `{ key }` |
384
- | `del(opts)` | `Promise<number>` | Delete one or more keys. Options: `{ keys: string[] }` |
385
- | **Multi-Key** | | |
386
- | `mset<T>(opts)` / `mSet<T>(opts)` | `Promise<void>` | Set multiple key-value pairs. Options: `{ payload: Array<{ key, value }>, options?: { log } }` |
387
- | `mget<T>(opts)` / `mGet<T>(opts)` | `Promise<(T \| null)[]>` | Get multiple values with optional transform. Options: `{ keys, transform? }` |
388
- | `getStrings(opts)` | `Promise<(string \| null)[]>` | Get multiple raw string values. Options: `{ keys }` |
389
- | `getObjects(opts)` | `Promise<(object \| null)[]>` | Get multiple values parsed as JSON. Options: `{ keys }` |
390
- | **Hashes** | | |
391
- | `hset<T>(opts)` / `hSet<T>(opts)` | `Promise<number>` | Set hash fields. Options: `{ key, value: Record<string, unknown>, options?: { log } }` |
392
- | `hgetall(opts)` / `hGetAll(opts)` | `Promise<Record<string, string> \| null>` | Get all hash fields with optional transform. Options: `{ key, transform? }` |
393
- | **Key Scanning** | | |
394
- | `keys(opts)` | `Promise<string[]>` | Find keys matching a glob pattern. Options: `{ key }` |
395
- | **RedisJSON** | | |
396
- | `jSet<T>(opts)` | `Promise<string \| null>` | Set a JSON document at path (`JSON.SET`) |
397
- | `jGet<T>(opts)` | `Promise<T \| null>` | Get a JSON document or path (`JSON.GET`) |
398
- | `jDelete(opts)` | `Promise<number>` | Delete a JSON path (`JSON.DEL`) |
399
- | `jNumberIncreaseBy(opts)` | `Promise<string \| null>` | Increment a number at path (`JSON.NUMINCRBY`) |
400
- | `jStringAppend(opts)` | `Promise<number[] \| null>` | Append to a string at path (`JSON.STRAPPEND`) |
401
- | `jPush<T>(opts)` | `Promise<number[] \| null>` | Push to an array at path (`JSON.ARRAPPEND`) |
402
- | `jPop<T>(opts)` | `Promise<T \| null>` | Pop from an array at path (`JSON.ARRPOP`) |
403
- | **Pub/Sub** | | |
404
- | `subscribe(opts)` | `void` | Subscribe to a topic. Options: `{ topic }` |
405
- | `publish<T>(opts)` | `Promise<void>` | Publish to one or more topics with optional compression. Options: `{ topics, payload, useCompress? }` |
406
- | `unsubscribe(opts)` | `void` | Unsubscribe from a topic. Options: `{ topic }` |
407
- | **Raw** | | |
408
- | `execute<R>(command, parameters?)` | `Promise<R>` | Execute any Redis command directly |
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.
409
567
 
410
- ## Troubleshooting
568
+ ### BullMQ (BullMQHelper)
411
569
 
412
- ### "[execute] Invalid client to execute | command: ..."
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.
413
573
 
414
- **Cause:** `execute()` was called when the ioredis client is `null` or `undefined`. This typically happens if the helper was not properly constructed or the connection was never established.
574
+ `RedisSingleHelper` (and `RedisSentinelHelper`) are BullMQ-compatible out of the box because the framework sets `maxRetriesPerRequest: null` in `buildDefaultOpts`.
415
575
 
416
- **Fix:** Ensure the helper is instantiated correctly and, if using `autoConnect: false`, call `await redis.connect()` before issuing commands.
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 });
417
581
 
418
- ### "[subscribe] Failed to subscribe to topic: ..."
582
+ const queue = BullMQHelper.newInstance({
583
+ queueName: 'email',
584
+ role: 'queue',
585
+ redisConnection: redis,
586
+ });
587
+ ```
419
588
 
420
- **Cause:** The ioredis `subscribe()` callback received an error. This can happen if the client lost its connection or the Redis server rejected the subscription.
589
+ ### Socket.IO component (SocketIOServerHelper)
421
590
 
422
- **Fix:** Check that the Redis server is reachable and the client is in a valid state. Monitor the `onError` callback for connection issues.
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:
423
592
 
424
- ### "[unsubscribe] Failed to unsubscribe from topic: ..."
593
+ ```typescript
594
+ import { AbstractRedisHelper, RedisSentinelHelper } from '@venizia/ignis-helpers';
425
595
 
426
- **Cause:** The ioredis `unsubscribe()` callback received an error, usually due to a broken connection.
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);
599
+ ```
427
600
 
428
- **Fix:** Same as above -- verify connectivity and client state.
601
+ See the Socket.IO component documentation for the full component registration and adapter setup.
429
602
 
430
- ### Connection Refused / Timeout
603
+ ### WebSocket component (WebSocket server and emitter)
431
604
 
432
- **Symptoms:** `ECONNREFUSED`, connection hangs, or `onError` fires immediately.
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:
433
606
 
434
- **Checklist:**
435
- - Verify Redis is running and reachable at the configured `host:port`
436
- - Check firewall rules and network access between your application and the Redis server
437
- - If using `autoConnect: false`, ensure you call `await redis.connect()` before any operations
438
- - Verify `password` is correct (Redis returns a generic error for auth failure)
439
- - For clusters, ensure all node addresses are reachable and the cluster is healthy (`redis-cli cluster info`)
607
+ ```typescript
608
+ import { AbstractRedisHelper, RedisClusterHelper } from '@venizia/ignis-helpers';
440
609
 
441
- ### Pub/Sub Subscriber Mode Conflicts
610
+ const redis = new RedisClusterHelper({ name: 'ws-redis', nodes });
611
+ this.bind<AbstractRedisHelper>({ key: '@app/websocket/redis-connection' }).to(redis);
612
+ ```
442
613
 
443
- **Symptoms:** `ERR only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET allowed in this context`
614
+ See the WebSocket component documentation for the full component registration.
444
615
 
445
- **Cause:** You called `subscribe()` on a client and then attempted a regular command (`get`, `set`, etc.) on the same client.
616
+ ### Casbin authorization enforcer
446
617
 
447
- **Fix:** Use a separate connection for Pub/Sub:
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`:
448
619
 
449
620
  ```typescript
450
- const dataClient = new RedisHelper({ name: 'data', host, port, password });
451
- const subClient = new RedisHelper({ name: 'sub', host, port, password });
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
+ ```
452
634
 
453
- // Use subClient only for subscribe/unsubscribe
454
- subClient.subscribe({ topic: 'events' });
455
- subClient.getClient().on('message', (channel, msg) => { /* ... */ });
635
+ ### Binding into an application
456
636
 
457
- // Use dataClient for everything else
458
- await dataClient.set({ key: 'foo', value: 'bar' });
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
+ }
459
654
  ```
460
655
 
461
- ### RedisJSON Commands Return Errors
656
+ ## 10. Production Notes
462
657
 
463
- **Symptoms:** `ERR unknown command 'JSON.SET'`
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.
464
659
 
465
- **Cause:** The RedisJSON module is not installed on your Redis server.
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.
466
661
 
467
- **Fix:** Install Redis Stack or the RedisJSON module. See [RedisJSON documentation](https://redis.io/docs/stack/json/).
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.
468
663
 
469
- ## See Also
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.
470
665
 
471
- - **Related Concepts:**
472
- - [Services](/guides/core-concepts/services) - Using Redis in services
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.
473
667
 
474
- - **Other Helpers:**
475
- - [Helpers Index](../index) - All available helpers
476
- - [Queue Helper](../queue/) - BullMQ uses Redis as backend
668
+ - **Avoid `keys()` in production.** The Redis `KEYS` command scans the entire keyspace and blocks the server. Use `execute('SCAN', [...])` for production pattern matching.
477
669
 
478
- - **References:**
479
- - [DataSources](/references/base/datasources) - Database connections
670
+ ## Import Reference
480
671
 
481
- - **External Resources:**
482
- - [ioredis Documentation](https://github.com/redis/ioredis) - Redis client library
483
- - [Redis Commands](https://redis.io/commands/) - Redis command reference
484
- - [RedisJSON](https://redis.io/docs/stack/json/) - JSON module documentation
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
+ ```
703
+
704
+ ## See Also
485
705
 
486
- - **Best Practices:**
487
- - [Performance Optimization](/best-practices/performance-optimization) - Caching strategies
488
- - [Security Guidelines](/best-practices/security-guidelines) - Redis security
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