@venizia/ignis-docs 0.2.1-0 → 0.2.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/content/best-practices/architectural-patterns.md +3 -3
  2. package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
  3. package/content/best-practices/contribution-workflow.md +2 -2
  4. package/content/best-practices/error-handling.md +94 -89
  5. package/content/best-practices/security-guidelines.md +5 -5
  6. package/content/extensions/components/api-reference.md +22 -21
  7. package/content/extensions/components/authentication/api.md +64 -28
  8. package/content/extensions/components/authentication/errors.md +19 -5
  9. package/content/extensions/components/authentication/index.md +19 -18
  10. package/content/extensions/components/authentication/usage.md +55 -30
  11. package/content/extensions/components/authorization/api.md +351 -84
  12. package/content/extensions/components/authorization/errors.md +51 -17
  13. package/content/extensions/components/authorization/getting-started.md +227 -0
  14. package/content/extensions/components/authorization/index.md +24 -16
  15. package/content/extensions/components/authorization/usage.md +45 -21
  16. package/content/extensions/components/health-check.md +15 -9
  17. package/content/extensions/components/index.md +24 -90
  18. package/content/extensions/components/mail/api.md +105 -54
  19. package/content/extensions/components/mail/errors.md +12 -10
  20. package/content/extensions/components/mail/index.md +32 -13
  21. package/content/extensions/components/mail/usage.md +26 -18
  22. package/content/extensions/components/request-tracker.md +18 -14
  23. package/content/extensions/components/socket-io/api.md +377 -882
  24. package/content/extensions/components/socket-io/errors.md +49 -51
  25. package/content/extensions/components/socket-io/index.md +72 -88
  26. package/content/extensions/components/socket-io/usage.md +107 -117
  27. package/content/extensions/components/static-asset/api.md +83 -31
  28. package/content/extensions/components/static-asset/errors.md +18 -7
  29. package/content/extensions/components/static-asset/index.md +18 -11
  30. package/content/extensions/components/static-asset/usage.md +11 -6
  31. package/content/extensions/components/template/index.md +3 -3
  32. package/content/extensions/components/websocket/api.md +58 -27
  33. package/content/extensions/components/websocket/errors.md +3 -3
  34. package/content/extensions/components/websocket/index.md +8 -7
  35. package/content/extensions/components/websocket/usage.md +21 -8
  36. package/content/extensions/helpers/cron/index.md +8 -7
  37. package/content/extensions/helpers/crypto/index.md +16 -8
  38. package/content/extensions/helpers/crypto/reference.md +96 -24
  39. package/content/extensions/helpers/env/index.md +14 -10
  40. package/content/extensions/helpers/error/index.md +99 -23
  41. package/content/extensions/helpers/index.md +61 -47
  42. package/content/extensions/helpers/inversion/index.md +23 -6
  43. package/content/extensions/helpers/inversion/reference.md +30 -22
  44. package/content/extensions/helpers/kafka/admin.md +3 -2
  45. package/content/extensions/helpers/kafka/compile-binary.md +69 -44
  46. package/content/extensions/helpers/kafka/consumer.md +24 -21
  47. package/content/extensions/helpers/kafka/examples.md +22 -234
  48. package/content/extensions/helpers/kafka/index.md +30 -62
  49. package/content/extensions/helpers/kafka/producer.md +31 -26
  50. package/content/extensions/helpers/kafka/schema-registry.md +19 -14
  51. package/content/extensions/helpers/logger/hf-logger.md +49 -22
  52. package/content/extensions/helpers/logger/index.md +37 -12
  53. package/content/extensions/helpers/logger/pino.md +31 -11
  54. package/content/extensions/helpers/logger/reference.md +243 -52
  55. package/content/extensions/helpers/network/api.md +65 -30
  56. package/content/extensions/helpers/network/index.md +32 -11
  57. package/content/extensions/helpers/queue/index.md +17 -6
  58. package/content/extensions/helpers/queue/reference.md +52 -25
  59. package/content/extensions/helpers/redis/index.md +29 -11
  60. package/content/extensions/helpers/redis/reference.md +85 -28
  61. package/content/extensions/helpers/secrets/index.md +82 -12
  62. package/content/extensions/helpers/socket-io/api.md +40 -21
  63. package/content/extensions/helpers/socket-io/index.md +26 -10
  64. package/content/extensions/helpers/storage/api.md +42 -24
  65. package/content/extensions/helpers/storage/index.md +10 -7
  66. package/content/extensions/helpers/types/index.md +20 -7
  67. package/content/extensions/helpers/types/reference.md +53 -14
  68. package/content/extensions/helpers/uid/index.md +166 -10
  69. package/content/extensions/helpers/websocket/api.md +52 -13
  70. package/content/extensions/helpers/websocket/index.md +7 -4
  71. package/content/extensions/helpers/worker-thread/index.md +9 -5
  72. package/content/extensions/helpers/worker-thread/reference.md +20 -20
  73. package/content/extensions/index.md +38 -39
  74. package/content/extensions/src-details/mcp-server.md +96 -548
  75. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  76. package/content/guides/core-concepts/persistent/index.md +5 -1
  77. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  78. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  79. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  80. package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
  81. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  82. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  83. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  84. package/content/guides/get-started/philosophy.md +135 -670
  85. package/content/guides/get-started/setup.md +53 -74
  86. package/content/guides/migrations/redis-helpers-migration.md +4 -3
  87. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  88. package/content/guides/migrations/unified-connectors-migration.md +7 -6
  89. package/content/guides/tutorials/realtime-chat.md +1 -1
  90. package/content/references/base/application.md +63 -21
  91. package/content/references/base/components.md +3 -3
  92. package/content/references/base/connectors.md +14 -14
  93. package/content/references/base/controllers.md +12 -12
  94. package/content/references/base/datasources-reference.md +32 -31
  95. package/content/references/base/datasources.md +6 -6
  96. package/content/references/base/dependency-injection.md +12 -11
  97. package/content/references/base/filter-system/application-usage.md +54 -30
  98. package/content/references/base/filter-system/array-operators.md +24 -46
  99. package/content/references/base/filter-system/comparison-operators.md +47 -67
  100. package/content/references/base/filter-system/default-filter.md +59 -53
  101. package/content/references/base/filter-system/fields-order-pagination.md +92 -146
  102. package/content/references/base/filter-system/index.md +25 -13
  103. package/content/references/base/filter-system/json-filtering.md +45 -184
  104. package/content/references/base/filter-system/list-operators.md +23 -53
  105. package/content/references/base/filter-system/logical-operators.md +63 -121
  106. package/content/references/base/filter-system/null-operators.md +34 -104
  107. package/content/references/base/filter-system/pattern-matching.md +40 -55
  108. package/content/references/base/filter-system/quick-reference.md +86 -198
  109. package/content/references/base/filter-system/range-operators.md +18 -46
  110. package/content/references/base/filter-system/tips.md +6 -6
  111. package/content/references/base/filter-system/use-cases.md +33 -15
  112. package/content/references/base/grpc-controllers.md +53 -17
  113. package/content/references/base/index.md +5 -3
  114. package/content/references/base/middlewares.md +11 -10
  115. package/content/references/base/models-reference.md +17 -17
  116. package/content/references/base/models.md +4 -3
  117. package/content/references/base/providers.md +8 -8
  118. package/content/references/base/repositories/advanced.md +224 -326
  119. package/content/references/base/repositories/index.md +19 -6
  120. package/content/references/base/repositories/mixins.md +5 -5
  121. package/content/references/base/repositories/relations.md +160 -293
  122. package/content/references/base/repositories/soft-deletable.md +16 -6
  123. package/content/references/base/secrets.md +17 -13
  124. package/content/references/base/services.md +6 -4
  125. package/content/references/configuration/environment-variables.md +31 -23
  126. package/content/references/configuration/index.md +6 -4
  127. package/content/references/index.md +1 -1
  128. package/content/references/utilities/duration.md +85 -0
  129. package/content/references/utilities/index.md +5 -1
  130. package/content/references/utilities/jsx-reference.md +11 -11
  131. package/content/references/utilities/jsx.md +2 -2
  132. package/content/references/utilities/module.md +78 -25
  133. package/content/references/utilities/request.md +2 -1
  134. package/content/references/utilities/retry.md +139 -0
  135. package/content/references/utilities/schema.md +2 -2
  136. package/content/references/utilities/statuses-reference.md +4 -4
  137. package/content/references/utilities/statuses.md +5 -5
  138. package/dist/mcp-server/index.js +0 -0
  139. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  140. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  141. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  142. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  143. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  144. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  145. package/package.json +17 -16
