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