chub-dev 0.1.0 → 0.1.2-beta.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 +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1851 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: key-value
|
|
3
|
+
description: "Redis JavaScript client (node-redis) for key-value storage, caching, and pub/sub messaging"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "5.9.0"
|
|
7
|
+
updated-on: "2026-03-01"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "redis,database,cache,key-value,pubsub"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Redis JavaScript Client (node-redis) - Complete Integration Guide
|
|
13
|
+
|
|
14
|
+
## GOLDEN RULE
|
|
15
|
+
|
|
16
|
+
**ALWAYS use the official `redis` npm package (node-redis) for Redis integration.**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install redis
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**DO NOT use:**
|
|
23
|
+
- `ioredis` (alternative client, not official)
|
|
24
|
+
- `redis-node` (unofficial package)
|
|
25
|
+
- `node_redis` (deprecated naming)
|
|
26
|
+
- Any other third-party Redis clients unless specifically required
|
|
27
|
+
|
|
28
|
+
The official `redis` package is maintained by Redis and is the recommended client for Node.js applications.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Basic Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install redis
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Installation with TypeScript Support
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install redis
|
|
44
|
+
npm install --save-dev @types/node
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `redis` package includes built-in TypeScript definitions.
|
|
48
|
+
|
|
49
|
+
### Environment Setup
|
|
50
|
+
|
|
51
|
+
Create a `.env` file in your project root:
|
|
52
|
+
|
|
53
|
+
```env
|
|
54
|
+
REDIS_HOST=localhost
|
|
55
|
+
REDIS_PORT=6379
|
|
56
|
+
REDIS_PASSWORD=your_password_here
|
|
57
|
+
REDIS_DB=0
|
|
58
|
+
REDIS_URL=redis://username:password@localhost:6379
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Install dotenv to load environment variables:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install dotenv
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Initialization
|
|
70
|
+
|
|
71
|
+
### Basic Connection (Localhost)
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { createClient } from 'redis';
|
|
75
|
+
|
|
76
|
+
const client = createClient();
|
|
77
|
+
|
|
78
|
+
client.on('error', (err) => {
|
|
79
|
+
console.error('Redis Client Error', err);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await client.connect();
|
|
83
|
+
|
|
84
|
+
// Use the client...
|
|
85
|
+
|
|
86
|
+
await client.quit();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Connection with Environment Variables
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import { createClient } from 'redis';
|
|
93
|
+
import dotenv from 'dotenv';
|
|
94
|
+
|
|
95
|
+
dotenv.config();
|
|
96
|
+
|
|
97
|
+
const client = createClient({
|
|
98
|
+
socket: {
|
|
99
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
100
|
+
port: parseInt(process.env.REDIS_PORT || '6379')
|
|
101
|
+
},
|
|
102
|
+
password: process.env.REDIS_PASSWORD,
|
|
103
|
+
database: parseInt(process.env.REDIS_DB || '0')
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
client.on('error', (err) => {
|
|
107
|
+
console.error('Redis Client Error', err);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await client.connect();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Connection Using URL
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
import { createClient } from 'redis';
|
|
117
|
+
import dotenv from 'dotenv';
|
|
118
|
+
|
|
119
|
+
dotenv.config();
|
|
120
|
+
|
|
121
|
+
const client = createClient({
|
|
122
|
+
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
client.on('error', (err) => {
|
|
126
|
+
console.error('Redis Client Error', err);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await client.connect();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Connection with Authentication
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
import { createClient } from 'redis';
|
|
136
|
+
|
|
137
|
+
const client = createClient({
|
|
138
|
+
url: 'redis://alice:foobared@awesome.redis.server:6380'
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Or using socket configuration
|
|
142
|
+
const clientAlt = createClient({
|
|
143
|
+
socket: {
|
|
144
|
+
host: 'awesome.redis.server',
|
|
145
|
+
port: 6380
|
|
146
|
+
},
|
|
147
|
+
username: 'alice',
|
|
148
|
+
password: 'foobared'
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await client.connect();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Connection with TLS/SSL
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
import { createClient } from 'redis';
|
|
158
|
+
import fs from 'fs';
|
|
159
|
+
|
|
160
|
+
const client = createClient({
|
|
161
|
+
socket: {
|
|
162
|
+
host: 'redis.example.com',
|
|
163
|
+
port: 6380,
|
|
164
|
+
tls: true,
|
|
165
|
+
key: fs.readFileSync('/path/to/client-key.pem'),
|
|
166
|
+
cert: fs.readFileSync('/path/to/client-cert.pem'),
|
|
167
|
+
ca: [fs.readFileSync('/path/to/ca-cert.pem')]
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await client.connect();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Connection Status Checks
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
// Check if client is ready to execute commands
|
|
178
|
+
if (client.isReady) {
|
|
179
|
+
console.log('Client is ready');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check if underlying socket is open
|
|
183
|
+
if (client.isOpen) {
|
|
184
|
+
console.log('Connection is open');
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Duplicate Connection (for Pub/Sub)
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
const client = createClient();
|
|
192
|
+
await client.connect();
|
|
193
|
+
|
|
194
|
+
// Create a duplicate connection for pub/sub
|
|
195
|
+
const subscriber = client.duplicate();
|
|
196
|
+
await subscriber.connect();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Graceful Shutdown
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// Graceful disconnect
|
|
203
|
+
await client.quit();
|
|
204
|
+
|
|
205
|
+
// Force disconnect
|
|
206
|
+
await client.disconnect();
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Core API Surfaces
|
|
212
|
+
|
|
213
|
+
## String Operations
|
|
214
|
+
|
|
215
|
+
### Basic SET and GET
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Set a string value
|
|
219
|
+
await client.set('key', 'value');
|
|
220
|
+
|
|
221
|
+
// Get a string value
|
|
222
|
+
const value = await client.get('key');
|
|
223
|
+
console.log(value); // 'value'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### SET with Options
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// Set with expiration (EX = seconds)
|
|
230
|
+
await client.set('session:123', 'user_data', {
|
|
231
|
+
EX: 3600 // expires in 1 hour
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Set with expiration (PX = milliseconds)
|
|
235
|
+
await client.set('temp:key', 'value', {
|
|
236
|
+
PX: 5000 // expires in 5 seconds
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Set only if key doesn't exist (NX)
|
|
240
|
+
await client.set('key', 'value', {
|
|
241
|
+
NX: true
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Set only if key exists (XX)
|
|
245
|
+
await client.set('key', 'new_value', {
|
|
246
|
+
XX: true
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Get old value and set new value
|
|
250
|
+
const oldValue = await client.set('key', 'new_value', {
|
|
251
|
+
GET: true
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Multiple Keys
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// Set multiple keys at once
|
|
259
|
+
await client.mSet({
|
|
260
|
+
'key1': 'value1',
|
|
261
|
+
'key2': 'value2',
|
|
262
|
+
'key3': 'value3'
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Get multiple keys at once
|
|
266
|
+
const values = await client.mGet(['key1', 'key2', 'key3']);
|
|
267
|
+
console.log(values); // ['value1', 'value2', 'value3']
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Increment and Decrement
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
// Set initial value
|
|
274
|
+
await client.set('counter', 0);
|
|
275
|
+
|
|
276
|
+
// Increment by 1
|
|
277
|
+
await client.incr('counter'); // Returns 1
|
|
278
|
+
|
|
279
|
+
// Increment by specific amount
|
|
280
|
+
await client.incrBy('counter', 10); // Returns 11
|
|
281
|
+
|
|
282
|
+
// Increment float
|
|
283
|
+
await client.incrByFloat('price', 2.5); // Increment by 2.5
|
|
284
|
+
|
|
285
|
+
// Decrement by 1
|
|
286
|
+
await client.decr('counter');
|
|
287
|
+
|
|
288
|
+
// Decrement by specific amount
|
|
289
|
+
await client.decrBy('counter', 5);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### String Manipulation
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// Append to string
|
|
296
|
+
await client.set('message', 'Hello');
|
|
297
|
+
await client.append('message', ' World'); // 'Hello World'
|
|
298
|
+
|
|
299
|
+
// Get substring
|
|
300
|
+
const substr = await client.getRange('message', 0, 4); // 'Hello'
|
|
301
|
+
|
|
302
|
+
// Get length
|
|
303
|
+
const len = await client.strLen('message'); // 11
|
|
304
|
+
|
|
305
|
+
// Set range
|
|
306
|
+
await client.setRange('message', 6, 'Redis'); // 'Hello Redis'
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Hash Operations
|
|
312
|
+
|
|
313
|
+
### Basic Hash Operations
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
// Set a single field in a hash
|
|
317
|
+
await client.hSet('user:1000', 'name', 'John Doe');
|
|
318
|
+
|
|
319
|
+
// Get a single field from a hash
|
|
320
|
+
const name = await client.hGet('user:1000', 'name');
|
|
321
|
+
console.log(name); // 'John Doe'
|
|
322
|
+
|
|
323
|
+
// Set multiple fields at once
|
|
324
|
+
await client.hSet('user:1000', {
|
|
325
|
+
name: 'John Doe',
|
|
326
|
+
email: '[email protected]',
|
|
327
|
+
age: 30
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Get all fields and values
|
|
331
|
+
const user = await client.hGetAll('user:1000');
|
|
332
|
+
console.log(user);
|
|
333
|
+
// { name: 'John Doe', email: '[email protected]', age: '30' }
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Advanced Hash Operations
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
// Check if field exists
|
|
340
|
+
const exists = await client.hExists('user:1000', 'email'); // true
|
|
341
|
+
|
|
342
|
+
// Get all field names
|
|
343
|
+
const fields = await client.hKeys('user:1000');
|
|
344
|
+
// ['name', 'email', 'age']
|
|
345
|
+
|
|
346
|
+
// Get all values
|
|
347
|
+
const values = await client.hVals('user:1000');
|
|
348
|
+
// ['John Doe', '[email protected]', '30']
|
|
349
|
+
|
|
350
|
+
// Get number of fields
|
|
351
|
+
const count = await client.hLen('user:1000'); // 3
|
|
352
|
+
|
|
353
|
+
// Get multiple fields
|
|
354
|
+
const userData = await client.hmGet('user:1000', ['name', 'email']);
|
|
355
|
+
// ['John Doe', '[email protected]']
|
|
356
|
+
|
|
357
|
+
// Delete fields
|
|
358
|
+
await client.hDel('user:1000', 'age');
|
|
359
|
+
|
|
360
|
+
// Increment numeric field
|
|
361
|
+
await client.hSet('user:1000', 'loginCount', 0);
|
|
362
|
+
await client.hIncrBy('user:1000', 'loginCount', 1); // 1
|
|
363
|
+
|
|
364
|
+
// Increment float field
|
|
365
|
+
await client.hIncrByFloat('user:1000', 'balance', 10.50);
|
|
366
|
+
|
|
367
|
+
// Set only if field doesn't exist
|
|
368
|
+
await client.hSetNX('user:1000', 'created', Date.now());
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Scan Hash Fields
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
// Scan hash fields (for large hashes)
|
|
375
|
+
for await (const { field, value } of client.hScanIterator('user:1000')) {
|
|
376
|
+
console.log(`${field}: ${value}`);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## List Operations
|
|
383
|
+
|
|
384
|
+
### Basic List Operations
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
// Push elements to the right (end) of list
|
|
388
|
+
await client.rPush('tasks', 'task1');
|
|
389
|
+
await client.rPush('tasks', ['task2', 'task3']); // Multiple values
|
|
390
|
+
|
|
391
|
+
// Push elements to the left (beginning) of list
|
|
392
|
+
await client.lPush('tasks', 'urgent_task');
|
|
393
|
+
await client.lPush('tasks', ['task0', 'task-1']);
|
|
394
|
+
|
|
395
|
+
// Get list length
|
|
396
|
+
const length = await client.lLen('tasks');
|
|
397
|
+
|
|
398
|
+
// Get range of elements
|
|
399
|
+
const tasks = await client.lRange('tasks', 0, -1); // Get all
|
|
400
|
+
const firstThree = await client.lRange('tasks', 0, 2); // Get first 3
|
|
401
|
+
|
|
402
|
+
// Get element by index
|
|
403
|
+
const task = await client.lIndex('tasks', 0);
|
|
404
|
+
|
|
405
|
+
// Pop from right (end)
|
|
406
|
+
const lastTask = await client.rPop('tasks');
|
|
407
|
+
|
|
408
|
+
// Pop from left (beginning)
|
|
409
|
+
const firstTask = await client.lPop('tasks');
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Advanced List Operations
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
// Blocking pop (wait for element)
|
|
416
|
+
const task = await client.blPop('tasks', 10); // Wait up to 10 seconds
|
|
417
|
+
// Returns: { key: 'tasks', element: 'task1' }
|
|
418
|
+
|
|
419
|
+
const taskRight = await client.brPop('tasks', 10);
|
|
420
|
+
|
|
421
|
+
// Set element at index
|
|
422
|
+
await client.lSet('tasks', 0, 'updated_task');
|
|
423
|
+
|
|
424
|
+
// Insert before/after element
|
|
425
|
+
await client.lInsert('tasks', 'BEFORE', 'task2', 'new_task');
|
|
426
|
+
await client.lInsert('tasks', 'AFTER', 'task2', 'another_task');
|
|
427
|
+
|
|
428
|
+
// Remove elements
|
|
429
|
+
await client.lRem('tasks', 2, 'task1'); // Remove first 2 occurrences
|
|
430
|
+
await client.lRem('tasks', -1, 'task2'); // Remove last occurrence
|
|
431
|
+
await client.lRem('tasks', 0, 'task3'); // Remove all occurrences
|
|
432
|
+
|
|
433
|
+
// Trim list to range
|
|
434
|
+
await client.lTrim('tasks', 0, 9); // Keep only first 10 elements
|
|
435
|
+
|
|
436
|
+
// Move element between lists
|
|
437
|
+
const element = await client.rPopLPush('source', 'destination');
|
|
438
|
+
|
|
439
|
+
// Blocking move
|
|
440
|
+
const moved = await client.brPopLPush('source', 'destination', 5);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## Set Operations
|
|
446
|
+
|
|
447
|
+
### Basic Set Operations
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// Add members to set
|
|
451
|
+
await client.sAdd('tags', 'javascript');
|
|
452
|
+
await client.sAdd('tags', ['nodejs', 'redis', 'database']);
|
|
453
|
+
|
|
454
|
+
// Check if member exists
|
|
455
|
+
const exists = await client.sIsMember('tags', 'nodejs'); // true
|
|
456
|
+
|
|
457
|
+
// Get all members
|
|
458
|
+
const allTags = await client.sMembers('tags');
|
|
459
|
+
// ['javascript', 'nodejs', 'redis', 'database']
|
|
460
|
+
|
|
461
|
+
// Get number of members
|
|
462
|
+
const count = await client.sCard('tags'); // 4
|
|
463
|
+
|
|
464
|
+
// Remove members
|
|
465
|
+
await client.sRem('tags', 'database');
|
|
466
|
+
await client.sRem('tags', ['nodejs', 'redis']);
|
|
467
|
+
|
|
468
|
+
// Pop random member
|
|
469
|
+
const randomTag = await client.sPop('tags');
|
|
470
|
+
|
|
471
|
+
// Get random member without removing
|
|
472
|
+
const random = await client.sRandMember('tags');
|
|
473
|
+
const randomThree = await client.sRandMemberCount('tags', 3);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Set Operations Between Multiple Sets
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
// Create sets
|
|
480
|
+
await client.sAdd('set1', ['a', 'b', 'c']);
|
|
481
|
+
await client.sAdd('set2', ['b', 'c', 'd']);
|
|
482
|
+
await client.sAdd('set3', ['c', 'd', 'e']);
|
|
483
|
+
|
|
484
|
+
// Union (combine all unique members)
|
|
485
|
+
const union = await client.sUnion(['set1', 'set2']);
|
|
486
|
+
// ['a', 'b', 'c', 'd']
|
|
487
|
+
|
|
488
|
+
// Store union in new set
|
|
489
|
+
await client.sUnionStore('result', ['set1', 'set2']);
|
|
490
|
+
|
|
491
|
+
// Intersection (common members)
|
|
492
|
+
const inter = await client.sInter(['set1', 'set2']);
|
|
493
|
+
// ['b', 'c']
|
|
494
|
+
|
|
495
|
+
// Store intersection
|
|
496
|
+
await client.sInterStore('result', ['set1', 'set2']);
|
|
497
|
+
|
|
498
|
+
// Difference (members in first set but not in others)
|
|
499
|
+
const diff = await client.sDiff(['set1', 'set2']);
|
|
500
|
+
// ['a']
|
|
501
|
+
|
|
502
|
+
// Store difference
|
|
503
|
+
await client.sDiffStore('result', ['set1', 'set2']);
|
|
504
|
+
|
|
505
|
+
// Move member between sets
|
|
506
|
+
await client.sMove('set1', 'set2', 'a');
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Scan Set Members
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
// Scan set members (for large sets)
|
|
513
|
+
for await (const member of client.sScanIterator('tags')) {
|
|
514
|
+
console.log(member);
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Sorted Set Operations
|
|
521
|
+
|
|
522
|
+
### Basic Sorted Set Operations
|
|
523
|
+
|
|
524
|
+
```javascript
|
|
525
|
+
// Add members with scores
|
|
526
|
+
await client.zAdd('leaderboard', { score: 100, value: 'player1' });
|
|
527
|
+
await client.zAdd('leaderboard', [
|
|
528
|
+
{ score: 200, value: 'player2' },
|
|
529
|
+
{ score: 150, value: 'player3' }
|
|
530
|
+
]);
|
|
531
|
+
|
|
532
|
+
// Get rank (0-based, lowest to highest)
|
|
533
|
+
const rank = await client.zRank('leaderboard', 'player1'); // 0
|
|
534
|
+
|
|
535
|
+
// Get reverse rank (highest to lowest)
|
|
536
|
+
const revRank = await client.zRevRank('leaderboard', 'player2'); // 0
|
|
537
|
+
|
|
538
|
+
// Get score
|
|
539
|
+
const score = await client.zScore('leaderboard', 'player2'); // 200
|
|
540
|
+
|
|
541
|
+
// Get number of members
|
|
542
|
+
const count = await client.zCard('leaderboard'); // 3
|
|
543
|
+
|
|
544
|
+
// Increment score
|
|
545
|
+
await client.zIncrBy('leaderboard', 50, 'player1'); // 150
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Range Queries
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
// Get range by rank (lowest to highest)
|
|
552
|
+
const bottom3 = await client.zRange('leaderboard', 0, 2);
|
|
553
|
+
// ['player1', 'player3', 'player2']
|
|
554
|
+
|
|
555
|
+
// Get range with scores
|
|
556
|
+
const withScores = await client.zRangeWithScores('leaderboard', 0, 2);
|
|
557
|
+
// [{ value: 'player1', score: 150 }, ...]
|
|
558
|
+
|
|
559
|
+
// Get range by rank (highest to lowest)
|
|
560
|
+
const top3 = await client.zRevRange('leaderboard', 0, 2);
|
|
561
|
+
|
|
562
|
+
// Get range by score
|
|
563
|
+
const range = await client.zRangeByScore('leaderboard', 100, 200);
|
|
564
|
+
|
|
565
|
+
// Get range by score with limit
|
|
566
|
+
const limited = await client.zRangeByScore('leaderboard', 100, 200, {
|
|
567
|
+
LIMIT: {
|
|
568
|
+
offset: 0,
|
|
569
|
+
count: 10
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// Get reverse range by score
|
|
574
|
+
const revRange = await client.zRevRangeByScore('leaderboard', 200, 100);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Advanced Sorted Set Operations
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
// Count members in score range
|
|
581
|
+
const count = await client.zCount('leaderboard', 100, 200);
|
|
582
|
+
|
|
583
|
+
// Count members by lexicographical range
|
|
584
|
+
const lexCount = await client.zLexCount('leaderboard', '[a', '[z');
|
|
585
|
+
|
|
586
|
+
// Remove members
|
|
587
|
+
await client.zRem('leaderboard', 'player1');
|
|
588
|
+
await client.zRem('leaderboard', ['player2', 'player3']);
|
|
589
|
+
|
|
590
|
+
// Remove by rank range
|
|
591
|
+
await client.zRemRangeByRank('leaderboard', 0, 1); // Remove bottom 2
|
|
592
|
+
|
|
593
|
+
// Remove by score range
|
|
594
|
+
await client.zRemRangeByScore('leaderboard', 0, 100);
|
|
595
|
+
|
|
596
|
+
// Pop highest/lowest scoring member
|
|
597
|
+
const highest = await client.zPopMax('leaderboard');
|
|
598
|
+
const lowest = await client.zPopMin('leaderboard');
|
|
599
|
+
|
|
600
|
+
// Pop with count
|
|
601
|
+
const top3 = await client.zPopMax('leaderboard', 3);
|
|
602
|
+
|
|
603
|
+
// Blocking pop
|
|
604
|
+
const member = await client.bzPopMax('leaderboard', 5); // Wait up to 5 sec
|
|
605
|
+
const minMember = await client.bzPopMin('leaderboard', 5);
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Sorted Set Operations Between Multiple Sets
|
|
609
|
+
|
|
610
|
+
```javascript
|
|
611
|
+
// Union of sorted sets
|
|
612
|
+
await client.zUnionStore('result', ['set1', 'set2']);
|
|
613
|
+
|
|
614
|
+
// Union with weights
|
|
615
|
+
await client.zUnionStore('result', ['set1', 'set2'], {
|
|
616
|
+
WEIGHTS: [2, 3]
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Union with aggregate function
|
|
620
|
+
await client.zUnionStore('result', ['set1', 'set2'], {
|
|
621
|
+
AGGREGATE: 'MAX' // or 'MIN', 'SUM'
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Intersection
|
|
625
|
+
await client.zInterStore('result', ['set1', 'set2']);
|
|
626
|
+
|
|
627
|
+
// Intersection with weights and aggregate
|
|
628
|
+
await client.zInterStore('result', ['set1', 'set2'], {
|
|
629
|
+
WEIGHTS: [1, 2],
|
|
630
|
+
AGGREGATE: 'SUM'
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Scan Sorted Set
|
|
635
|
+
|
|
636
|
+
```javascript
|
|
637
|
+
// Scan sorted set members
|
|
638
|
+
for await (const member of client.zScanIterator('leaderboard')) {
|
|
639
|
+
console.log(member);
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## Key Management Operations
|
|
646
|
+
|
|
647
|
+
### Key Operations
|
|
648
|
+
|
|
649
|
+
```javascript
|
|
650
|
+
// Check if key exists
|
|
651
|
+
const exists = await client.exists('mykey'); // 1 if exists, 0 if not
|
|
652
|
+
const multiExists = await client.exists(['key1', 'key2']); // Count
|
|
653
|
+
|
|
654
|
+
// Delete keys
|
|
655
|
+
await client.del('mykey');
|
|
656
|
+
await client.del(['key1', 'key2', 'key3']);
|
|
657
|
+
|
|
658
|
+
// Set expiration in seconds
|
|
659
|
+
await client.expire('mykey', 60); // Expire in 60 seconds
|
|
660
|
+
|
|
661
|
+
// Set expiration at specific timestamp
|
|
662
|
+
const timestamp = Math.floor(Date.now() / 1000) + 3600;
|
|
663
|
+
await client.expireAt('mykey', timestamp);
|
|
664
|
+
|
|
665
|
+
// Set expiration in milliseconds
|
|
666
|
+
await client.pExpire('mykey', 60000); // 60 seconds
|
|
667
|
+
|
|
668
|
+
// Get time to live in seconds
|
|
669
|
+
const ttl = await client.ttl('mykey'); // -1 if no expiration, -2 if not exists
|
|
670
|
+
|
|
671
|
+
// Get time to live in milliseconds
|
|
672
|
+
const pttl = await client.pTtl('mykey');
|
|
673
|
+
|
|
674
|
+
// Remove expiration
|
|
675
|
+
await client.persist('mykey');
|
|
676
|
+
|
|
677
|
+
// Rename key
|
|
678
|
+
await client.rename('oldkey', 'newkey');
|
|
679
|
+
|
|
680
|
+
// Rename only if new key doesn't exist
|
|
681
|
+
const renamed = await client.renameNX('oldkey', 'newkey');
|
|
682
|
+
|
|
683
|
+
// Get key type
|
|
684
|
+
const type = await client.type('mykey'); // 'string', 'list', 'set', etc.
|
|
685
|
+
|
|
686
|
+
// Get random key
|
|
687
|
+
const randomKey = await client.randomKey();
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Scanning Keys
|
|
691
|
+
|
|
692
|
+
```javascript
|
|
693
|
+
// Scan all keys (use instead of KEYS for production)
|
|
694
|
+
const keys = [];
|
|
695
|
+
for await (const key of client.scanIterator()) {
|
|
696
|
+
keys.push(key);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Scan with pattern
|
|
700
|
+
for await (const key of client.scanIterator({
|
|
701
|
+
MATCH: 'user:*',
|
|
702
|
+
COUNT: 100
|
|
703
|
+
})) {
|
|
704
|
+
console.log(key);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Scan specific key type
|
|
708
|
+
for await (const key of client.scanIterator({
|
|
709
|
+
TYPE: 'string',
|
|
710
|
+
COUNT: 100
|
|
711
|
+
})) {
|
|
712
|
+
console.log(key);
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Transactions
|
|
719
|
+
|
|
720
|
+
### Basic Transaction (MULTI/EXEC)
|
|
721
|
+
|
|
722
|
+
```javascript
|
|
723
|
+
// Execute multiple commands atomically
|
|
724
|
+
await client.set('another-key', 'another-value');
|
|
725
|
+
|
|
726
|
+
const results = await client
|
|
727
|
+
.multi()
|
|
728
|
+
.set('key', 'value')
|
|
729
|
+
.get('another-key')
|
|
730
|
+
.incr('counter')
|
|
731
|
+
.exec();
|
|
732
|
+
|
|
733
|
+
console.log(results); // ['OK', 'another-value', 1]
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Transaction with Error Handling
|
|
737
|
+
|
|
738
|
+
```javascript
|
|
739
|
+
try {
|
|
740
|
+
const results = await client
|
|
741
|
+
.multi()
|
|
742
|
+
.set('key1', 'value1')
|
|
743
|
+
.set('key2', 'value2')
|
|
744
|
+
.get('key1')
|
|
745
|
+
.exec();
|
|
746
|
+
|
|
747
|
+
// Check each result
|
|
748
|
+
results.forEach((result, index) => {
|
|
749
|
+
if (result instanceof Error) {
|
|
750
|
+
console.error(`Command ${index} failed:`, result);
|
|
751
|
+
} else {
|
|
752
|
+
console.log(`Command ${index} result:`, result);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error('Transaction failed:', error);
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Transaction with WATCH (Optimistic Locking)
|
|
761
|
+
|
|
762
|
+
```javascript
|
|
763
|
+
// Watch a key for changes
|
|
764
|
+
await client.watch('balance');
|
|
765
|
+
|
|
766
|
+
const balance = parseInt(await client.get('balance'));
|
|
767
|
+
|
|
768
|
+
if (balance >= 100) {
|
|
769
|
+
// This transaction will fail if 'balance' changed after WATCH
|
|
770
|
+
const results = await client
|
|
771
|
+
.multi()
|
|
772
|
+
.decrBy('balance', 100)
|
|
773
|
+
.incrBy('purchases', 1)
|
|
774
|
+
.exec();
|
|
775
|
+
|
|
776
|
+
if (results === null) {
|
|
777
|
+
console.log('Transaction aborted - balance was modified');
|
|
778
|
+
} else {
|
|
779
|
+
console.log('Purchase successful');
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
await client.unwatch();
|
|
783
|
+
console.log('Insufficient balance');
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Complex Transaction Example
|
|
788
|
+
|
|
789
|
+
```javascript
|
|
790
|
+
async function transferMoney(fromAccount, toAccount, amount) {
|
|
791
|
+
await client.watch([fromAccount, toAccount]);
|
|
792
|
+
|
|
793
|
+
const fromBalance = parseFloat(await client.get(fromAccount));
|
|
794
|
+
|
|
795
|
+
if (fromBalance < amount) {
|
|
796
|
+
await client.unwatch();
|
|
797
|
+
throw new Error('Insufficient funds');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const results = await client
|
|
801
|
+
.multi()
|
|
802
|
+
.decrByFloat(fromAccount, amount)
|
|
803
|
+
.incrByFloat(toAccount, amount)
|
|
804
|
+
.exec();
|
|
805
|
+
|
|
806
|
+
if (results === null) {
|
|
807
|
+
throw new Error('Transaction failed - accounts modified during transfer');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return results;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Usage
|
|
814
|
+
try {
|
|
815
|
+
await transferMoney('account:1', 'account:2', 50.00);
|
|
816
|
+
console.log('Transfer successful');
|
|
817
|
+
} catch (error) {
|
|
818
|
+
console.error('Transfer failed:', error.message);
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
---
|
|
823
|
+
|
|
824
|
+
## Pipelining
|
|
825
|
+
|
|
826
|
+
### Automatic Pipelining
|
|
827
|
+
|
|
828
|
+
Node-redis automatically pipelines commands that are queued in the same tick:
|
|
829
|
+
|
|
830
|
+
```javascript
|
|
831
|
+
// These commands are automatically batched and sent together
|
|
832
|
+
const [result1, result2, result3] = await Promise.all([
|
|
833
|
+
client.set('key1', 'value1'),
|
|
834
|
+
client.set('key2', 'value2'),
|
|
835
|
+
client.get('key1')
|
|
836
|
+
]);
|
|
837
|
+
|
|
838
|
+
console.log(result1); // 'OK'
|
|
839
|
+
console.log(result2); // 'OK'
|
|
840
|
+
console.log(result3); // 'value1'
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Manual Pipelining with Multi (No Atomicity)
|
|
844
|
+
|
|
845
|
+
```javascript
|
|
846
|
+
// Execute commands as pipeline without transaction semantics
|
|
847
|
+
const results = await client
|
|
848
|
+
.multi()
|
|
849
|
+
.set('key1', 'value1')
|
|
850
|
+
.set('key2', 'value2')
|
|
851
|
+
.get('key1')
|
|
852
|
+
.mGet(['key1', 'key2'])
|
|
853
|
+
.exec();
|
|
854
|
+
|
|
855
|
+
console.log(results);
|
|
856
|
+
// ['OK', 'OK', 'value1', ['value1', 'value2']]
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### Large Batch Operations
|
|
860
|
+
|
|
861
|
+
```javascript
|
|
862
|
+
async function batchSetKeys(keyValuePairs) {
|
|
863
|
+
const pipeline = client.multi();
|
|
864
|
+
|
|
865
|
+
for (const [key, value] of Object.entries(keyValuePairs)) {
|
|
866
|
+
pipeline.set(key, value);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const results = await pipeline.exec();
|
|
870
|
+
return results;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Usage
|
|
874
|
+
const data = {
|
|
875
|
+
'user:1': 'Alice',
|
|
876
|
+
'user:2': 'Bob',
|
|
877
|
+
'user:3': 'Charlie'
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
await batchSetKeys(data);
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## Pub/Sub
|
|
886
|
+
|
|
887
|
+
### Basic Subscriber
|
|
888
|
+
|
|
889
|
+
```javascript
|
|
890
|
+
import { createClient } from 'redis';
|
|
891
|
+
|
|
892
|
+
const client = createClient();
|
|
893
|
+
await client.connect();
|
|
894
|
+
|
|
895
|
+
// Create dedicated connection for subscribing
|
|
896
|
+
const subscriber = client.duplicate();
|
|
897
|
+
await subscriber.connect();
|
|
898
|
+
|
|
899
|
+
// Subscribe to channel
|
|
900
|
+
await subscriber.subscribe('notifications', (message) => {
|
|
901
|
+
console.log('Received message:', message);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
console.log('Subscribed to notifications channel');
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### Basic Publisher
|
|
908
|
+
|
|
909
|
+
```javascript
|
|
910
|
+
import { createClient } from 'redis';
|
|
911
|
+
|
|
912
|
+
const publisher = createClient();
|
|
913
|
+
await publisher.connect();
|
|
914
|
+
|
|
915
|
+
// Publish messages
|
|
916
|
+
await publisher.publish('notifications', 'Hello, World!');
|
|
917
|
+
await publisher.publish('notifications', JSON.stringify({
|
|
918
|
+
type: 'alert',
|
|
919
|
+
message: 'System update'
|
|
920
|
+
}));
|
|
921
|
+
|
|
922
|
+
console.log('Messages published');
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### Multiple Channels
|
|
926
|
+
|
|
927
|
+
```javascript
|
|
928
|
+
// Subscribe to multiple channels
|
|
929
|
+
await subscriber.subscribe('channel1', (message) => {
|
|
930
|
+
console.log('Channel1:', message);
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
await subscriber.subscribe('channel2', (message) => {
|
|
934
|
+
console.log('Channel2:', message);
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
await subscriber.subscribe('channel3', (message) => {
|
|
938
|
+
console.log('Channel3:', message);
|
|
939
|
+
});
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### Pattern-Based Subscription
|
|
943
|
+
|
|
944
|
+
```javascript
|
|
945
|
+
// Subscribe to channels matching pattern
|
|
946
|
+
await subscriber.pSubscribe('user:*', (message, channel) => {
|
|
947
|
+
console.log(`Message from ${channel}:`, message);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
// Publish to matching channels
|
|
951
|
+
await publisher.publish('user:1000', 'User 1000 logged in');
|
|
952
|
+
await publisher.publish('user:2000', 'User 2000 logged out');
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
### Unsubscribe
|
|
956
|
+
|
|
957
|
+
```javascript
|
|
958
|
+
// Unsubscribe from specific channel
|
|
959
|
+
await subscriber.unsubscribe('channel1');
|
|
960
|
+
|
|
961
|
+
// Unsubscribe from all channels
|
|
962
|
+
await subscriber.unsubscribe();
|
|
963
|
+
|
|
964
|
+
// Unsubscribe from pattern
|
|
965
|
+
await subscriber.pUnsubscribe('user:*');
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### Complete Pub/Sub Example
|
|
969
|
+
|
|
970
|
+
```javascript
|
|
971
|
+
import { createClient } from 'redis';
|
|
972
|
+
|
|
973
|
+
// Subscriber setup
|
|
974
|
+
async function setupSubscriber() {
|
|
975
|
+
const client = createClient();
|
|
976
|
+
await client.connect();
|
|
977
|
+
|
|
978
|
+
const subscriber = client.duplicate();
|
|
979
|
+
await subscriber.connect();
|
|
980
|
+
|
|
981
|
+
await subscriber.subscribe('chat:room1', (message) => {
|
|
982
|
+
const data = JSON.parse(message);
|
|
983
|
+
console.log(`${data.user}: ${data.text}`);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
return subscriber;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Publisher setup
|
|
990
|
+
async function setupPublisher() {
|
|
991
|
+
const publisher = createClient();
|
|
992
|
+
await publisher.connect();
|
|
993
|
+
return publisher;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Usage
|
|
997
|
+
const subscriber = await setupSubscriber();
|
|
998
|
+
const publisher = await setupPublisher();
|
|
999
|
+
|
|
1000
|
+
// Publish chat messages
|
|
1001
|
+
await publisher.publish('chat:room1', JSON.stringify({
|
|
1002
|
+
user: 'Alice',
|
|
1003
|
+
text: 'Hello everyone!'
|
|
1004
|
+
}));
|
|
1005
|
+
|
|
1006
|
+
await publisher.publish('chat:room1', JSON.stringify({
|
|
1007
|
+
user: 'Bob',
|
|
1008
|
+
text: 'Hi Alice!'
|
|
1009
|
+
}));
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
## Redis Streams
|
|
1015
|
+
|
|
1016
|
+
### Add to Stream (XADD)
|
|
1017
|
+
|
|
1018
|
+
```javascript
|
|
1019
|
+
// Add entry to stream
|
|
1020
|
+
const id = await client.xAdd('events', '*', {
|
|
1021
|
+
user: 'alice',
|
|
1022
|
+
action: 'login',
|
|
1023
|
+
timestamp: Date.now()
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
console.log('Entry ID:', id); // '1234567890123-0'
|
|
1027
|
+
|
|
1028
|
+
// Add with specific ID
|
|
1029
|
+
await client.xAdd('events', '1234567890123-1', {
|
|
1030
|
+
user: 'bob',
|
|
1031
|
+
action: 'logout'
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
// Add with maxLen (limit stream size)
|
|
1035
|
+
await client.xAdd('events', '*', {
|
|
1036
|
+
user: 'charlie',
|
|
1037
|
+
action: 'signup'
|
|
1038
|
+
}, {
|
|
1039
|
+
TRIM: {
|
|
1040
|
+
strategy: 'MAXLEN',
|
|
1041
|
+
strategyModifier: '~', // Approximate
|
|
1042
|
+
threshold: 1000
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Read from Stream (XREAD)
|
|
1048
|
+
|
|
1049
|
+
```javascript
|
|
1050
|
+
// Read new entries
|
|
1051
|
+
const messages = await client.xRead(
|
|
1052
|
+
{ key: 'events', id: '0' },
|
|
1053
|
+
{ COUNT: 10 }
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
console.log(messages);
|
|
1057
|
+
// [{ name: 'events', messages: [{ id: '...', message: {...} }] }]
|
|
1058
|
+
|
|
1059
|
+
// Read from multiple streams
|
|
1060
|
+
const multiStream = await client.xRead([
|
|
1061
|
+
{ key: 'stream1', id: '0' },
|
|
1062
|
+
{ key: 'stream2', id: '0' }
|
|
1063
|
+
]);
|
|
1064
|
+
|
|
1065
|
+
// Blocking read (wait for new entries)
|
|
1066
|
+
const newMessages = await client.xRead(
|
|
1067
|
+
{ key: 'events', id: '$' }, // '$' means only new messages
|
|
1068
|
+
{ BLOCK: 5000 } // Wait up to 5 seconds
|
|
1069
|
+
);
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
### Stream Range Queries
|
|
1073
|
+
|
|
1074
|
+
```javascript
|
|
1075
|
+
// Read all entries
|
|
1076
|
+
const all = await client.xRange('events', '-', '+');
|
|
1077
|
+
|
|
1078
|
+
// Read range with IDs
|
|
1079
|
+
const range = await client.xRange('events', '1234567890000', '1234567899999');
|
|
1080
|
+
|
|
1081
|
+
// Read with limit
|
|
1082
|
+
const limited = await client.xRange('events', '-', '+', { COUNT: 100 });
|
|
1083
|
+
|
|
1084
|
+
// Reverse range
|
|
1085
|
+
const reverse = await client.xRevRange('events', '+', '-', { COUNT: 10 });
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
### Stream Length and Info
|
|
1089
|
+
|
|
1090
|
+
```javascript
|
|
1091
|
+
// Get stream length
|
|
1092
|
+
const length = await client.xLen('events');
|
|
1093
|
+
|
|
1094
|
+
// Get stream info
|
|
1095
|
+
const info = await client.xInfo('events');
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
### Consumer Groups
|
|
1099
|
+
|
|
1100
|
+
```javascript
|
|
1101
|
+
// Create consumer group
|
|
1102
|
+
await client.xGroupCreate('events', 'processors', '0', {
|
|
1103
|
+
MKSTREAM: true // Create stream if it doesn't exist
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
// Read as consumer group
|
|
1107
|
+
const messages = await client.xReadGroup(
|
|
1108
|
+
'processors',
|
|
1109
|
+
'consumer1',
|
|
1110
|
+
{ key: 'events', id: '>' }, // '>' means undelivered messages
|
|
1111
|
+
{ COUNT: 10, BLOCK: 5000 }
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
// Process messages and acknowledge
|
|
1115
|
+
for (const stream of messages) {
|
|
1116
|
+
for (const message of stream.messages) {
|
|
1117
|
+
// Process message
|
|
1118
|
+
console.log('Processing:', message.id, message.message);
|
|
1119
|
+
|
|
1120
|
+
// Acknowledge message
|
|
1121
|
+
await client.xAck('events', 'processors', message.id);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
### Delete from Stream
|
|
1127
|
+
|
|
1128
|
+
```javascript
|
|
1129
|
+
// Delete specific entries
|
|
1130
|
+
await client.xDel('events', ['1234567890123-0', '1234567890123-1']);
|
|
1131
|
+
|
|
1132
|
+
// Trim stream
|
|
1133
|
+
await client.xTrim('events', 'MAXLEN', 1000);
|
|
1134
|
+
|
|
1135
|
+
// Approximate trim
|
|
1136
|
+
await client.xTrim('events', 'MAXLEN', { strategyModifier: '~', threshold: 1000 });
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
### Complete Stream Example
|
|
1140
|
+
|
|
1141
|
+
```javascript
|
|
1142
|
+
import { createClient } from 'redis';
|
|
1143
|
+
|
|
1144
|
+
const client = createClient();
|
|
1145
|
+
await client.connect();
|
|
1146
|
+
|
|
1147
|
+
// Producer
|
|
1148
|
+
async function addEvent(eventData) {
|
|
1149
|
+
return await client.xAdd('events', '*', eventData);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Consumer with consumer group
|
|
1153
|
+
async function processEvents() {
|
|
1154
|
+
// Create group if not exists
|
|
1155
|
+
try {
|
|
1156
|
+
await client.xGroupCreate('events', 'workers', '0', { MKSTREAM: true });
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
// Group already exists
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
while (true) {
|
|
1162
|
+
const messages = await client.xReadGroup(
|
|
1163
|
+
'workers',
|
|
1164
|
+
'worker1',
|
|
1165
|
+
{ key: 'events', id: '>' },
|
|
1166
|
+
{ COUNT: 10, BLOCK: 5000 }
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
if (messages && messages.length > 0) {
|
|
1170
|
+
for (const stream of messages) {
|
|
1171
|
+
for (const message of stream.messages) {
|
|
1172
|
+
try {
|
|
1173
|
+
// Process event
|
|
1174
|
+
console.log('Processing event:', message.message);
|
|
1175
|
+
|
|
1176
|
+
// Acknowledge
|
|
1177
|
+
await client.xAck('events', 'workers', message.id);
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
console.error('Error processing:', error);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Usage
|
|
1188
|
+
await addEvent({ type: 'user_login', user: 'alice' });
|
|
1189
|
+
await addEvent({ type: 'user_logout', user: 'bob' });
|
|
1190
|
+
|
|
1191
|
+
// Start consumer
|
|
1192
|
+
processEvents();
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
---
|
|
1196
|
+
|
|
1197
|
+
## Scripting with Lua
|
|
1198
|
+
|
|
1199
|
+
### Basic Script Execution (EVAL)
|
|
1200
|
+
|
|
1201
|
+
```javascript
|
|
1202
|
+
// Execute Lua script
|
|
1203
|
+
const result = await client.eval(
|
|
1204
|
+
`return redis.call('SET', KEYS[1], ARGV[1])`,
|
|
1205
|
+
{
|
|
1206
|
+
keys: ['mykey'],
|
|
1207
|
+
arguments: ['myvalue']
|
|
1208
|
+
}
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
console.log(result); // 'OK'
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
### Script with Multiple Operations
|
|
1215
|
+
|
|
1216
|
+
```javascript
|
|
1217
|
+
const script = `
|
|
1218
|
+
local current = redis.call('GET', KEYS[1])
|
|
1219
|
+
if current == false then
|
|
1220
|
+
current = 0
|
|
1221
|
+
end
|
|
1222
|
+
local new = tonumber(current) + tonumber(ARGV[1])
|
|
1223
|
+
redis.call('SET', KEYS[1], new)
|
|
1224
|
+
return new
|
|
1225
|
+
`;
|
|
1226
|
+
|
|
1227
|
+
const newValue = await client.eval(script, {
|
|
1228
|
+
keys: ['counter'],
|
|
1229
|
+
arguments: ['5']
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
console.log('New counter value:', newValue);
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
### Load and Execute Scripts (SCRIPT LOAD / EVALSHA)
|
|
1236
|
+
|
|
1237
|
+
```javascript
|
|
1238
|
+
// Load script and get SHA1
|
|
1239
|
+
const sha = await client.scriptLoad(`
|
|
1240
|
+
return redis.call('GET', KEYS[1])
|
|
1241
|
+
`);
|
|
1242
|
+
|
|
1243
|
+
console.log('Script SHA:', sha);
|
|
1244
|
+
|
|
1245
|
+
// Execute by SHA1 (more efficient for repeated calls)
|
|
1246
|
+
const value = await client.evalSha(sha, {
|
|
1247
|
+
keys: ['mykey'],
|
|
1248
|
+
arguments: []
|
|
1249
|
+
});
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
### Rate Limiting with Lua Script
|
|
1253
|
+
|
|
1254
|
+
```javascript
|
|
1255
|
+
const rateLimitScript = `
|
|
1256
|
+
local key = KEYS[1]
|
|
1257
|
+
local limit = tonumber(ARGV[1])
|
|
1258
|
+
local window = tonumber(ARGV[2])
|
|
1259
|
+
local current = redis.call('INCR', key)
|
|
1260
|
+
if current == 1 then
|
|
1261
|
+
redis.call('EXPIRE', key, window)
|
|
1262
|
+
end
|
|
1263
|
+
if current > limit then
|
|
1264
|
+
return 0
|
|
1265
|
+
else
|
|
1266
|
+
return 1
|
|
1267
|
+
end
|
|
1268
|
+
`;
|
|
1269
|
+
|
|
1270
|
+
async function checkRateLimit(userId, limit = 10, window = 60) {
|
|
1271
|
+
const allowed = await client.eval(rateLimitScript, {
|
|
1272
|
+
keys: [`ratelimit:${userId}`],
|
|
1273
|
+
arguments: [limit.toString(), window.toString()]
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
return allowed === 1;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Usage
|
|
1280
|
+
const allowed = await checkRateLimit('user:1000', 10, 60);
|
|
1281
|
+
if (allowed) {
|
|
1282
|
+
console.log('Request allowed');
|
|
1283
|
+
} else {
|
|
1284
|
+
console.log('Rate limit exceeded');
|
|
1285
|
+
}
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
### Atomic Get and Delete
|
|
1289
|
+
|
|
1290
|
+
```javascript
|
|
1291
|
+
const getAndDelete = `
|
|
1292
|
+
local value = redis.call('GET', KEYS[1])
|
|
1293
|
+
if value then
|
|
1294
|
+
redis.call('DEL', KEYS[1])
|
|
1295
|
+
end
|
|
1296
|
+
return value
|
|
1297
|
+
`;
|
|
1298
|
+
|
|
1299
|
+
const value = await client.eval(getAndDelete, {
|
|
1300
|
+
keys: ['temp:key'],
|
|
1301
|
+
arguments: []
|
|
1302
|
+
});
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
---
|
|
1306
|
+
|
|
1307
|
+
## JSON Support (RedisJSON)
|
|
1308
|
+
|
|
1309
|
+
### Basic JSON Operations
|
|
1310
|
+
|
|
1311
|
+
```javascript
|
|
1312
|
+
// Set JSON document
|
|
1313
|
+
await client.json.set('user:1', '$', {
|
|
1314
|
+
name: 'John Doe',
|
|
1315
|
+
email: '[email protected]',
|
|
1316
|
+
age: 30,
|
|
1317
|
+
tags: ['developer', 'redis']
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
// Get entire JSON document
|
|
1321
|
+
const user = await client.json.get('user:1');
|
|
1322
|
+
console.log(user);
|
|
1323
|
+
|
|
1324
|
+
// Get specific path
|
|
1325
|
+
const name = await client.json.get('user:1', { path: '$.name' });
|
|
1326
|
+
const age = await client.json.get('user:1', { path: '$.age' });
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
### Update JSON Fields
|
|
1330
|
+
|
|
1331
|
+
```javascript
|
|
1332
|
+
// Set specific field
|
|
1333
|
+
await client.json.set('user:1', '$.email', '"[email protected]"');
|
|
1334
|
+
|
|
1335
|
+
// Update nested field
|
|
1336
|
+
await client.json.set('user:1', '$.address.city', '"New York"');
|
|
1337
|
+
|
|
1338
|
+
// Delete field
|
|
1339
|
+
await client.json.del('user:1', '$.age');
|
|
1340
|
+
```
|
|
1341
|
+
|
|
1342
|
+
### JSON Array Operations
|
|
1343
|
+
|
|
1344
|
+
```javascript
|
|
1345
|
+
// Append to array
|
|
1346
|
+
await client.json.arrAppend('user:1', '$.tags', '"nodejs"', '"javascript"');
|
|
1347
|
+
|
|
1348
|
+
// Get array length
|
|
1349
|
+
const length = await client.json.arrLen('user:1', '$.tags');
|
|
1350
|
+
|
|
1351
|
+
// Pop from array
|
|
1352
|
+
await client.json.arrPop('user:1', '$.tags', -1); // Pop last element
|
|
1353
|
+
|
|
1354
|
+
// Insert into array
|
|
1355
|
+
await client.json.arrInsert('user:1', '$.tags', 0, '"beginner"');
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
### JSON Numeric Operations
|
|
1359
|
+
|
|
1360
|
+
```javascript
|
|
1361
|
+
// Increment numeric value
|
|
1362
|
+
await client.json.numIncrBy('user:1', '$.loginCount', 1);
|
|
1363
|
+
|
|
1364
|
+
// Multiply numeric value
|
|
1365
|
+
await client.json.numMultBy('user:1', '$.score', 1.5);
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
---
|
|
1369
|
+
|
|
1370
|
+
## Time Series (RedisTimeSeries)
|
|
1371
|
+
|
|
1372
|
+
### Basic Time Series Operations
|
|
1373
|
+
|
|
1374
|
+
```javascript
|
|
1375
|
+
// Create time series
|
|
1376
|
+
await client.ts.create('temperature:sensor1', {
|
|
1377
|
+
RETENTION: 86400000, // Retain for 24 hours
|
|
1378
|
+
LABELS: { sensor: 'temp', location: 'room1' }
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
// Add data points
|
|
1382
|
+
await client.ts.add('temperature:sensor1', '*', 22.5); // Current time
|
|
1383
|
+
await client.ts.add('temperature:sensor1', Date.now(), 23.0); // Specific time
|
|
1384
|
+
|
|
1385
|
+
// Add multiple data points
|
|
1386
|
+
await client.ts.mAdd([
|
|
1387
|
+
{ key: 'temperature:sensor1', timestamp: '*', value: 22.8 },
|
|
1388
|
+
{ key: 'temperature:sensor2', timestamp: '*', value: 21.5 }
|
|
1389
|
+
]);
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
### Query Time Series
|
|
1393
|
+
|
|
1394
|
+
```javascript
|
|
1395
|
+
// Get range of data
|
|
1396
|
+
const data = await client.ts.range('temperature:sensor1', '-', '+');
|
|
1397
|
+
|
|
1398
|
+
// Get range with aggregation
|
|
1399
|
+
const hourlyAvg = await client.ts.range('temperature:sensor1', '-', '+', {
|
|
1400
|
+
AGGREGATION: {
|
|
1401
|
+
type: 'AVG',
|
|
1402
|
+
timeBucket: 3600000 // 1 hour
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
// Get last value
|
|
1407
|
+
const latest = await client.ts.get('temperature:sensor1');
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
---
|
|
1411
|
+
|
|
1412
|
+
## Search and Query (RediSearch)
|
|
1413
|
+
|
|
1414
|
+
### Create Index
|
|
1415
|
+
|
|
1416
|
+
```javascript
|
|
1417
|
+
// Create index on hash
|
|
1418
|
+
await client.ft.create('idx:users', {
|
|
1419
|
+
'$.name': {
|
|
1420
|
+
type: 'TEXT',
|
|
1421
|
+
AS: 'name'
|
|
1422
|
+
},
|
|
1423
|
+
'$.age': {
|
|
1424
|
+
type: 'NUMERIC',
|
|
1425
|
+
AS: 'age'
|
|
1426
|
+
},
|
|
1427
|
+
'$.email': {
|
|
1428
|
+
type: 'TAG',
|
|
1429
|
+
AS: 'email'
|
|
1430
|
+
}
|
|
1431
|
+
}, {
|
|
1432
|
+
ON: 'HASH',
|
|
1433
|
+
PREFIX: 'user:'
|
|
1434
|
+
});
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
### Search Documents
|
|
1438
|
+
|
|
1439
|
+
```javascript
|
|
1440
|
+
// Search by text
|
|
1441
|
+
const results = await client.ft.search('idx:users', 'John');
|
|
1442
|
+
|
|
1443
|
+
// Search with filters
|
|
1444
|
+
const filtered = await client.ft.search('idx:users', '@age:[25 35]');
|
|
1445
|
+
|
|
1446
|
+
// Search with multiple conditions
|
|
1447
|
+
const complex = await client.ft.search('idx:users', '@name:John @age:[20 40]');
|
|
1448
|
+
|
|
1449
|
+
// Get search count
|
|
1450
|
+
const count = await client.ft.search('idx:users', '*', { LIMIT: { from: 0, size: 0 } });
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
---
|
|
1454
|
+
|
|
1455
|
+
## Geospatial Operations
|
|
1456
|
+
|
|
1457
|
+
### Add Geo Points
|
|
1458
|
+
|
|
1459
|
+
```javascript
|
|
1460
|
+
// Add location
|
|
1461
|
+
await client.geoAdd('locations', {
|
|
1462
|
+
longitude: -122.4194,
|
|
1463
|
+
latitude: 37.7749,
|
|
1464
|
+
member: 'San Francisco'
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
// Add multiple locations
|
|
1468
|
+
await client.geoAdd('locations', [
|
|
1469
|
+
{ longitude: -118.2437, latitude: 34.0522, member: 'Los Angeles' },
|
|
1470
|
+
{ longitude: -73.9352, latitude: 40.7306, member: 'New York' }
|
|
1471
|
+
]);
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
### Query Geo Points
|
|
1475
|
+
|
|
1476
|
+
```javascript
|
|
1477
|
+
// Get position
|
|
1478
|
+
const position = await client.geoPos('locations', 'San Francisco');
|
|
1479
|
+
console.log(position); // [{ longitude: -122.4194, latitude: 37.7749 }]
|
|
1480
|
+
|
|
1481
|
+
// Get distance between points
|
|
1482
|
+
const distance = await client.geoDist('locations', 'San Francisco', 'Los Angeles', 'mi');
|
|
1483
|
+
console.log(`Distance: ${distance} miles`);
|
|
1484
|
+
|
|
1485
|
+
// Search by radius
|
|
1486
|
+
const nearby = await client.geoRadius('locations', {
|
|
1487
|
+
longitude: -122.4194,
|
|
1488
|
+
latitude: 37.7749
|
|
1489
|
+
}, 500, 'mi');
|
|
1490
|
+
|
|
1491
|
+
// Search by member
|
|
1492
|
+
const nearSF = await client.geoRadiusByMember('locations', 'San Francisco', 600, 'mi', {
|
|
1493
|
+
WITHDIST: true,
|
|
1494
|
+
WITHCOORD: true
|
|
1495
|
+
});
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
## HyperLogLog (Cardinality Estimation)
|
|
1501
|
+
|
|
1502
|
+
```javascript
|
|
1503
|
+
// Add elements
|
|
1504
|
+
await client.pfAdd('unique:visitors', ['user1', 'user2', 'user3']);
|
|
1505
|
+
await client.pfAdd('unique:visitors', ['user2', 'user4']); // user2 counted once
|
|
1506
|
+
|
|
1507
|
+
// Get count
|
|
1508
|
+
const count = await client.pfCount('unique:visitors'); // ~4
|
|
1509
|
+
|
|
1510
|
+
// Merge multiple HyperLogLogs
|
|
1511
|
+
await client.pfMerge('combined', ['unique:visitors:day1', 'unique:visitors:day2']);
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
---
|
|
1515
|
+
|
|
1516
|
+
## Bitmap Operations
|
|
1517
|
+
|
|
1518
|
+
```javascript
|
|
1519
|
+
// Set bit
|
|
1520
|
+
await client.setBit('login:2024-01-15', 100, 1); // User 100 logged in
|
|
1521
|
+
|
|
1522
|
+
// Get bit
|
|
1523
|
+
const bit = await client.getBit('login:2024-01-15', 100); // 1
|
|
1524
|
+
|
|
1525
|
+
// Count set bits
|
|
1526
|
+
const count = await client.bitCount('login:2024-01-15');
|
|
1527
|
+
|
|
1528
|
+
// Bitwise operations
|
|
1529
|
+
await client.bitOp('AND', 'result', ['bitmap1', 'bitmap2']);
|
|
1530
|
+
await client.bitOp('OR', 'result', ['bitmap1', 'bitmap2']);
|
|
1531
|
+
await client.bitOp('XOR', 'result', ['bitmap1', 'bitmap2']);
|
|
1532
|
+
|
|
1533
|
+
// Find first bit
|
|
1534
|
+
const pos = await client.bitPos('login:2024-01-15', 1); // First set bit
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1537
|
+
---
|
|
1538
|
+
|
|
1539
|
+
## Server and Connection Management
|
|
1540
|
+
|
|
1541
|
+
### Server Information
|
|
1542
|
+
|
|
1543
|
+
```javascript
|
|
1544
|
+
// Get server info
|
|
1545
|
+
const info = await client.info();
|
|
1546
|
+
console.log(info);
|
|
1547
|
+
|
|
1548
|
+
// Get specific section
|
|
1549
|
+
const stats = await client.info('stats');
|
|
1550
|
+
const memory = await client.info('memory');
|
|
1551
|
+
|
|
1552
|
+
// Ping server
|
|
1553
|
+
const pong = await client.ping(); // 'PONG'
|
|
1554
|
+
|
|
1555
|
+
// Get server time
|
|
1556
|
+
const time = await client.time();
|
|
1557
|
+
```
|
|
1558
|
+
|
|
1559
|
+
### Database Management
|
|
1560
|
+
|
|
1561
|
+
```javascript
|
|
1562
|
+
// Select database (0-15 by default)
|
|
1563
|
+
await client.select(1);
|
|
1564
|
+
|
|
1565
|
+
// Get database size
|
|
1566
|
+
const size = await client.dbSize();
|
|
1567
|
+
|
|
1568
|
+
// Flush current database
|
|
1569
|
+
await client.flushDb();
|
|
1570
|
+
|
|
1571
|
+
// Flush all databases
|
|
1572
|
+
await client.flushAll();
|
|
1573
|
+
|
|
1574
|
+
// Save database to disk
|
|
1575
|
+
await client.save();
|
|
1576
|
+
|
|
1577
|
+
// Background save
|
|
1578
|
+
await client.bgSave();
|
|
1579
|
+
|
|
1580
|
+
// Get last save time
|
|
1581
|
+
const lastSave = await client.lastSave();
|
|
1582
|
+
```
|
|
1583
|
+
|
|
1584
|
+
### Client Management
|
|
1585
|
+
|
|
1586
|
+
```javascript
|
|
1587
|
+
// Get client list
|
|
1588
|
+
const clients = await client.clientList();
|
|
1589
|
+
|
|
1590
|
+
// Get client ID
|
|
1591
|
+
const id = await client.clientId();
|
|
1592
|
+
|
|
1593
|
+
// Set client name
|
|
1594
|
+
await client.clientSetName('my-app');
|
|
1595
|
+
|
|
1596
|
+
// Get client name
|
|
1597
|
+
const name = await client.clientGetName();
|
|
1598
|
+
|
|
1599
|
+
// Get client info
|
|
1600
|
+
const clientInfo = await client.clientInfo();
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
---
|
|
1604
|
+
|
|
1605
|
+
## Error Handling
|
|
1606
|
+
|
|
1607
|
+
### Connection Errors
|
|
1608
|
+
|
|
1609
|
+
```javascript
|
|
1610
|
+
import { createClient } from 'redis';
|
|
1611
|
+
|
|
1612
|
+
const client = createClient({
|
|
1613
|
+
socket: {
|
|
1614
|
+
host: 'localhost',
|
|
1615
|
+
port: 6379,
|
|
1616
|
+
reconnectStrategy: (retries) => {
|
|
1617
|
+
if (retries > 10) {
|
|
1618
|
+
return new Error('Max retries reached');
|
|
1619
|
+
}
|
|
1620
|
+
return Math.min(retries * 100, 3000);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
client.on('error', (err) => {
|
|
1626
|
+
console.error('Redis error:', err);
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
client.on('connect', () => {
|
|
1630
|
+
console.log('Connected to Redis');
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
client.on('reconnecting', () => {
|
|
1634
|
+
console.log('Reconnecting to Redis...');
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
client.on('ready', () => {
|
|
1638
|
+
console.log('Redis client ready');
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
await client.connect();
|
|
1642
|
+
```
|
|
1643
|
+
|
|
1644
|
+
### Command Errors
|
|
1645
|
+
|
|
1646
|
+
```javascript
|
|
1647
|
+
try {
|
|
1648
|
+
await client.get('nonexistent');
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
console.error('Command error:', error);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// Multiple operations with individual error handling
|
|
1654
|
+
const results = await client.multi()
|
|
1655
|
+
.set('key1', 'value1')
|
|
1656
|
+
.incr('not-a-number') // This will error
|
|
1657
|
+
.get('key1')
|
|
1658
|
+
.exec();
|
|
1659
|
+
|
|
1660
|
+
results.forEach((result, i) => {
|
|
1661
|
+
if (result instanceof Error) {
|
|
1662
|
+
console.error(`Command ${i} failed:`, result.message);
|
|
1663
|
+
} else {
|
|
1664
|
+
console.log(`Command ${i} result:`, result);
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
```
|
|
1668
|
+
|
|
1669
|
+
---
|
|
1670
|
+
|
|
1671
|
+
## Connection Pooling and Performance
|
|
1672
|
+
|
|
1673
|
+
### Configure Connection Pool
|
|
1674
|
+
|
|
1675
|
+
```javascript
|
|
1676
|
+
const client = createClient({
|
|
1677
|
+
socket: {
|
|
1678
|
+
host: 'localhost',
|
|
1679
|
+
port: 6379,
|
|
1680
|
+
connectTimeout: 5000,
|
|
1681
|
+
keepAlive: 5000
|
|
1682
|
+
},
|
|
1683
|
+
password: 'your_password'
|
|
1684
|
+
});
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
### Readonly Replicas
|
|
1688
|
+
|
|
1689
|
+
```javascript
|
|
1690
|
+
const client = createClient({
|
|
1691
|
+
socket: {
|
|
1692
|
+
host: 'localhost',
|
|
1693
|
+
port: 6379
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
// Send read commands to replicas
|
|
1698
|
+
await client.sendCommand(['READONLY']);
|
|
1699
|
+
```
|
|
1700
|
+
|
|
1701
|
+
---
|
|
1702
|
+
|
|
1703
|
+
## Cluster Support
|
|
1704
|
+
|
|
1705
|
+
### Connect to Cluster
|
|
1706
|
+
|
|
1707
|
+
```javascript
|
|
1708
|
+
import { createCluster } from 'redis';
|
|
1709
|
+
|
|
1710
|
+
const cluster = createCluster({
|
|
1711
|
+
rootNodes: [
|
|
1712
|
+
{ url: 'redis://localhost:7000' },
|
|
1713
|
+
{ url: 'redis://localhost:7001' },
|
|
1714
|
+
{ url: 'redis://localhost:7002' }
|
|
1715
|
+
]
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
cluster.on('error', (err) => console.error('Cluster error:', err));
|
|
1719
|
+
|
|
1720
|
+
await cluster.connect();
|
|
1721
|
+
|
|
1722
|
+
// Use cluster like regular client
|
|
1723
|
+
await cluster.set('key', 'value');
|
|
1724
|
+
const value = await cluster.get('key');
|
|
1725
|
+
|
|
1726
|
+
await cluster.quit();
|
|
1727
|
+
```
|
|
1728
|
+
|
|
1729
|
+
---
|
|
1730
|
+
|
|
1731
|
+
## Complete Application Example
|
|
1732
|
+
|
|
1733
|
+
```javascript
|
|
1734
|
+
import { createClient } from 'redis';
|
|
1735
|
+
import dotenv from 'dotenv';
|
|
1736
|
+
|
|
1737
|
+
dotenv.config();
|
|
1738
|
+
|
|
1739
|
+
class RedisManager {
|
|
1740
|
+
constructor() {
|
|
1741
|
+
this.client = null;
|
|
1742
|
+
this.subscriber = null;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
async connect() {
|
|
1746
|
+
this.client = createClient({
|
|
1747
|
+
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
1748
|
+
password: process.env.REDIS_PASSWORD
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
this.client.on('error', (err) => {
|
|
1752
|
+
console.error('Redis Client Error:', err);
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
await this.client.connect();
|
|
1756
|
+
console.log('Connected to Redis');
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
async cacheGet(key) {
|
|
1760
|
+
const value = await this.client.get(key);
|
|
1761
|
+
return value ? JSON.parse(value) : null;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
async cacheSet(key, value, ttl = 3600) {
|
|
1765
|
+
await this.client.set(key, JSON.stringify(value), { EX: ttl });
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
async invalidateCache(pattern) {
|
|
1769
|
+
const keys = [];
|
|
1770
|
+
for await (const key of this.client.scanIterator({ MATCH: pattern })) {
|
|
1771
|
+
keys.push(key);
|
|
1772
|
+
}
|
|
1773
|
+
if (keys.length > 0) {
|
|
1774
|
+
await this.client.del(keys);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
async trackUserActivity(userId, action) {
|
|
1779
|
+
const key = `activity:${userId}:${new Date().toISOString().split('T')[0]}`;
|
|
1780
|
+
await this.client.rPush(key, JSON.stringify({
|
|
1781
|
+
action,
|
|
1782
|
+
timestamp: Date.now()
|
|
1783
|
+
}));
|
|
1784
|
+
await this.client.expire(key, 86400 * 7); // Keep for 7 days
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
async getUserActivity(userId, date) {
|
|
1788
|
+
const key = `activity:${userId}:${date}`;
|
|
1789
|
+
const activities = await this.client.lRange(key, 0, -1);
|
|
1790
|
+
return activities.map(a => JSON.parse(a));
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
async incrementPageView(pageId) {
|
|
1794
|
+
await this.client.incr(`pageviews:${pageId}`);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
async getPageViews(pageId) {
|
|
1798
|
+
const views = await this.client.get(`pageviews:${pageId}`);
|
|
1799
|
+
return parseInt(views || '0');
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
async addToLeaderboard(userId, score) {
|
|
1803
|
+
await this.client.zAdd('leaderboard', { score, value: userId });
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
async getLeaderboard(count = 10) {
|
|
1807
|
+
const results = await this.client.zRangeWithScores('leaderboard', 0, count - 1, {
|
|
1808
|
+
REV: true
|
|
1809
|
+
});
|
|
1810
|
+
return results.map(r => ({ userId: r.value, score: r.score }));
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
async disconnect() {
|
|
1814
|
+
if (this.subscriber) {
|
|
1815
|
+
await this.subscriber.quit();
|
|
1816
|
+
}
|
|
1817
|
+
if (this.client) {
|
|
1818
|
+
await this.client.quit();
|
|
1819
|
+
}
|
|
1820
|
+
console.log('Disconnected from Redis');
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// Usage
|
|
1825
|
+
const redis = new RedisManager();
|
|
1826
|
+
await redis.connect();
|
|
1827
|
+
|
|
1828
|
+
// Cache operations
|
|
1829
|
+
await redis.cacheSet('user:1000', { name: 'John', email: '[email protected]' });
|
|
1830
|
+
const user = await redis.cacheGet('user:1000');
|
|
1831
|
+
console.log(user);
|
|
1832
|
+
|
|
1833
|
+
// Track activity
|
|
1834
|
+
await redis.trackUserActivity('user:1000', 'login');
|
|
1835
|
+
await redis.trackUserActivity('user:1000', 'view_profile');
|
|
1836
|
+
|
|
1837
|
+
// Get activity
|
|
1838
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1839
|
+
const activities = await redis.getUserActivity('user:1000', today);
|
|
1840
|
+
console.log(activities);
|
|
1841
|
+
|
|
1842
|
+
// Leaderboard
|
|
1843
|
+
await redis.addToLeaderboard('user:1000', 500);
|
|
1844
|
+
await redis.addToLeaderboard('user:2000', 750);
|
|
1845
|
+
await redis.addToLeaderboard('user:3000', 250);
|
|
1846
|
+
|
|
1847
|
+
const topUsers = await redis.getLeaderboard(3);
|
|
1848
|
+
console.log('Top users:', topUsers);
|
|
1849
|
+
|
|
1850
|
+
await redis.disconnect();
|
|
1851
|
+
```
|