@@ -6,7 +6,7 @@ difficulty: intermediate
6
6
 
7
7
  # Redis
8
8
 
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.
9
+ The Redis helper gives you one data API for Redis. Talk to a single node, a sharded cluster, or a Sentinel-managed high-availability pair - the calls you write stay the same.
10
10
 
11
11
  ## In one example
12
12
 
@@ -34,7 +34,8 @@ console.log(session); // { userId: 42, active: true }
34
34
 
35
35
  ## How it works
36
36
 
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.
37
+ - **Three topologies, one interface.** `RedisSingleHelper`, `RedisClusterHelper`, and `RedisSentinelHelper` all extend `AbstractRedisHelper` and implement `IRedisHelper`. Only the constructor options differ.
38
+ - **The data API never changes.** `get`, `hSet`, `publish`, and every other method are identical across all three. Code written against `IRedisHelper` works unchanged, whichever topology backs it.
38
39
 
39
40
  | Topology | Class | Fits |
40
41
  |----------|-------|------|
@@ -42,18 +43,35 @@ console.log(session); // { userId: 42, active: true }
42
43
  | Cluster | `RedisClusterHelper` | Data sharded across nodes |
43
44
  | Sentinel | `RedisSentinelHelper` | Automatic failover for one logical master |
44
45
 
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`.
46
+ - **Pick a topology via the factory.** `createRedisHelper({ mode })` picks a topology from configuration. Pass a literal `mode` (`RedisModes.SINGLE | CLUSTER | SENTINEL`) and it returns the concrete class, not the generic `IRedisHelper`.
47
+ - **Connection lifecycle is automatic.** With `autoConnect: true` (the default), the ioredis client starts connecting inside the constructor. Set `autoConnect: false` and call `connect()` yourself instead.
48
+ - **Reconnect backoff grows with each attempt:** 2 s, then 4 s, then 5 s for every attempt after that. A successful reconnect resets it.
49
+ - **The default does not reconnect at all.** `maxRetry` defaults to `0`, which gives up after the first failed attempt. Pass `-1` to reconnect forever.
50
+
51
+ | `maxRetry` | Behavior |
52
+ |---|---|
53
+ | `0` (default) | No reconnect - the client gives up after the first failed attempt |
54
+ | `-1` | Reconnect forever |
55
+ | A positive number | Stop reconnecting after that many attempts |
56
+
57
+ - **BullMQ compatibility differs by topology.** Single and Sentinel helpers always set `maxRetriesPerRequest: null`, which is what BullMQ requires. Cluster does not get this automatically - see the [Full reference](/extensions/helpers/redis/reference).
58
+ - **Values are JSON-serialized automatically.**
59
+
60
+ | Method group | Behavior |
61
+ |---|---|
62
+ | `set`, `mSet`, `jSet`, `jPush` | `JSON.stringify` before writing |
63
+ | `getObject`, `getObjects`, `jGet` | `JSON.parse` on read |
64
+ | `get`, `mGet` | Return the raw stored string, unless you pass `transform` |
65
+
66
+ Wrote a value with `set`? Read it back with `getObject`/`getObjects`.
67
+
50
68
  - **Every method is camelCase.** `hSet`, `lPush`, `sAdd` - even where the underlying ioredis/Redis command is lowercase (`hset`, `lpush`, `sadd`). There is no lowercase alias.
51
69
 
52
70
  ## Common tasks
53
71
 
54
72
  ### Pick a topology
55
73
 
56
- Switch topology by changing `mode` and its matching options; the rest of your code does not change.
74
+ Switch topology by changing `mode` and its matching options. The rest of your code does not change.
57
75
 
58
76
  ```typescript
59
77
  import { createRedisHelper, RedisModes, type IRedisHelper } from '@venizia/ignis-helpers';
