@heady/redis-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # Redis Client
2
+
3
+ ![Language](https://img.shields.io/badge/language-TypeScript-blue)
4
+ ![License](https://img.shields.io/badge/license-MIT-green)
5
+ ![Version](https://img.shields.io/badge/version-1.0.0-orange)
6
+
7
+ **Redis Client** is a lightweight, type-safe wrapper for [ioredis](https://github.com/luin/ioredis). It solves the problem of scaling Redis infrastructure by automatically routing **Write** operations to a Primary node and **Read** operations to Replica nodes.
8
+
9
+ ## ๐Ÿ“‘ Table of Contents
10
+ - [Features](#-features)
11
+ - [Prerequisites](#-prerequisites)
12
+ - [Installation](#-installation)
13
+ - [Usage](#-usage)
14
+ - [Configuration](#-configuration)
15
+ - [Architecture](#-architecture)
16
+ - [Running Tests](#-running-tests)
17
+ - [Contributing](#-contributing)
18
+ - [License](#-license)
19
+
20
+ ---
21
+
22
+ ## โœจ Features
23
+
24
+ * **Automatic Traffic Splitting:** Writes (`set`, `del`, `expire`) are sent to the Primary; Reads (`get`, `scan`, `hget`) are sent to Replicas.
25
+ * **Type Safety:** Built with TypeScript, providing full IntelliSense and type checking out of the box.
26
+ * **Performance:** Offloads expensive read operations (like `SCAN`) from your Master node to prevent blocking critical write traffic.
27
+ * **Flexible Access:** Exposes the underlying `ioredis` instances if you need to run custom or unsupported commands.
28
+
29
+ ---
30
+
31
+ ## ๐Ÿ”ง Prerequisites
32
+
33
+ Before you begin, ensure you have met the following requirements:
34
+ * **Node.js** (v14 or higher)
35
+ * **Redis:** A Redis setup with at least one Master and one Replica (e.g., AWS ElastiCache Cluster Mode Disabled).
36
+
37
+ ---
38
+
39
+ ## ๐Ÿš€ Installation
40
+
41
+ 1. **Install the package and the peer dependency:**
42
+ ```bash
43
+ npm install redis-client
44
+
45
+ ---
46
+
47
+ ## ๐Ÿ’ก Usage
48
+
49
+ ``` typescript
50
+ import { Redis } from 'redis-client';
51
+
52
+ // 1. Initialize with separate endpoints
53
+ const redis = new Redis({
54
+ primary: {
55
+ host: 'primary-node.redis.aws.internal',
56
+ port: 6379,
57
+ },
58
+ replica: {
59
+ host: 'replica-node.redis.aws.internal',
60
+ port: 6379,
61
+ }
62
+ });
63
+
64
+ async function main() {
65
+ // โœ๏ธ WRITE: Automatically routed to Primary
66
+ await redis.set('user:101', 'John Doe', 3600);
67
+ console.log('User saved to Primary node');
68
+
69
+ // ๐Ÿ“– READ: Automatically routed to Replica
70
+ const user = await redis.get('user:101');
71
+ console.log('User read from Replica node:', user);
72
+
73
+ // ๐Ÿงน CLEANUP
74
+ await redis.disconnect();
75
+ }
76
+
77
+ main();
78
+ ```
79
+
80
+ ---
81
+
82
+ ## โš™๏ธ Configuration
83
+
84
+ The RedisClient constructor accepts a configuration object with two keys: primary and replica. Both accept standard ioredis options.
85
+
86
+ | Property | Type | Description |
87
+ |:-----|:--------:|------:|
88
+ | primary | RedisOptions | Configuration for the Master (Write) node. |
89
+ | replica | RedisOptions | Configuration for the Read Replica
90
+
91
+
92
+ Example Config Object:
93
+ ```TypeScript
94
+ {
95
+ primary: { host: '127.0.0.1', port: 6379, password: 'auth' },
96
+ replica: { host: '127.0.0.1', port: 6380, password: 'auth' }
97
+ }
98
+ ```
99
+ ---
100
+
101
+ ## ๐Ÿ— Architecture
102
+
103
+ This library implements the Read/Write Splitting pattern:
104
+
105
+ 1. Command Interception: The client checks the method being called (e.g., set vs get).
106
+ 2. Routing: * Mutating commands โ†’ Primary Connection
107
+ - Read-only commands โ†’ Replica Connection
108
+ 3. Result: This ensures high availability and prevents heavy read queries from slowing down data ingestion.
109
+
110
+ ---
111
+
112
+ ## ๐Ÿงช Running Tests
113
+
114
+ This project uses Jest with ts-jest for unit testing. The tests mock ioredis to ensure traffic is routed to the correct connection without needing a real Redis instance.
115
+
116
+ To run the test suite:
117
+ ``` Bash
118
+ npm test
119
+ ```
120
+
121
+ Expected Output:
122
+ ```Plaintext
123
+
124
+ PASS test/client.test.ts
125
+ RedisClient
126
+ โœ“ should create two separate Redis connections
127
+ โœ“ set() should call primary node
128
+ โœ“ get() should call replica node
129
+ ```
130
+
131
+ ## ๐Ÿค Contributing
132
+
133
+ Contributions are always welcome! Please follow these steps:
134
+
135
+ Fork the project.
136
+
137
+ Create your feature branch (git checkout -b feature/NewFeature).
138
+
139
+ Commit your changes (git commit -m 'Add some NewFeature').
140
+
141
+ Run tests to ensure no regressions (npm test).
142
+
143
+ Push to the branch.
144
+
145
+ Open a Pull Request.
146
+
147
+
148
+ ## ๐Ÿ“œ License
149
+
150
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,2 @@
1
+ export * from "./types";
2
+ export * from "./redisClient";
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./redisClient"), exports);
@@ -0,0 +1,39 @@
1
+ import { Redis as RedisType } from "ioredis";
2
+ import { RedisConfig } from "./types";
3
+ export declare class RedisClient {
4
+ private primary;
5
+ private replica;
6
+ constructor(config: RedisConfig);
7
+ /**
8
+ * -------------------------
9
+ * WRITE OPERATIONS (Primary)
10
+ * -------------------------
11
+ */
12
+ set(key: string, value: string | number, ttlSeconds?: number): Promise<string | null>;
13
+ hset(key: string, object: Record<string, string | number>): Promise<number>;
14
+ del(key: string): Promise<number>;
15
+ expire(key: string, seconds: number): Promise<number>;
16
+ /**
17
+ * -------------------------
18
+ * READ OPERATIONS (Replica)
19
+ * -------------------------
20
+ */
21
+ get(key: string): Promise<string | null>;
22
+ hget(key: string, field: string): Promise<string | null>;
23
+ hgetall(key: string): Promise<Record<string, string>>;
24
+ scan(cursor: string, matchPattern: string, count?: number): Promise<[string, string[]]>;
25
+ /**
26
+ * -------------------------
27
+ * UTILITIES
28
+ * -------------------------
29
+ */
30
+ /**
31
+ * Execute a custom command on the Primary node manually
32
+ */
33
+ getWriteClient(): RedisType;
34
+ /**
35
+ * Execute a custom command on the Replica node manually
36
+ */
37
+ getReadClient(): RedisType;
38
+ disconnect(): Promise<void>;
39
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RedisClient = void 0;
7
+ const ioredis_1 = __importDefault(require("ioredis"));
8
+ class RedisClient {
9
+ constructor(config) {
10
+ // Initialize Write Connection
11
+ this.primary = new ioredis_1.default(config.primary);
12
+ // Initialize Read Connection
13
+ this.replica = new ioredis_1.default(config.replica);
14
+ // Error handling to prevent crashing the app
15
+ this.primary.on("error", (err) => console.error("Redis Primary Error:", err));
16
+ this.replica.on("error", (err) => console.error("Redis Replica Error:", err));
17
+ }
18
+ /**
19
+ * -------------------------
20
+ * WRITE OPERATIONS (Primary)
21
+ * -------------------------
22
+ */
23
+ async set(key, value, ttlSeconds) {
24
+ if (ttlSeconds) {
25
+ return this.primary.set(key, value, "EX", ttlSeconds);
26
+ }
27
+ return this.primary.set(key, value);
28
+ }
29
+ async hset(key, object) {
30
+ return this.primary.hset(key, object);
31
+ }
32
+ async del(key) {
33
+ return this.primary.del(key);
34
+ }
35
+ async expire(key, seconds) {
36
+ return this.primary.expire(key, seconds);
37
+ }
38
+ /**
39
+ * -------------------------
40
+ * READ OPERATIONS (Replica)
41
+ * -------------------------
42
+ */
43
+ async get(key) {
44
+ return this.replica.get(key);
45
+ }
46
+ async hget(key, field) {
47
+ return this.replica.hget(key, field);
48
+ }
49
+ async hgetall(key) {
50
+ return this.replica.hgetall(key);
51
+ }
52
+ async scan(cursor, matchPattern, count = 100) {
53
+ return this.replica.scan(cursor, "MATCH", matchPattern, "COUNT", count);
54
+ }
55
+ /**
56
+ * -------------------------
57
+ * UTILITIES
58
+ * -------------------------
59
+ */
60
+ /**
61
+ * Execute a custom command on the Primary node manually
62
+ */
63
+ getWriteClient() {
64
+ return this.primary;
65
+ }
66
+ /**
67
+ * Execute a custom command on the Replica node manually
68
+ */
69
+ getReadClient() {
70
+ return this.replica;
71
+ }
72
+ async disconnect() {
73
+ await Promise.all([this.primary.quit(), this.replica.quit()]);
74
+ }
75
+ }
76
+ exports.RedisClient = RedisClient;
@@ -0,0 +1,7 @@
1
+ import { RedisOptions } from "ioredis";
2
+ export interface RedisConfig {
3
+ /** Configuration for the Primary (Write) Node */
4
+ primary: RedisOptions;
5
+ /** Configuration for the Replica (Read) Node(s) */
6
+ replica: RedisOptions;
7
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@heady/redis-client",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "jest",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "author": "",
15
+ "license": "ISC",
16
+ "description": "",
17
+ "dependencies": {
18
+ "@types/node": "^25.0.3",
19
+ "ioredis": "^5.9.1",
20
+ "typescript": "^5.9.3"
21
+ },
22
+ "devDependencies": {
23
+ "@types/jest": "^30.0.0",
24
+ "jest": "^30.2.0",
25
+ "ts-jest": "^29.4.6"
26
+ }
27
+ }