@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.
- package/content/best-practices/architectural-patterns.md +3 -3
- package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/content/best-practices/contribution-workflow.md +2 -2
- package/content/best-practices/error-handling.md +94 -89
- package/content/best-practices/security-guidelines.md +5 -5
- package/content/extensions/components/api-reference.md +22 -21
- package/content/extensions/components/authentication/api.md +64 -28
- package/content/extensions/components/authentication/errors.md +19 -5
- package/content/extensions/components/authentication/index.md +19 -18
- package/content/extensions/components/authentication/usage.md +55 -30
- package/content/extensions/components/authorization/api.md +351 -84
- package/content/extensions/components/authorization/errors.md +51 -17
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +24 -16
- package/content/extensions/components/authorization/usage.md +45 -21
- package/content/extensions/components/health-check.md +15 -9
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +105 -54
- package/content/extensions/components/mail/errors.md +12 -10
- package/content/extensions/components/mail/index.md +32 -13
- package/content/extensions/components/mail/usage.md +26 -18
- package/content/extensions/components/request-tracker.md +18 -14
- package/content/extensions/components/socket-io/api.md +377 -882
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +72 -88
- package/content/extensions/components/socket-io/usage.md +107 -117
- package/content/extensions/components/static-asset/api.md +83 -31
- package/content/extensions/components/static-asset/errors.md +18 -7
- package/content/extensions/components/static-asset/index.md +18 -11
- package/content/extensions/components/static-asset/usage.md +11 -6
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +58 -27
- package/content/extensions/components/websocket/errors.md +3 -3
- package/content/extensions/components/websocket/index.md +8 -7
- package/content/extensions/components/websocket/usage.md +21 -8
- package/content/extensions/helpers/cron/index.md +8 -7
- package/content/extensions/helpers/crypto/index.md +16 -8
- package/content/extensions/helpers/crypto/reference.md +96 -24
- package/content/extensions/helpers/env/index.md +14 -10
- package/content/extensions/helpers/error/index.md +99 -23
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +23 -6
- package/content/extensions/helpers/inversion/reference.md +30 -22
- package/content/extensions/helpers/kafka/admin.md +3 -2
- package/content/extensions/helpers/kafka/compile-binary.md +69 -44
- package/content/extensions/helpers/kafka/consumer.md +24 -21
- package/content/extensions/helpers/kafka/examples.md +22 -234
- package/content/extensions/helpers/kafka/index.md +30 -62
- package/content/extensions/helpers/kafka/producer.md +31 -26
- package/content/extensions/helpers/kafka/schema-registry.md +19 -14
- package/content/extensions/helpers/logger/hf-logger.md +49 -22
- package/content/extensions/helpers/logger/index.md +37 -12
- package/content/extensions/helpers/logger/pino.md +31 -11
- package/content/extensions/helpers/logger/reference.md +243 -52
- package/content/extensions/helpers/network/api.md +65 -30
- package/content/extensions/helpers/network/index.md +32 -11
- package/content/extensions/helpers/queue/index.md +17 -6
- package/content/extensions/helpers/queue/reference.md +52 -25
- package/content/extensions/helpers/redis/index.md +29 -11
- package/content/extensions/helpers/redis/reference.md +85 -28
- package/content/extensions/helpers/secrets/index.md +82 -12
- package/content/extensions/helpers/socket-io/api.md +40 -21
- package/content/extensions/helpers/socket-io/index.md +26 -10
- package/content/extensions/helpers/storage/api.md +42 -24
- package/content/extensions/helpers/storage/index.md +10 -7
- package/content/extensions/helpers/types/index.md +20 -7
- package/content/extensions/helpers/types/reference.md +53 -14
- package/content/extensions/helpers/uid/index.md +166 -10
- package/content/extensions/helpers/websocket/api.md +52 -13
- package/content/extensions/helpers/websocket/index.md +7 -4
- package/content/extensions/helpers/worker-thread/index.md +9 -5
- package/content/extensions/helpers/worker-thread/reference.md +20 -20
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +4 -3
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +7 -6
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +63 -21
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +14 -14
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +32 -31
- package/content/references/base/datasources.md +6 -6
- package/content/references/base/dependency-injection.md +12 -11
- package/content/references/base/filter-system/application-usage.md +54 -30
- package/content/references/base/filter-system/array-operators.md +24 -46
- package/content/references/base/filter-system/comparison-operators.md +47 -67
- package/content/references/base/filter-system/default-filter.md +59 -53
- package/content/references/base/filter-system/fields-order-pagination.md +92 -146
- package/content/references/base/filter-system/index.md +25 -13
- package/content/references/base/filter-system/json-filtering.md +45 -184
- package/content/references/base/filter-system/list-operators.md +23 -53
- package/content/references/base/filter-system/logical-operators.md +63 -121
- package/content/references/base/filter-system/null-operators.md +34 -104
- package/content/references/base/filter-system/pattern-matching.md +40 -55
- package/content/references/base/filter-system/quick-reference.md +86 -198
- package/content/references/base/filter-system/range-operators.md +18 -46
- package/content/references/base/filter-system/tips.md +6 -6
- package/content/references/base/filter-system/use-cases.md +33 -15
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +11 -10
- package/content/references/base/models-reference.md +17 -17
- package/content/references/base/models.md +4 -3
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +224 -326
- package/content/references/base/repositories/index.md +19 -6
- package/content/references/base/repositories/mixins.md +5 -5
- package/content/references/base/repositories/relations.md +160 -293
- package/content/references/base/repositories/soft-deletable.md +16 -6
- package/content/references/base/secrets.md +17 -13
- package/content/references/base/services.md +6 -4
- package/content/references/configuration/environment-variables.md +31 -23
- package/content/references/configuration/index.md +6 -4
- package/content/references/index.md +1 -1
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +5 -1
- package/content/references/utilities/jsx-reference.md +11 -11
- package/content/references/utilities/jsx.md +2 -2
- package/content/references/utilities/module.md +78 -25
- package/content/references/utilities/request.md +2 -1
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +2 -2
- package/content/references/utilities/statuses-reference.md +4 -4
- package/content/references/utilities/statuses.md +5 -5
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- 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
|
|
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
|
|
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
|
|
46
|
-
- **Connection lifecycle is automatic.** With `autoConnect: true` (default) the ioredis client starts connecting
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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` |
|
|
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
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
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
|
|
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` |
|
|
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
|
|
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
|
|
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.
|
|
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()
|
|
430
|
-
- **Lazy or immediate.** `lazyConnect` is set to `!autoConnect`. With the default `autoConnect: true
|
|
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()`
|
|
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
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
27
|
+
Swap `provider` for `SecretProviders.HASHICORP_VAULT` (with a `config: { endpoint, auth }`) or `SecretProviders.DOTENV_VAULT` to change the backend.
|
|
28
28
|
|
|
29
|
-
##
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
353
|
-
|
|
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 }
|
|
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
|
|
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`),
|
|
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
|
|