@@ -71,7 +89,7 @@ See [Full reference](/extensions/helpers/redis/reference) for the cluster (`node
71
89
 
72
90
  ### Cache a value with a TTL
73
91
 
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.
92
+ `options.expiresIn` on `set` is in **milliseconds**. `ttl()` reports remaining time in **seconds**. Mixing the two up is the most common bug with this helper.
75
93
 
76
94
  ```typescript
77
95
  await redis.set({
@@ -85,7 +103,7 @@ const remaining = await redis.ttl({ key: 'session:42' }); // seconds; -1 = no ex
85
103
 
86
104
  ### Publish and subscribe
87
105
 
88
- A subscribed ioredis connection cannot run regular commands, so use a second helper instance for subscribing and keep the first for data operations.
106
+ A subscribed ioredis connection cannot run regular commands. Use a second helper instance for subscribing, and keep the first for data operations.
89
107
 
90
108
  ```typescript
91
109
  const subscriber = new RedisSingleHelper({ name: 'sub', host: 'localhost', port: 6379, password: 'secret' });
@@ -112,7 +130,7 @@ await redis.lPush({ key: 'queue:emails', values: ['welcome@example.com'] });
112
130
 
113
131
  ### Drop to a raw command
114
132
 
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).
133
+ `execute` calls any ioredis command directly. Reach for it when the typed API doesn't cover a command, like `SCAN`. Prefer `SCAN` over `keys()` in production - `keys()` blocks the server on large keyspaces.
116
134
 
117
135
  ```typescript
118
136
  const [cursor, matched] = await redis.execute<[string, string[]]>('SCAN', [
@@ -21,6 +21,21 @@ Backed by **ioredis** under the hood.
21
21
  - [`packages/helpers/src/modules/redis/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/types.ts) - option and callback types
22
22
  - [`packages/helpers/src/modules/redis/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/common/constants.ts) - `RedisModes`, `RedisSentinelRoles`
23
23
 
24
+ ## Find what you need
25
+
26
+ | You want to | Go to |
27
+ |---|---|
28
+ | See the class hierarchy and the nine capability interfaces | [Class and Interface Model](#class-and-interface-model) |
29
+ | Construct a single-node client | [Construction - Single](#construction---single) |
30
+ | Construct a cluster client | [Construction - Cluster](#construction---cluster) |
31
+ | Construct a Sentinel client, or understand Sentinel HA | [Construction - Sentinel](#construction---sentinel) |
32
+ | Pick a topology from configuration instead of hardcoding a class | [Selecting a Topology - the Factory](#selecting-a-topology---the-factory) |
33
+ | Understand `autoConnect`, the four lifecycle callbacks, or `duplicateClient()` | [Lifecycle and Events](#lifecycle-and-events) |
34
+ | Look up a method's signature and behavior | [Full Method Reference](#full-method-reference) |
35
+ | Wire the helper into BullMQ, Socket.IO, WebSocket, or Casbin | [Using the Helper Across IGNIS](#using-the-helper-across-ignis) |
36
+ | Avoid a production footgun | [Production Notes](#production-notes) |
37
+ | Copy the full import list | [Import Reference](#import-reference) |
38
+
24
39
  ## Class and Interface Model
25
40
 
26
41
  ### Class hierarchy
@@ -33,7 +48,7 @@ Backed by **ioredis** under the hood.
33
48
  | `RedisSentinelHelper` | `AbstractRedisHelper` | ioredis `Redis` client (Sentinel mode); returns `Redis` from `getClient()` |
34
49
 
35
50
  > [!TIP] Typing rule
36
- > 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.
51
+ > Declare parameters and bindings as `IRedisHelper`. Use `isRedisHelper(value)` for runtime checks - it reads a `Symbol.for` brand, so it still answers `true` for a helper built by a second installed copy of the package, where `instanceof AbstractRedisHelper` answers `false`. Never type-check against the concrete subclasses unless you need topology-specific behavior.
37
52
 
38
53
  ### TRedisClient
39
54
 
@@ -101,10 +116,11 @@ Combines `IRedisSingleHelperProps` and `IRedisHelperCallbacks`.
101
116
  | `name` | `string` | Yes | - | Helper identifier used in logs and as `IRedisHelper.name` |
102
117
  | `host` | `string` | Yes | - | Redis server hostname |
103
118
  | `port` | `string \| number` | Yes | - | Redis server port |
119
+ | `user` | `string` | No | - | Accepted by the type but not read by the constructor - has no effect. Set auth via `password` only. |
104
120
  | `password` | `string` | Yes | - | Redis `requirepass` value |
105
121
  | `database` | `number` | No | `0` | Redis database index (0-15) |
106
122
  | `autoConnect` | `boolean` | No | `true` | Connect immediately; `false` uses ioredis `lazyConnect` |
107
- | `maxRetry` | `number` | No | `0` | Max reconnect attempts; `0` = unlimited; `-1` = no retry |
123
+ | `maxRetry` | `number` | No | `0` | Reconnect attempts before giving up. `0` (default) = no reconnect; `-1` = reconnect forever |
108
124
  | `onInitialized` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired synchronously after construction |
109
125
  | `onConnected` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired on TCP connection established |
110
126
  | `onReady` | `(opts: { name: string; helper: IRedisHelper }) => void` | No | - | Fired when client is ready for commands |
@@ -112,10 +128,31 @@ Combines `IRedisSingleHelperProps` and `IRedisHelperCallbacks`.
112
128
 
113
129
  ### Retry strategy
114
130
 
115
- - **Backoff formula.** `Math.max(Math.min(attempt * 2000, 5000), 1000)` - starting at 1 s, capped at 5 s.
116
- - **Stop condition.** Reconnect stops when `attempt > maxRetry` (if `maxRetry > -1`).
131
+ ioredis calls the strategy with `attempt` starting at `1`, so the first reconnect waits 2 s.
132
+
133
+ | `attempt` | Delay |
134
+ |---|---|
135
+ | 1 | 2 s |
136
+ | 2 | 4 s |
137
+ | 3 and later | 5 s |
138
+
139
+ - **Backoff formula.** `Math.max(Math.min(attempt * 2000, 5000), 1000)`. The `1000` floor is unreachable, because the smallest `attempt` is `1`.
140
+ - **Backoff resets on success.** ioredis sets `retryAttempts` back to `0` once the client reaches `ready`.
141
+ - **Stop condition.** Reconnect stops when `attempt > maxRetry`, unless `maxRetry` is `-1`. Under the default `maxRetry: 0` that fires on the very first attempt, so the client never reconnects.
142
+ - **This strategy does not retry commands.** It only schedules reconnection.
117
143
  - **BullMQ requirement.** The framework always sets `maxRetriesPerRequest: null` internally, which BullMQ requires.
118
144
 
145
+ ### What happens to commands while the connection is down
146
+
147
+ `maxRetriesPerRequest: null` turns off the per-request retry limit, so ioredis parks commands in its offline queue instead of failing them after N attempts. How that ends depends on `maxRetry`.
148
+
149
+ | `maxRetry` | Outcome for queued commands |
150
+ |---|---|
151
+ | `-1` or a positive number | Flushed to the server once the client reconnects |
152
+ | `0` (default) | Rejected with `Connection is closed.` as soon as the client gives up |
153
+
154
+ To survive a restart or a failover without losing commands, set `maxRetry: -1`.
155
+
119
156
  ## Construction - Cluster
120
157
 
121
158
  `Source ->` [`packages/helpers/src/modules/redis/cluster/cluster.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/redis/cluster/cluster.helper.ts)
@@ -151,7 +188,7 @@ const cluster = new RedisClusterHelper({
151
188
  > [!WARNING] No framework defaults injected
152
189
  > Cluster does not apply the backoff retry strategy or `maxRetriesPerRequest: null` automatically. Pass those inside `clusterOptions.redisOptions` if your consumers (e.g. BullMQ) require them.
153
190
 
154
- `duplicateClient()` on a cluster creates a **new** `Cluster` instance from the same startup nodes and options (because ioredis `Cluster` does not implement `.duplicate()`).
191
+ `duplicateClient()` on a cluster creates a **new** `Cluster` instance from the same startup nodes and options. ioredis `Cluster` doesn't implement `.duplicate()`, so the helper builds a fresh instance instead.
155
192
 
156
193
  ## Construction - Sentinel
157
194
 
@@ -180,9 +217,9 @@ Redis Sentinel is a high-availability (HA) architecture made of three process ty
180
217
  ### Why your app connects to sentinels, not the master
181
218
 
182
219
  - **The master's address can change after a failover.** Hard-coding a master host would break on every failover.
183
- - **You give ioredis the sentinel addresses instead**, plus the monitored master group name (`masterName`). ioredis asks a sentinel for the current master and connects to it.
220
+ - **You give ioredis the sentinel addresses instead**, plus the monitored master group name (`masterName`). ioredis asks a sentinel for the current master, then connects to it.
184
221
  - **ioredis re-queries sentinels automatically after a failover** - no manual reconnection logic needed.
185
- - **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.
222
+ - **Your application code does not change during a failover.** The only observable effect is a few seconds of transient command failures. Set `maxRetry: -1` so the client reconnects and drains its queued commands - the default `maxRetry: 0` rejects them instead.
186
223
 
187
224
  ### Constructor
188
225
 
@@ -219,7 +256,7 @@ const redis = new RedisSentinelHelper({
219
256
  | `sentinelUsername` | `string` | No | - | Sentinel ACL username |
220
257
  | `database` | `number` | No | `0` | Redis database index |
221
258
  | `autoConnect` | `boolean` | No | `true` | Connect immediately |
222
- | `maxRetry` | `number` | No | `0` | Max reconnect attempts |
259
+ | `maxRetry` | `number` | No | `0` | Reconnect attempts before giving up. `0` (default) = no reconnect; `-1` = reconnect forever. See [Retry strategy](#retry-strategy) |
223
260
  | `redisOptions` | `Partial<RedisOptions>` | No | - | Extra ioredis options; first-class fields above always override matching keys here |
224
261
  | `onInitialized` / `onConnected` / `onReady` / `onError` | callbacks | No | - | Same shape as single |
225
262
 
@@ -236,7 +273,7 @@ They are independent. You can name the helper `'ha-cache'` while `masterName` is
236
273
 
237
274
  ### The authentication model - 4 relationships
238
275
 
239
- Sentinel deployments have four separate authentication relationships. The app only configures two of them; the other two are server-side.
276
+ Sentinel deployments have four separate authentication relationships. The app configures two of them. The other two are server-side.
240
277
 
241
278
  | # | From | To | App field | Server config |
242
279
  |---|------|----|-----------|---------------|
@@ -245,7 +282,7 @@ Sentinel deployments have four separate authentication relationships. The app on
245
282
  | 3 | **Sentinel** | **Data nodes** | (not set by app) | `sentinel auth-pass <group> <pass>` in sentinel.conf |
246
283
  | 4 | **Replica** | **Master** (replication) | (not set by app) | `masterauth` in redis.conf |
247
284
 
248
- **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`.
285
+ **Relationship 3 is the silent failure.** If the data nodes require a password but `sentinel.conf` has no `sentinel auth-pass`, the sentinel processes can't check master health. They trigger false failovers instead. Configure `sentinel auth-pass` on the server side, even when your app sets only `password`.
249
286
 
250
287
  **Common cases:**
251
288
  - Private network, sentinels unauthenticated: set only `password`.
@@ -258,7 +295,7 @@ When the master fails:
258
295
  1. Sentinels reach quorum and promote a replica.
259
296
  2. ioredis detects the master change via sentinel notification.
260
297
  3. ioredis reconnects to the new master transparently.
261
- 4. Any in-flight commands during the few-second window may fail transiently - the retry strategy retries them.
298
+ 4. Commands issued during the few-second window queue up, then flush once the client reconnects. This needs `maxRetry: -1` or a positive `maxRetry`; the default `0` rejects them.
262
299
  5. **Your application code does not change.** The same `RedisSentinelHelper` instance continues to work after failover.
263
300
 
264
301
  ### Local testing with Docker Compose
@@ -426,10 +463,10 @@ const dedicated = redis.duplicateClient();
426
463
  ```
427
464
 
428
465
  - **Creates an independent ioredis connection** from the same configuration.
429
- - **Implementation.** `AbstractRedisHelper.duplicateClient()` calls ioredis `client.duplicate()`, which copies the parent's `options` - including `lazyConnect`.
430
- - **Lazy or immediate.** `lazyConnect` is set to `!autoConnect`. With the default `autoConnect: true` (`lazyConnect: false`) the duplicate connects immediately; it is lazy (connects on first use) only when the parent was created with `autoConnect: false`.
466
+ - **Implementation.** `AbstractRedisHelper.duplicateClient()` calls ioredis `client.duplicate()`. That copies the parent's `options`, including `lazyConnect`.
467
+ - **Lazy or immediate.** `lazyConnect` is set to `!autoConnect`. With the default `autoConnect: true`, the duplicate connects immediately (`lazyConnect: false`). With `autoConnect: false`, the duplicate is lazy - it connects on first use.
431
468
  - **When to use it.** Whenever a consumer needs its own dedicated connection - Pub/Sub, BullMQ, Socket.IO adapters.
432
- - **Cluster is different.** For `RedisClusterHelper`, `duplicateClient()` instead constructs a new `Cluster` from the same startup nodes and options (ioredis `Cluster` has no `.duplicate()`).
469
+ - **Cluster is different.** For `RedisClusterHelper`, `duplicateClient()` builds a new `Cluster` from the same startup nodes and options instead. ioredis `Cluster` has no `.duplicate()`.
433
470
 
434
471
  ## Full Method Reference
435
472
 
@@ -437,11 +474,31 @@ const dedicated = redis.duplicateClient();
437
474
 
438
475
  ### General notes (apply to all families)
439
476
 
440
- - **Empty-input no-ops:** Methods that accept an array (`keys`, `members`, `values`, `fields`, `payload`) short-circuit immediately when the array is empty, without calling ioredis - returning `0`, `[]`, or (for `mSet`) resolving `void`, per each method's return type. This prevents the "wrong number of arguments" error ioredis throws on empty varargs.
441
- - **Boolean mapping:** `expire`, `expireAt`, `persist`, `hExists`, `sIsMember` map ioredis numeric replies to `true` (`=== 1`) / `false`.
442
- - **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`.
443
- - **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`).
444
- - **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.
477
+ - **Empty-input no-ops.** `keys`, `members`, `values`, `fields`, and `payload` are all array inputs. An empty array skips the ioredis call and returns immediately - this avoids the "wrong number of arguments" error ioredis throws on empty varargs.
478
+
479
+ | Return value | Applies to |
480
+ |---|---|
481
+ | `0` | Count methods - `del`, `hDel`, `sRem`, `sAdd`, `lPush`, `rPush` |
482
+ | `[]` | Array reads - `mGet`, `sMembers` |
483
+ | `void` (no-op) | `mSet` |
484
+ - **Boolean mapping.** `expire`, `expireAt`, `persist`, `hExists`, and `sIsMember` map ioredis numeric replies to `true` (`=== 1`) or `false`.
485
+ - **JSON auto-serialization.**
486
+
487
+ | Method group | Behavior |
488
+ |---|---|
489
+ | `set`, `mSet`, `jSet`, `jPush` | `JSON.stringify` before writing |
490
+ | `getObject`, `getObjects`, `jGet` | `JSON.parse` on read |
491
+ | `get`, `mGet` | Return the raw string, unless you pass `transform` |
492
+
493
+ - **TTL units differ by field.**
494
+
495
+ | Field | Unit | ioredis command |
496
+ |---|---|---|
497
+ | `set.options.expiresIn` | milliseconds | `PX` |
498
+ | `expire.seconds` | seconds | `EXPIRE` |
499
+ | `expireAt.atEpochSeconds` | Unix epoch seconds | `EXPIREAT` |
500
+
501
+ - **Pub/Sub errors.** On a subscription callback error, the helper logs the error and returns. It never throws - throwing inside an ioredis async callback would be an unhandled rejection.
445
502
 
446
503
  ### IRedisConnection - connection lifecycle
447
504
 
@@ -521,7 +578,7 @@ const dedicated = redis.duplicateClient();
521
578
 
522
579
  | Method | Signature | Behavior |
523
580
  |--------|-----------|----------|
524
- | `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) |
581
+ | `publish<T>` | `(opts: { topics: string[]; payload: T; useCompress?: boolean }): Promise<void>` | JSON-serializes `payload` into a `Buffer`, optionally zlib-deflates it, and publishes to each topic. Filters out empty/blank topics first; if none remain, logs an error and returns without publishing |
525
582
  | `subscribe` | `(opts: { topic: string }): void` | Subscribes the client to `topic`; on subscription error the helper **logs** it and does not throw |
526
583
  | `unsubscribe` | `(opts: { topic: string }): void` | Unsubscribes from `topic`; on error the helper **logs** it and does not throw |
527
584
 
@@ -575,15 +632,15 @@ const info = await redis.execute<string>('INFO');
575
632
 
576
633
  ## Using the Helper Across IGNIS
577
634
 
578
- 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.
635
+ All three topologies implement `IRedisHelper` and are interchangeable in every integration below. Switch topology by changing the helper you construct. The consumer code does not change.
579
636
 
580
637
  ### BullMQ (BullMQHelper)
581
638
 
582
639
  `BullMQHelper` accepts a `redisConnection: IRedisHelper`. Internally it:
583
- - Calls `redisConnection.duplicateClient()` to create dedicated `Queue` and `Worker` connections (BullMQ requires separate connections per role).
640
+ - Calls `redisConnection.duplicateClient()` to create dedicated `Queue` and `Worker` connections. BullMQ requires a separate connection per role.
584
641
  - Calls `redisConnection.getClient() instanceof Cluster` to detect cluster topology and adjust BullMQ configuration.
585
642
 
586
- `RedisSingleHelper` (and `RedisSentinelHelper`) are BullMQ-compatible out of the box because the framework sets `maxRetriesPerRequest: null` in `buildDefaultOpts`.
643
+ `RedisSingleHelper` and `RedisSentinelHelper` are BullMQ-compatible out of the box. The framework sets `maxRetriesPerRequest: null` in `buildDefaultOpts`.
587
644
 
588
645
  ```typescript
589
646
  import { BullMQHelper } from '@venizia/ignis-helpers/bullmq';
@@ -617,7 +674,7 @@ See the Socket.IO component documentation for the full component registration an
617
674
  ### WebSocket component (WebSocket server and emitter)
618
675
 
619
676
  - **Resolved from a binding key.** The WebSocket component resolves an `AbstractRedisHelper` from `@app/websocket/redis-connection`.
620
- - **Two dedicated connections.** `WebSocketServerHelper` calls `duplicateClient()` for its pub and sub connections; the WebSocket emitter calls `duplicateClient()` for its own pub connection.
677
+ - **Two dedicated connections.** `WebSocketServerHelper` calls `duplicateClient()` for its pub and sub connections. The WebSocket emitter calls `duplicateClient()` again for its own pub connection.
621
678
  - **Any topology works.** Bind single, cluster, or Sentinel:
622
679
 
623
680
  ```typescript
@@ -632,7 +689,7 @@ See the WebSocket component documentation for the full component registration.
632
689
  ### Casbin authorization enforcer
633
690
 
634
691
  - **Configured via options.** The cached Casbin enforcer (`ICasbinEnforcerCachedRedis`) accepts `connection: IRedisHelper` inside its `options`.
635
- - **Uses only the typed API.** It calls the helper's `get`, `set` (with `options.expiresIn`), and `del` to read, write, and invalidate cached policy keys - it never reaches the raw client.
692
+ - **Uses only the typed API.** It calls the helper's `get`, `set` (with `options.expiresIn`), and `del` to read, write, and invalidate cached policy keys. It never reaches the raw client.
636
693
  - **Any topology works** as the `connection`:
637
694
 
638
695
  ```typescript
@@ -674,10 +731,10 @@ class MyApp extends BaseApplication {
674
731
  ## Production Notes
675
732
 
676
733
  - **Use a singleton per logical connection.** Construct the helper once at startup and share it. Do not construct a new helper per request - each construction opens a new connection pool.
677
- - **`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.
734
+ - **`duplicateClient()` for Pub/Sub.** A subscribed ioredis connection cannot run normal commands. Always call `duplicateClient()` to get a dedicated subscriber connection. Keep the main client for data operations.
678
735
  - **Sentinel for HA; Cluster for sharding.** These are different problems. Sentinel gives you automatic failover for a single logical master. Cluster shards data across nodes. They are not interchangeable.
679
- - **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.
680
- - **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.
736
+ - **TTL units differ by field** - see the [units table above](#general-notes-apply-to-all-families). Mixing them up causes unexpected key expirations.
737
+ - **Sentinel `sentinel auth-pass` is a server-side requirement.** If your data nodes require a password, configure `sentinel auth-pass <group> <password>` in `sentinel.conf`, in addition to the app-side `password` field. Missing it causes false failovers, even when `password` is correct.
681
738
  - **Avoid `keys()` in production.** The Redis `KEYS` command scans the entire keyspace and blocks the server. Use `execute('SCAN', [...])` for production pattern matching.
682
739
 
683
740
  ## Import Reference
@@ -6,10 +6,10 @@ difficulty: intermediate
6
6
 
7
7
  # Secrets & Vault
8
8
 
9
- The Secrets helper family reads configuration and credentials from a backend - HashiCorp Vault, an encrypted `.env.vault`, or plain `process.env` - behind one `ISecretsHelper` interface. Only HashiCorp mints dynamic, rotating credentials; the other two are static.
9
+ The Secrets helper family reads configuration and credentials from a backend, behind one `ISecretsHelper` interface. Swap the provider, and the calling code stays the same.
10
10
 
11
11
  > [!TIP] In an application, you rarely build a provider by hand
12
- > An IGNIS app enables secrets by overriding `registerSecrets()` on its application class; the framework builds the provider, hydrates static secrets into `Envs`, and wires rotation for you. See the [Secrets & Vault deep dive](/references/base/secrets) and the [guide](/guides/core-concepts/secrets-vault). This page documents the provider family itself.
12
+ > An IGNIS app enables secrets by overriding `registerSecrets()` on its application class. The framework then builds the provider, hydrates static secrets into `Envs`, and wires rotation for you. See the [Secrets & Vault deep dive](/references/base/secrets) and the [guide](/guides/core-concepts/secrets-vault). This page documents the provider family itself.
13
13
 
14
14
  ## In one example
15
15
 
@@ -24,13 +24,9 @@ await secrets.configure();
24
24
  const value = await secrets.get({ path: 'ignored', key: 'APP_ENV_DB_PASSWORD' });
25
25
  ```
26
26
 
27
- Swap `provider` for `SecretProviders.HASHICORP_VAULT` (with a `config: { endpoint, auth }`) or `SecretProviders.DOTENV_VAULT` to change the backend - the calling code stays the same.
27
+ Swap `provider` for `SecretProviders.HASHICORP_VAULT` (with a `config: { endpoint, auth }`) or `SecretProviders.DOTENV_VAULT` to change the backend.
28
28
 
29
- ## How it works
30
-
31
- - **One base, three providers.** `AbstractSecretsHelper` owns the provider-agnostic machinery (TTL cache, lease registry, renewal scheduler, rotation dispatch). Concrete providers implement only the raw fetch/renew/revoke calls.
32
- - **Two secret classes.** Static secrets (`get`/`getBundle`) are TTL-cached. Dynamic, lease-bearing secrets (`lease`) are renewed automatically and drive rotation - only HashiCorp supports them.
33
- - **Factory selection.** `createSecretsHelper({ provider })` picks the tier; the peer-backed providers are reached only through a bundler-invisible dynamic import (`importOptionalModule`), so importing the package never requires `node-vault` or `@dotenvx/dotenvx` - not even when the application is compiled into a binary with `Bun.build`, which resolves literal dynamic imports at bundle time. No `external` entry is needed in the compile step.
29
+ ## The three providers
34
30
 
35
31
  | Provider | `SecretProviders` value | Kind | Optional peer |
36
32
  |----------|-------------------------|------|---------------|
@@ -38,6 +34,19 @@ Swap `provider` for `SecretProviders.HASHICORP_VAULT` (with a `config: { endpoin
38
34
  | `HashiCorpVaultHelper` | `hashicorp-vault` | KV + dynamic + rotation | `node-vault` (`@venizia/ignis-helpers/hashicorp-vault`) |
39
35
  | `DotenvVaultHelper` | `dotenv-vault` | Static (encrypted `.env.vault`) | `@dotenvx/dotenvx` (`@venizia/ignis-helpers/dotenv-vault`) |
40
36
 
37
+ Only HashiCorp mints dynamic, lease-bearing credentials. The other two are read-only snapshots.
38
+
39
+ ## How it works
40
+
41
+ - **One base class, three providers.** `AbstractSecretsHelper` owns the provider-agnostic machinery: a TTL cache, a lease registry, a renewal scheduler, and rotation dispatch. Each concrete provider implements only the raw fetch/renew/revoke calls.
42
+ - **Static reads are TTL-cached.** `get()` and `getBundle()` cache by path, for `cacheTtlSeconds` (default `300`).
43
+ - **The cache evicts lazily, not on a timer.** An expired entry is only cleared the next time you call `get()` or `getBundle()` for that path - there's no background sweep.
44
+ - **Dynamic secrets renew themselves.** `lease()` opens a lease-bearing secret, and schedules its own renewal at `ttlSeconds * renewBeforeRatio` (default ratio `0.66`). Only HashiCorp supports `lease()` - the static providers throw.
45
+ - **A failed renewal retries with backoff, then rotates.** If Vault is unreachable, the scheduler retries with capped exponential backoff. Once a renewal genuinely can't extend the lease, the helper fetches a fresh one and dispatches rotation.
46
+ - **`createSecretsHelper({ provider })` picks the concrete class for you.**
47
+ - **The peer packages stay invisible to bundlers.** `node-vault` and `@dotenvx/dotenvx` load only through a dynamic import (`ModuleUtility.load`). Importing `@venizia/ignis-helpers` never requires either package.
48
+ - **This holds under `Bun.build` too.** A literal dynamic import resolves at bundle time, so a compiled binary needs no `external` entry for either peer.
49
+
41
50
  ## The `ISecretsHelper` interface
42
51
 
43
52
  | Method | Purpose |
@@ -52,10 +61,71 @@ Swap `provider` for `SecretProviders.HASHICORP_VAULT` (with a `config: { endpoin
52
61
 
53
62
  ## Common tasks
54
63
 
55
- - **Pick a provider.** `createSecretsHelper({ provider: SecretProviders.HASHICORP_VAULT, config: { endpoint, auth: { method: 'app-role', roleId, secretId } } })`. Install the peer first (`bun add node-vault`).
56
- - **Read a static value or a bundle.** `await secrets.get({ path, key })` / `await secrets.getBundle({ path })`.
57
- - **Open a dynamic, rotating credential.** `await secrets.lease({ path: 'database/creds/app-role', key: 'datasources.PostgresDataSource' })` - the provider renews it and emits `onRotate` when the backend issues fresh credentials.
58
- - **Static providers reject `lease()`.** `SystemEnvsHelper` and `DotenvVaultHelper` throw NotSupported - only HashiCorp has dynamic secrets.
64
+ ### Read a static value or a bundle
65
+
66
+ ```typescript
67
+ const password = await secrets.get({ path: 'secret/data/app', key: 'DB_PASSWORD' });
68
+ const bundle = await secrets.getBundle({ path: 'secret/data/app' });
69
+ ```
70
+
71
+ ### Connect to HashiCorp Vault
72
+
73
+ Install the peer first: `bun add node-vault`. Vault supports three auth methods.
74
+
75
+ | `auth.method` | Fields |
76
+ |---|---|
77
+ | `token` | `token` |
78
+ | `app-role` | `roleId`, `secretId`, `mountPath?` |
79
+ | `kubernetes` | `role`, `jwtPath?`, `mountPath?` |
80
+
81
+ ```typescript
82
+ const secrets = await createSecretsHelper({
83
+ provider: SecretProviders.HASHICORP_VAULT,
84
+ config: {
85
+ endpoint: 'https://vault.internal:8200',
86
+ auth: { method: 'app-role', roleId, secretId },
87
+ },
88
+ });
89
+ await secrets.configure();
90
+ ```
91
+
92
+ ### Open a dynamic, rotating credential
93
+
94
+ ```typescript
95
+ const lease = await secrets.lease({
96
+ path: 'database/creds/app-role',
97
+ key: 'datasources.PostgresDataSource',
98
+ });
99
+
100
+ secrets.onRotate(({ key, lease }) => {
101
+ logger.for('secrets').info('Rotated | key: %s | ttl: %d', key, lease.ttlSeconds);
102
+ });
103
+ ```
104
+
105
+ ### Rebuild a live consumer when its secret rotates
106
+
107
+ `registerRotatable` connects a pool - or any `ISecretRotatable` - directly to a lease key. It rebuilds on rotation without going through `onRotate`.
108
+
109
+ ```typescript
110
+ secrets.registerRotatable({ key: 'datasources.PostgresDataSource', target: pool });
111
+ ```
112
+
113
+ ### Read a static `.env.vault`
114
+
115
+ ```typescript
116
+ const secrets = await createSecretsHelper({
117
+ provider: SecretProviders.DOTENV_VAULT,
118
+ config: { path: '.env.vault', dotenvKey: process.env.DOTENV_KEY },
119
+ });
120
+ ```
121
+
122
+ ### Shut down cleanly
123
+
124
+ `shutdown()` clears every renewal timer and revokes every outstanding lease. Call it on application stop.
125
+
126
+ ```typescript
127
+ await secrets.shutdown();
128
+ ```
59
129
 
60
130
  ## See also
61
131
 
@@ -16,6 +16,19 @@ Exhaustive reference for `SocketIOServerHelper` and `SocketIOClientHelper`. For
16
16
  - [`packages/helpers/src/modules/socket/socket-io/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/common/constants.ts) - `SocketIOConstants`, `SocketIOClientStates`
17
17
  - [`packages/helpers/src/modules/socket/socket-io/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/index.ts) - barrel export (`@venizia/ignis-helpers/socket-io`)
18
18
 
19
+ ## Find what you need
20
+
21
+ | You want to | Go to |
22
+ |---|---|
23
+ | See the full server and client call sequence | [Architecture](#architecture) |
24
+ | Look up a server method (`send()`, `on()`, `getClients()`, ...) | [Server API](#server-api) |
25
+ | Look up a client method (`emit()`, `subscribe()`, `joinRooms()`, ...) | [Client API](#client-api) |
26
+ | Follow the auth handshake step by step | [Authentication Protocol](#authentication-protocol) |
27
+ | Understand the three Redis connections | [Redis Adapter](#redis-adapter) |
28
+ | Look up a type, option, or callback signature | [Types Reference](#types-reference) |
29
+ | Look up an event name, room default, or client state | [Constants](#constants) |
30
+ | Match an error message to its cause | [Common Errors](#common-errors) |
31
+
19
32
  ## Architecture
20
33
 
21
34
  ```
@@ -96,19 +109,21 @@ Extends `BaseHelper`. Manages a `socket.io` server with a Redis adapter, authent
96
109
 
97
110
  #### `constructor(opts: TSocketIOServerOptions)`
98
111
 
99
- Validates the runtime-specific `server` (Node) or `engine` (Bun) field and duplicates the provided `redisConnection` into three independent clients (`redisPub`, `redisSub`, `redisEmitter`). Does **not** start the IO server - call `configure()` to complete initialization.
112
+ Validates the runtime-specific `server` (Node) or `engine` (Bun) field. Duplicates the provided `redisConnection` into three independent clients (`redisPub`, `redisSub`, `redisEmitter`). Does **not** start the IO server - call `configure()` to complete initialization.
100
113
 
101
114
  **Throws:**
102
115
 
103
- - `'[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!'` - `runtime: 'node'` and `server` is falsy
104
- - `'[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!'` - `runtime: 'bun'` and `engine` is falsy
105
- - `'[SocketIOServerHelper] Unsupported runtime!'` - `runtime` is neither `'node'` nor `'bun'`
106
- - `'Invalid redis connection to config socket.io adapter!'` - `redisConnection` is falsy
116
+ | Message | Condition |
117
+ |---------|-----------|
118
+ | `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!` | `runtime: 'node'` and `server` is falsy |
119
+ | `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!` | `runtime: 'bun'` and `engine` is falsy |
120
+ | `[SocketIOServerHelper] Unsupported runtime!` | `runtime` is neither `'node'` nor `'bun'` |
121
+ | `Invalid redis connection to config socket.io adapter!` | `redisConnection` is falsy |
107
122
 
108
123
  #### `configure(): Promise<void>`
109
124
 
110
125
  1. Registers `error` listeners on all three duplicated Redis clients (logged, not thrown)
111
- 2. Kicks off `connect()` on any client still in `'wait'` status (duplicated clients inherit `lazyConnect` from the parent and do not dial on their own)
126
+ 2. Kicks off `connect()` on any client still in `'wait'` status
112
127
  3. `await Promise.all([waitForRedisReady(redisPub), waitForRedisReady(redisSub), waitForRedisReady(redisEmitter)])`
113
128
  4. Creates the `IOServer` - `new IOServer(server, serverOptions)` for Node.js, or `new IOServer()` followed by `io.bind(engine)` for Bun
114
129
  5. Attaches the Redis adapter via `@socket.io/redis-adapter`
@@ -118,7 +133,7 @@ Validates the runtime-specific `server` (Node) or `engine` (Bun) field and dupli
118
133
  Must be called before `on()`, `send()`, or any server operation.
119
134
 
120
135
  > [!NOTE]
121
- > `waitForRedisReady` rejects after **30 seconds** if a client never reaches `ready` (or immediately on that client's `error` event), so a broken Redis connection fails `configure()` instead of hanging boot indefinitely.
136
+ > `waitForRedisReady` rejects after **30 seconds** if a client never reaches `ready` (or immediately on that client's `error` event). A broken Redis connection fails `configure()` instead of hanging boot indefinitely.
122
137
 
123
138
  #### `getIOServer(): IOServer`
124
139
 
@@ -159,13 +174,15 @@ socketServer.on({
159
174
 
160
175
  **Throws:**
161
176
 
162
- - `'[on] Invalid topic to start binding handler'` - `topic` is empty/falsy
163
- - `'[on] Invalid event handler | topic: {topic}'` - `handler` is missing
164
- - `'[on] IOServer is not initialized yet!'` - called before `configure()` completes
177
+ | Message | Condition |
178
+ |---------|-----------|
179
+ | `[on] Invalid topic to start binding handler` | `topic` is empty/falsy |
180
+ | `[on] Invalid event handler \| topic: {topic}` | `handler` is missing |
181
+ | `[on] IOServer is not initialized yet!` | called before `configure()` completes |
165
182
 
166
183
  #### `onClientConnect(opts: { socket: IOSocket }): void`
167
184
 
168
- Handles a new socket connection. Invoked automatically by the `'connection'` event; can also be called manually.
185
+ Handles a new socket connection. Invoked automatically by the `'connection'` event, but can also be called manually.
169
186
 
170
187
  1. Creates an `ISocketIOClient` entry with state `UNAUTHORIZED`
171
188
  2. Starts the `authenticateTimeout` timer
@@ -241,11 +258,11 @@ Extends `BaseHelper`. Manages a `socket.io-client` connection with authenticatio
241
258
 
242
259
  #### `constructor(opts: ISocketIOClientOptions)`
243
260
 
244
- Stores the callbacks and calls `configure()` internally - the client starts connecting immediately.
261
+ Stores the callbacks and calls `configure()` internally. The client starts connecting immediately.
245
262
 
246
263
  #### `configure(): void`
247
264
 
248
- Creates the `socket.io-client` connection (`io(host, options)`) and registers the internal lifecycle handlers below. Called automatically by the constructor; a second call is a no-op if a client instance already exists.
265
+ Creates the `socket.io-client` connection (`io(host, options)`) and registers the internal lifecycle handlers below. Called automatically by the constructor. A second call is a no-op if a client instance already exists.
249
266
 
250
267
  | Event | Behavior |
251
268
  |-------|----------|
@@ -294,7 +311,7 @@ const client = new SocketIOClientHelper({
294
311
 
295
312
  #### `subscribe<T>(opts: { event: string; handler: TSocketIOEventHandler<T>; ignoreDuplicate?: boolean }): void`
296
313
 
297
- Registers a handler on the client socket. The handler is wrapped with error handling that catches both sync throws and async rejections, and the wrapper is tracked in an internal `wrappedHandlers` map so `unsubscribe()` can find and remove it.
314
+ Registers a handler on the client socket. The handler is wrapped with error handling that catches both sync throws and async rejections. That wrapper is tracked in an internal `wrappedHandlers` map, so `unsubscribe()` can find and remove it.
298
315
 
299
316
  | Parameter | Type | Default | Description |
300
317
  |-----------|------|---------|-------------|
@@ -317,7 +334,7 @@ client.subscribeMany({
317
334
 
318
335
  #### `unsubscribe(opts: { event: string; handler?: TSocketIOEventHandler }): void`
319
336
 
320
- - With `handler`, removes only that specific handler (looked up in `wrappedHandlers`; no-op if it was not registered through this helper)
337
+ - With `handler`, removes only that specific handler (looked up in `wrappedHandlers`). No-op if that handler was not registered through this helper
321
338
  - Without `handler`, removes **all** handlers for the event
322
339
  - No-op if the event has no listeners
323
340
 
@@ -347,21 +364,23 @@ Emits an event to the server.
347
364
  | `doLog` | `boolean` | `false` | Logs the emission details |
348
365
  | `callback` | `() => void` | `undefined` | Invoked via `setImmediate` after the emit call |
349
366
 
350
- **Throws:**
367
+ **Throws** (both status `400`):
351
368
 
352
- - `'Invalid socket client state to emit'` (status 400) - the client is not connected
353
- - `'Topic is required to emit'` (status 400) - `topic` is empty/falsy
369
+ | Message | Condition |
370
+ |---------|-----------|
371
+ | `Invalid socket client state to emit` | the client is not connected |
372
+ | `Topic is required to emit` | `topic` is empty/falsy |
354
373
 
355
374
  #### `joinRooms(opts: { rooms: string[] }): void`
356
375
 
357
- Emits `'join'` with `{ rooms }`; the server validates the request through its `validateRoomFn`. Logs a warning and no-ops if the client is not connected.
376
+ Emits `'join'` with `{ rooms }`. The server validates the request through its `validateRoomFn`. Logs a warning and no-ops if the client is not connected.
358
377
 
359
378
  #### `leaveRooms(opts: { rooms: string[] }): void`
360
379
 
361
380
  Emits `'leave'` with `{ rooms }`. Logs a warning and no-ops if the client is not connected.
362
381
 
363
382
  > [!NOTE]
364
- > Unlike `joinRooms()`, leave requests are **not** validated server-side - the client can request to leave any room name, including ones it never joined (`socket.leave()` on a room the socket isn't in is a no-op).
383
+ > Unlike `joinRooms()`, leave requests are **not** validated server-side. The client can request to leave any room name, including ones it never joined. `socket.leave()` on a room the socket isn't in is a no-op.
365
384
 
366
385
  #### `shutdown(): void`
367
386
 
@@ -419,7 +438,7 @@ The server uses `@socket.io/redis-adapter` and `@socket.io/redis-emitter` for ho
419
438
  | `redisEmitter` | Powers `send()` for cross-instance message delivery |
420
439
 
421
440
  - **Initialized during `configure()`.** All three clients are created and awaited before the IO server starts.
422
- - **Lazy connect handled explicitly.** If the parent `redisConnection` uses `lazyConnect` (`autoConnect: false`), the duplicated clients are kicked into `connect()` explicitly, since duplicated clients inherit `lazyConnect` but never dial on their own.
441
+ - **Lazy connect handled explicitly.** Duplicated clients inherit `lazyConnect` but never dial on their own. If the parent `redisConnection` uses `lazyConnect` (`autoConnect: false`), `configure()` kicks each duplicated client into `connect()` explicitly.
423
442
 
424
443
  ## Types Reference
425
444