@envoy1084/effect-redis 0.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/LICENSE +21 -0
- package/README.md +1077 -0
- package/dist/index.d.mts +106 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +383 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
# effect-redis
|
|
2
|
+
|
|
3
|
+
> A experimental, Effect wrapper for Redis, providing type-safe, composable Redis operations with support for transactions, pipelines, and all major Redis command groups.
|
|
4
|
+
|
|
5
|
+
## What is This?
|
|
6
|
+
|
|
7
|
+
**effect-redis** is a comprehensive Redis client wrapper built on top of [Effect](https://effect.website/) and the [redis](https://www.npmjs.com/package/redis) npm package. It provides:
|
|
8
|
+
|
|
9
|
+
- **Effect-based API**: Full integration with Effect's functional error handling, dependency injection, and compositional patterns
|
|
10
|
+
- **Type-safe operations**: TypeScript types for all Redis commands with proper argument and return type validation
|
|
11
|
+
- **Transaction support**: Built-in support for Redis `MULTI` transactions with automatic queuing
|
|
12
|
+
- **Pipeline support**: Batch multiple commands efficiently with automatic execution
|
|
13
|
+
- **Comprehensive command coverage**: Support for 11+ Redis command groups including strings, hashes, lists, sets, sorted sets, geospatial, JSON, bitmaps, HyperLogLog, scripting, and generic commands
|
|
14
|
+
- **Dependency-driven**: Leverages Effect's Layer system for elegant connection management and resource lifecycle handling
|
|
15
|
+
- **Error handling**: Unified error handling through Effect's error channel with custom `RedisError` type
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
****
|
|
21
|
+
```bash
|
|
22
|
+
npm install @envoy1084/effect-redis effect @effect/platform-node
|
|
23
|
+
# or
|
|
24
|
+
yarn add @envoy1084/effect-redis effect @effect/platform-node
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @envoy1084/effect-redis effect @effect/platform-node
|
|
27
|
+
# or
|
|
28
|
+
bun add @envoy1084/effect-redis effect @effect/platform-node
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Basic Usage
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { runMain } from "@effect/platform-node/NodeRuntime";
|
|
35
|
+
import {
|
|
36
|
+
layerWithOptions,
|
|
37
|
+
RedisCore,
|
|
38
|
+
RedisCoreLive,
|
|
39
|
+
} from "@envoy1084/effect-redis";
|
|
40
|
+
import { Effect, Layer } from "effect";
|
|
41
|
+
|
|
42
|
+
// Create the Redis layer with connection options
|
|
43
|
+
const RedisLayer = RedisCoreLive.pipe(
|
|
44
|
+
Layer.provideMerge(
|
|
45
|
+
layerWithOptions({
|
|
46
|
+
url: "redis://localhost:6379",
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Define your program
|
|
52
|
+
const program = Effect.gen(function* () {
|
|
53
|
+
const redis = yield* RedisCore;
|
|
54
|
+
|
|
55
|
+
// Use redis commands
|
|
56
|
+
yield* redis.set("mykey", "myvalue");
|
|
57
|
+
const value = yield* redis.get("mykey");
|
|
58
|
+
yield* Effect.log(`Got value: ${value}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Run the program
|
|
62
|
+
program.pipe(Effect.provide(RedisLayer), runMain);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Connection Setup
|
|
66
|
+
|
|
67
|
+
### Providing Layers to Your Program
|
|
68
|
+
|
|
69
|
+
effect-redis uses Effect's Layer system for dependency injection and resource management. Here's how to configure connections:
|
|
70
|
+
|
|
71
|
+
#### 1. **With Custom Connection Options**
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { layerWithOptions } from "@envoy1084/effect-redis";
|
|
75
|
+
import { Layer } from "effect";
|
|
76
|
+
|
|
77
|
+
const RedisLayer = RedisCoreLive.pipe(
|
|
78
|
+
Layer.provideMerge(
|
|
79
|
+
layerWithOptions({
|
|
80
|
+
url: "redis://localhost:6379",
|
|
81
|
+
password: "your-password",
|
|
82
|
+
database: 0,
|
|
83
|
+
// ... all RedisClientOptions from the redis package
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### 2. **Multiple Redis Instances**
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { RedisCore, RedisCoreLive, layerWithOptions } from "@envoy1084/effect-redis";
|
|
93
|
+
import { Context, Layer, Effect } from "effect";
|
|
94
|
+
|
|
95
|
+
// Define tags for different Redis instances
|
|
96
|
+
class PrimaryRedis extends Context.Tag("PrimaryRedis")<
|
|
97
|
+
PrimaryRedis,
|
|
98
|
+
RedisCoreShape
|
|
99
|
+
>() {}
|
|
100
|
+
|
|
101
|
+
class CacheRedis extends Context.Tag("CacheRedis")<
|
|
102
|
+
CacheRedis,
|
|
103
|
+
RedisCoreShape
|
|
104
|
+
>() {}
|
|
105
|
+
|
|
106
|
+
// Create separate layers
|
|
107
|
+
const PrimaryRedisLayer = Layer.effect(
|
|
108
|
+
PrimaryRedis,
|
|
109
|
+
Effect.gen(function* () {
|
|
110
|
+
const { client } = yield* RedisConnection;
|
|
111
|
+
return makeRedisCore(client);
|
|
112
|
+
}),
|
|
113
|
+
).pipe(
|
|
114
|
+
Layer.provideMerge(
|
|
115
|
+
layerWithOptions({ url: "redis://primary:6379" }),
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Use in your program
|
|
120
|
+
const program = Effect.gen(function* () {
|
|
121
|
+
const primary = yield* PrimaryRedis;
|
|
122
|
+
const cache = yield* CacheRedis;
|
|
123
|
+
// Use both instances
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Usage Guide
|
|
128
|
+
|
|
129
|
+
### String Commands
|
|
130
|
+
|
|
131
|
+
String commands operate on text values.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const program = Effect.gen(function* () {
|
|
135
|
+
const redis = yield* RedisCore;
|
|
136
|
+
|
|
137
|
+
// Basic GET/SET
|
|
138
|
+
yield* redis.set("name", "Alice");
|
|
139
|
+
const name = yield* redis.get("name");
|
|
140
|
+
|
|
141
|
+
// Multiple keys
|
|
142
|
+
yield* redis.mSet([
|
|
143
|
+
["key1", "value1"],
|
|
144
|
+
["key2", "value2"],
|
|
145
|
+
]);
|
|
146
|
+
const values = yield* redis.mGet(["key1", "key2"]);
|
|
147
|
+
|
|
148
|
+
// Increment/Decrement
|
|
149
|
+
yield* redis.set("counter", "10");
|
|
150
|
+
yield* redis.incr("counter"); // 11
|
|
151
|
+
yield* redis.incrBy("counter", 5); // 16
|
|
152
|
+
yield* redis.decr("counter"); // 15
|
|
153
|
+
yield* redis.decrBy("counter", 3); // 12
|
|
154
|
+
|
|
155
|
+
// Float operations
|
|
156
|
+
yield* redis.set("price", "19.99");
|
|
157
|
+
yield* redis.incrByFloat("price", 0.01); // 20.00
|
|
158
|
+
|
|
159
|
+
// Advanced operations
|
|
160
|
+
yield* redis.append("name", " Smith");
|
|
161
|
+
const length = yield* redis.strLen("name");
|
|
162
|
+
const substring = yield* redis.getRange("name", 0, 4);
|
|
163
|
+
const oldValue = yield* redis.getSet("name", "Bob");
|
|
164
|
+
yield* redis.setNX("newkey", "value"); // Only if key doesn't exist
|
|
165
|
+
yield* redis.setEx("tempkey", 60, "value"); // With expiration
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Supported String Commands:**
|
|
170
|
+
- `get`, `set`, `mGet`, `mSet`, `setNX`, `mSetNX`
|
|
171
|
+
- `incr`, `incrBy`, `incrByFloat`, `decr`, `decrBy`
|
|
172
|
+
- `append`, `strLen`, `getRange`, `setRange`
|
|
173
|
+
- `getSet`, `getDel`, `getEx`, `setEx`, `pSetEx`
|
|
174
|
+
- `getdel`, `delEx`, `digest`, `lcs`, `mSetEx`
|
|
175
|
+
|
|
176
|
+
### Hash Commands
|
|
177
|
+
|
|
178
|
+
Hash commands operate on objects with named fields.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const program = Effect.gen(function* () {
|
|
182
|
+
const redis = yield* RedisCore;
|
|
183
|
+
|
|
184
|
+
// Basic operations
|
|
185
|
+
yield* redis.hSet("user:1", "name", "Alice");
|
|
186
|
+
yield* redis.hSet("user:1", {
|
|
187
|
+
"age": "30",
|
|
188
|
+
"email": "alice@example.com",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const name = yield* redis.hGet("user:1", "name");
|
|
192
|
+
const allFields = yield* redis.hGetAll("user:1");
|
|
193
|
+
const keys = yield* redis.hKeys("user:1");
|
|
194
|
+
const values = yield* redis.hVals("user:1");
|
|
195
|
+
|
|
196
|
+
// Check existence
|
|
197
|
+
const exists = yield* redis.hExists("user:1", "name");
|
|
198
|
+
|
|
199
|
+
// Increment
|
|
200
|
+
yield* redis.hSet("user:1", "age", "30");
|
|
201
|
+
yield* redis.hIncrBy("user:1", "age", 1); // 31
|
|
202
|
+
yield* redis.hIncrByFloat("user:1", "score", 2.5);
|
|
203
|
+
|
|
204
|
+
// Multiple operations
|
|
205
|
+
const fields = yield* redis.hmGet("user:1", ["name", "email"]);
|
|
206
|
+
|
|
207
|
+
// Delete and set operations
|
|
208
|
+
yield* redis.hSetNX("user:1", "name", "Bob"); // Won't update
|
|
209
|
+
yield* redis.hDel("user:1", "email");
|
|
210
|
+
|
|
211
|
+
// Expiration (Redis 7.4+)
|
|
212
|
+
yield* redis.hExpire("user:1", 3600, ["name", "email"]);
|
|
213
|
+
const ttl = yield* redis.hTTL("user:1", "name");
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Supported Hash Commands:**
|
|
218
|
+
- `hGet`, `hSet`, `hGetAll`, `hmGet`, `hKeys`, `hVals`, `hLen`
|
|
219
|
+
- `hExists`, `hSetNX`, `hDel`, `hGetDel`
|
|
220
|
+
- `hIncrBy`, `hIncrByFloat`, `hStrLen`
|
|
221
|
+
- `hExpire`, `hExpireAt`, `hExpireTime`, `hPersist`
|
|
222
|
+
- `hpExpire`, `hpExpireAt`, `hpExpireTime`, `hpTTL`, `hTTL`
|
|
223
|
+
- `hRandField`, `hScan`, `hGetEx`, `hSetEx`
|
|
224
|
+
|
|
225
|
+
### List Commands
|
|
226
|
+
|
|
227
|
+
List commands operate on ordered collections of strings.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const program = Effect.gen(function* () {
|
|
231
|
+
const redis = yield* RedisCore;
|
|
232
|
+
|
|
233
|
+
// Push operations
|
|
234
|
+
yield* redis.lPush("tasks", "task1");
|
|
235
|
+
yield* redis.lPush("tasks", "task2", "task3");
|
|
236
|
+
yield* redis.rPush("tasks", "task4");
|
|
237
|
+
|
|
238
|
+
// Pop operations
|
|
239
|
+
const firstTask = yield* redis.lPop("tasks");
|
|
240
|
+
const lastTask = yield* redis.rPop("tasks");
|
|
241
|
+
|
|
242
|
+
// Get range
|
|
243
|
+
const allTasks = yield* redis.lRange("tasks", 0, -1);
|
|
244
|
+
const midTasks = yield* redis.lRange("tasks", 0, 5);
|
|
245
|
+
|
|
246
|
+
// Check length
|
|
247
|
+
const count = yield* redis.lLen("tasks");
|
|
248
|
+
|
|
249
|
+
// Index operations
|
|
250
|
+
const taskByIndex = yield* redis.lIndex("tasks", 2);
|
|
251
|
+
yield* redis.lSet("tasks", 0, "updated_task");
|
|
252
|
+
|
|
253
|
+
// Insert and remove
|
|
254
|
+
yield* redis.lInsert("tasks", "BEFORE", "task2", "new_task");
|
|
255
|
+
yield* redis.lRem("tasks", 1, "old_task");
|
|
256
|
+
yield* redis.lTrim("tasks", 0, 10);
|
|
257
|
+
|
|
258
|
+
// Move between lists
|
|
259
|
+
yield* redis.lMove("tasks", "completed", "LEFT", "RIGHT");
|
|
260
|
+
|
|
261
|
+
// Blocking operations (waits for element)
|
|
262
|
+
const task = yield* redis.blPop(["tasks"], 30); // 30 second timeout
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Supported List Commands:**
|
|
267
|
+
- `lPush`, `lPushX`, `rPush`, `rPushX`
|
|
268
|
+
- `lPop`, `rPop`, `lLen`, `lIndex`, `lSet`
|
|
269
|
+
- `lRange`, `lRem`, `lTrim`, `lInsert`, `lPos`
|
|
270
|
+
- `lMove`, `lMPop`, `rPopLPush`
|
|
271
|
+
- `blPop`, `brPop`, `brPopLPush`, `blMove`, `blmPop`
|
|
272
|
+
|
|
273
|
+
### Set Commands
|
|
274
|
+
|
|
275
|
+
Set commands operate on unordered collections of unique strings.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const program = Effect.gen(function* () {
|
|
279
|
+
const redis = yield* RedisCore;
|
|
280
|
+
|
|
281
|
+
// Add and check members
|
|
282
|
+
yield* redis.sAdd("tags", "javascript", "typescript", "nodejs");
|
|
283
|
+
const isMember = yield* redis.sIsMember("tags", "javascript");
|
|
284
|
+
const allMembers = yield* redis.sMembers("tags");
|
|
285
|
+
const count = yield* redis.sCard("tags");
|
|
286
|
+
|
|
287
|
+
// Multiple membership check
|
|
288
|
+
const members = yield* redis.smIsMember("tags", [
|
|
289
|
+
"javascript",
|
|
290
|
+
"python",
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
// Random members
|
|
294
|
+
const randomMember = yield* redis.sRandMember("tags");
|
|
295
|
+
const randomMembers = yield* redis.sRandMember("tags", 2);
|
|
296
|
+
|
|
297
|
+
// Pop operations
|
|
298
|
+
const popped = yield* redis.sPop("tags");
|
|
299
|
+
const poppedMany = yield* redis.sPop("tags", 2);
|
|
300
|
+
|
|
301
|
+
// Set operations
|
|
302
|
+
const intersection = yield* redis.sInter(["tags", "languages"]);
|
|
303
|
+
const union = yield* redis.sUnion(["tags", "languages"]);
|
|
304
|
+
const difference = yield* redis.sDiff(["tags", "languages"]);
|
|
305
|
+
|
|
306
|
+
// Store operations
|
|
307
|
+
yield* redis.sInterStore("common_tags", ["tags", "languages"]);
|
|
308
|
+
yield* redis.sUnionStore("all_tags", ["tags", "languages"]);
|
|
309
|
+
yield* redis.sDiffStore("unique_tags", ["tags", "languages"]);
|
|
310
|
+
|
|
311
|
+
// Remove
|
|
312
|
+
yield* redis.sRem("tags", "python");
|
|
313
|
+
|
|
314
|
+
// Move
|
|
315
|
+
yield* redis.sMove("tags", "languages", "javascript");
|
|
316
|
+
|
|
317
|
+
// Scan
|
|
318
|
+
const scanResult = yield* redis.sScan("tags", 0);
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Supported Set Commands:**
|
|
323
|
+
- `sAdd`, `sRem`, `sCard`, `sMembers`, `sPop`, `sRandMember`
|
|
324
|
+
- `sIsMember`, `smIsMember`, `sMove`
|
|
325
|
+
- `sInter`, `sInterCard`, `sInterStore`
|
|
326
|
+
- `sUnion`, `sUnionStore`
|
|
327
|
+
- `sDiff`, `sDiffStore`
|
|
328
|
+
- `sScan`
|
|
329
|
+
|
|
330
|
+
### Sorted Set Commands
|
|
331
|
+
|
|
332
|
+
Sorted set commands operate on sets with scores that determine order.
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const program = Effect.gen(function* () {
|
|
336
|
+
const redis = yield* RedisCore;
|
|
337
|
+
|
|
338
|
+
// Add members with scores
|
|
339
|
+
yield* redis.zAdd("leaderboard", [
|
|
340
|
+
{ score: 100, member: "Alice" },
|
|
341
|
+
{ score: 90, member: "Bob" },
|
|
342
|
+
{ score: 85, member: "Charlie" },
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
// Get score
|
|
346
|
+
const score = yield* redis.zScore("leaderboard", "Alice");
|
|
347
|
+
const scores = yield* redis.zmScore("leaderboard", [
|
|
348
|
+
"Alice",
|
|
349
|
+
"Bob",
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
// Rank operations (0-indexed, ascending by default)
|
|
353
|
+
const rank = yield* redis.zRank("leaderboard", "Alice");
|
|
354
|
+
const revRank = yield* redis.zRevRank("leaderboard", "Alice");
|
|
355
|
+
|
|
356
|
+
// Count
|
|
357
|
+
const total = yield* redis.zCard("leaderboard");
|
|
358
|
+
const inRange = yield* redis.zCount("leaderboard", 80, 100);
|
|
359
|
+
|
|
360
|
+
// Range operations
|
|
361
|
+
const topThree = yield* redis.zRange("leaderboard", 0, 2);
|
|
362
|
+
const topThreeScores = yield* redis.zRange("leaderboard", 0, 2, {
|
|
363
|
+
withScores: true,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Range by score
|
|
367
|
+
const highScorers = yield* redis.zRangeByScore("leaderboard", 90, 100);
|
|
368
|
+
|
|
369
|
+
// Range by lex
|
|
370
|
+
const lexRange = yield* redis.zRangeByLex("leaderboard", "-", "+");
|
|
371
|
+
|
|
372
|
+
// Pop operations
|
|
373
|
+
const highest = yield* redis.zPopMax("leaderboard");
|
|
374
|
+
const lowest = yield* redis.zPopMin("leaderboard");
|
|
375
|
+
|
|
376
|
+
// Increment score
|
|
377
|
+
yield* redis.zIncrBy("leaderboard", 10, "Bob");
|
|
378
|
+
|
|
379
|
+
// Remove
|
|
380
|
+
yield* redis.zRem("leaderboard", "Charlie");
|
|
381
|
+
yield* redis.zRemRangeByRank("leaderboard", 0, 1);
|
|
382
|
+
yield* redis.zRemRangeByScore("leaderboard", 0, 50);
|
|
383
|
+
|
|
384
|
+
// Set operations
|
|
385
|
+
const inter = yield* redis.zInter(["leaderboard", "active_players"]);
|
|
386
|
+
yield* redis.zInterStore("top_active", 2, ["leaderboard", "active_players"]);
|
|
387
|
+
|
|
388
|
+
const union = yield* redis.zUnion(["leaderboard", "archive"]);
|
|
389
|
+
yield* redis.zUnionStore("all_time", 2, ["leaderboard", "archive"]);
|
|
390
|
+
|
|
391
|
+
// Scan
|
|
392
|
+
const scanResult = yield* redis.zScan("leaderboard", 0);
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Supported Sorted Set Commands:**
|
|
397
|
+
- `zAdd`, `zRem`, `zCard`, `zCount`, `zScore`, `zmScore`
|
|
398
|
+
- `zRank`, `zRevRank`, `zRange`, `zRevRange`
|
|
399
|
+
- `zRangeByScore`, `zRangeByLex`, `zRangeStore`
|
|
400
|
+
- `zPopMax`, `zPopMin`, `zMPop`
|
|
401
|
+
- `zIncrBy`, `zRandMember`
|
|
402
|
+
- `zRemRangeByRank`, `zRemRangeByScore`, `zRemRangeByLex`
|
|
403
|
+
- `zLexCount`, `zInter`, `zInterCard`, `zInterStore`
|
|
404
|
+
- `zUnion`, `zUnionStore`, `zDiff`, `zDiffStore`
|
|
405
|
+
- `zScan`
|
|
406
|
+
|
|
407
|
+
### Generic Commands
|
|
408
|
+
|
|
409
|
+
Generic commands work with all data types.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
const program = Effect.gen(function* () {
|
|
413
|
+
const redis = yield* RedisCore;
|
|
414
|
+
|
|
415
|
+
// Check existence
|
|
416
|
+
const exists = yield* redis.exists(["key1", "key2", "key3"]);
|
|
417
|
+
|
|
418
|
+
// Get type
|
|
419
|
+
const type = yield* redis.type("mykey");
|
|
420
|
+
|
|
421
|
+
// Delete keys
|
|
422
|
+
yield* redis.del(["key1", "key2"]);
|
|
423
|
+
yield* redis.unlink(["key3", "key4"]); // Async delete
|
|
424
|
+
|
|
425
|
+
// Expiration
|
|
426
|
+
yield* redis.expire("tempkey", 3600); // 1 hour in seconds
|
|
427
|
+
yield* redis.expireAt("tempkey", Math.floor(Date.now() / 1000) + 3600);
|
|
428
|
+
yield* redis.pExpire("tempkey", 3600000); // milliseconds
|
|
429
|
+
yield* redis.pExpireAt("tempkey", Date.now() + 3600000);
|
|
430
|
+
|
|
431
|
+
const ttl = yield* redis.ttl("tempkey"); // seconds
|
|
432
|
+
const pttl = yield* redis.pTTL("tempkey"); // milliseconds
|
|
433
|
+
yield* redis.persist("tempkey"); // Remove expiration
|
|
434
|
+
|
|
435
|
+
// Get expiration time
|
|
436
|
+
const expirationTime = yield* redis.expireTime("tempkey"); // Unix timestamp in seconds
|
|
437
|
+
const pExpirationTime = yield* redis.pExpireTime("tempkey"); // Unix timestamp in milliseconds
|
|
438
|
+
|
|
439
|
+
// Rename
|
|
440
|
+
yield* redis.rename("oldkey", "newkey");
|
|
441
|
+
yield* redis.renameNX("oldkey", "newkey"); // Only if newkey doesn't exist
|
|
442
|
+
|
|
443
|
+
// Copy
|
|
444
|
+
yield* redis.copy("source", "destination");
|
|
445
|
+
|
|
446
|
+
// Keys pattern matching
|
|
447
|
+
const allKeys = yield* redis.keys("*");
|
|
448
|
+
const userKeys = yield* redis.keys("user:*");
|
|
449
|
+
|
|
450
|
+
// Random key
|
|
451
|
+
const randomKey = yield* redis.randomKey();
|
|
452
|
+
|
|
453
|
+
// Scan (cursor-based iteration)
|
|
454
|
+
const scanResult = yield* redis.scan(0, { pattern: "user:*", count: 10 });
|
|
455
|
+
|
|
456
|
+
// Sort
|
|
457
|
+
const sorted = yield* redis.sort("mylist", {
|
|
458
|
+
by: "weight_*",
|
|
459
|
+
limit: { offset: 0, count: 10 },
|
|
460
|
+
get: ["object_*", "#"],
|
|
461
|
+
alpha: true,
|
|
462
|
+
direction: "DESC",
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Touch (update last access time)
|
|
466
|
+
const touched = yield* redis.touch(["key1", "key2"]);
|
|
467
|
+
|
|
468
|
+
// Dump and restore
|
|
469
|
+
const serialized = yield* redis.dump("mykey");
|
|
470
|
+
yield* redis.restore("newkey", 0, serialized);
|
|
471
|
+
|
|
472
|
+
// Migrate (move to another Redis instance)
|
|
473
|
+
yield* redis.migrate("target-host", 6379, "mykey", 0, 5000);
|
|
474
|
+
|
|
475
|
+
// Wait for replication
|
|
476
|
+
yield* redis.wait(1, 1000); // Wait for 1 replica within 1000ms
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Supported Generic Commands:**
|
|
481
|
+
- `del`, `exists`, `expire`, `expireAt`, `expireTime`
|
|
482
|
+
- `keys`, `migrate`, `move`, `persist`
|
|
483
|
+
- `pExpire`, `pExpireAt`, `pExpireTime`, `pTTL`
|
|
484
|
+
- `randomKey`, `rename`, `renameNX`, `restore`
|
|
485
|
+
- `scan`, `sort`, `sortRo`, `touch`, `ttl`, `type`
|
|
486
|
+
- `unlink`, `wait`, `copy`, `dump`
|
|
487
|
+
|
|
488
|
+
### JSON Commands
|
|
489
|
+
|
|
490
|
+
JSON commands store and manipulate JSON documents (requires Redis Stack).
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
const program = Effect.gen(function* () {
|
|
494
|
+
const redis = yield* RedisCore;
|
|
495
|
+
|
|
496
|
+
// Set JSON value
|
|
497
|
+
yield* redis.json.set("user:1", "$", {
|
|
498
|
+
name: "Alice",
|
|
499
|
+
age: 30,
|
|
500
|
+
tags: ["developer", "typescript"],
|
|
501
|
+
metadata: {
|
|
502
|
+
created: "2024-01-01",
|
|
503
|
+
active: true,
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Get JSON value
|
|
508
|
+
const user = yield* redis.json.get("user:1");
|
|
509
|
+
const name = yield* redis.json.get("user:1", { path: "$.name" });
|
|
510
|
+
|
|
511
|
+
// Update specific path
|
|
512
|
+
yield* redis.json.set("user:1", "$.age", 31);
|
|
513
|
+
|
|
514
|
+
// String operations
|
|
515
|
+
yield* redis.json.strAppend("user:1", "$.name", " Smith");
|
|
516
|
+
const nameLength = yield* redis.json.strLen("user:1", "$.name");
|
|
517
|
+
|
|
518
|
+
// Numeric operations
|
|
519
|
+
yield* redis.json.numIncrBy("user:1", "$.age", 1);
|
|
520
|
+
yield* redis.json.numMultBy("user:1", "$.age", 2);
|
|
521
|
+
|
|
522
|
+
// Array operations
|
|
523
|
+
yield* redis.json.arrAppend("user:1", "$.tags", "nodejs");
|
|
524
|
+
yield* redis.json.arrInsert("user:1", "$.tags", 0, "javascript");
|
|
525
|
+
const tagsLength = yield* redis.json.arrLen("user:1", "$.tags");
|
|
526
|
+
const tag = yield* redis.json.arrPop("user:1", "$.tags");
|
|
527
|
+
yield* redis.json.arrTrim("user:1", "$.tags", 0, 2);
|
|
528
|
+
const tagIndex = yield* redis.json.arrIndex(
|
|
529
|
+
"user:1",
|
|
530
|
+
"$.tags",
|
|
531
|
+
"typescript",
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// Object operations
|
|
535
|
+
const objKeys = yield* redis.json.objKeys("user:1", "$.metadata");
|
|
536
|
+
const objLen = yield* redis.json.objLen("user:1", "$.metadata");
|
|
537
|
+
|
|
538
|
+
// Type checking
|
|
539
|
+
const valueType = yield* redis.json.type("user:1", "$");
|
|
540
|
+
|
|
541
|
+
// Boolean toggle
|
|
542
|
+
yield* redis.json.toggle("user:1", "$.metadata.active");
|
|
543
|
+
|
|
544
|
+
// Delete path
|
|
545
|
+
yield* redis.json.del("user:1", "$.metadata.created");
|
|
546
|
+
|
|
547
|
+
// Merge JSON
|
|
548
|
+
yield* redis.json.merge("user:1", "$", { lastLogin: "2024-01-15" });
|
|
549
|
+
|
|
550
|
+
// Multiple keys
|
|
551
|
+
const users = yield* redis.json.mGet(["user:1", "user:2", "user:3"], "$");
|
|
552
|
+
|
|
553
|
+
// Multiple set
|
|
554
|
+
yield* redis.json.mSet([
|
|
555
|
+
{ key: "user:4", path: "$", value: { name: "Bob" } },
|
|
556
|
+
{ key: "user:5", path: "$", value: { name: "Charlie" } },
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
// Clear (sets arrays to [] and objects to {})
|
|
560
|
+
yield* redis.json.clear("user:1");
|
|
561
|
+
|
|
562
|
+
// Debug memory (get memory usage)
|
|
563
|
+
const memoryUsage = yield* redis.json.debugMemory("user:1");
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Supported JSON Commands:**
|
|
568
|
+
- `get`, `set`, `del`, `clear`, `merge`, `forget`
|
|
569
|
+
- `mGet`, `mSet`
|
|
570
|
+
- `type`, `debugMemory`
|
|
571
|
+
- `strAppend`, `strLen`
|
|
572
|
+
- `numIncrBy`, `numMultBy`
|
|
573
|
+
- `arrAppend`, `arrIndex`, `arrInsert`, `arrLen`, `arrPop`, `arrTrim`
|
|
574
|
+
- `objKeys`, `objLen`
|
|
575
|
+
- `toggle`
|
|
576
|
+
|
|
577
|
+
### Geospatial Commands
|
|
578
|
+
|
|
579
|
+
Geospatial commands store and query geographic coordinates.
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
const program = Effect.gen(function* () {
|
|
583
|
+
const redis = yield* RedisCore;
|
|
584
|
+
|
|
585
|
+
// Add locations
|
|
586
|
+
yield* redis.geoAdd("cities", [
|
|
587
|
+
{
|
|
588
|
+
longitude: -122.419,
|
|
589
|
+
latitude: 37.775,
|
|
590
|
+
member: "San Francisco",
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
longitude: -118.243,
|
|
594
|
+
latitude: 34.052,
|
|
595
|
+
member: "Los Angeles",
|
|
596
|
+
},
|
|
597
|
+
{ longitude: -87.629, latitude: 41.878, member: "Chicago" },
|
|
598
|
+
]);
|
|
599
|
+
|
|
600
|
+
// Get positions
|
|
601
|
+
const positions = yield* redis.geoPos("cities", [
|
|
602
|
+
"San Francisco",
|
|
603
|
+
"Los Angeles",
|
|
604
|
+
]);
|
|
605
|
+
|
|
606
|
+
// Get distance
|
|
607
|
+
const distance = yield* redis.geoDist(
|
|
608
|
+
"cities",
|
|
609
|
+
"San Francisco",
|
|
610
|
+
"Los Angeles",
|
|
611
|
+
"km",
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
// Get geohash
|
|
615
|
+
const hashes = yield* redis.geoHash("cities", [
|
|
616
|
+
"San Francisco",
|
|
617
|
+
"Los Angeles",
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
// Find nearby locations
|
|
621
|
+
const nearby = yield* redis.geoRadius(
|
|
622
|
+
"cities",
|
|
623
|
+
-122.419,
|
|
624
|
+
37.775,
|
|
625
|
+
100,
|
|
626
|
+
"km",
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
// Find nearby from member
|
|
630
|
+
const nearbyFromMember = yield* redis.geoRadiusByMember(
|
|
631
|
+
"cities",
|
|
632
|
+
"San Francisco",
|
|
633
|
+
100,
|
|
634
|
+
"km",
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
// Search in box/circle (Redis 6.2+)
|
|
638
|
+
const searchBox = yield* redis.geoSearch("cities", {
|
|
639
|
+
member: "San Francisco",
|
|
640
|
+
byBox: { width: 200, height: 200, unit: "km" },
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
const searchCircle = yield* redis.geoSearch("cities", {
|
|
644
|
+
member: "San Francisco",
|
|
645
|
+
byRadius: { radius: 100, unit: "km" },
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Store search results
|
|
649
|
+
yield* redis.geoSearchStore("nearby", "cities", {
|
|
650
|
+
member: "San Francisco",
|
|
651
|
+
byRadius: { radius: 100, unit: "km" },
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Supported Geospatial Commands:**
|
|
657
|
+
- `geoAdd`, `geoDist`, `geoHash`, `geoPos`
|
|
658
|
+
- `geoRadius`, `geoRadiusByMember`
|
|
659
|
+
- `geoRadiusRo`, `geoRadiusByMemberRo`
|
|
660
|
+
- `geoSearch`, `geoSearchStore`
|
|
661
|
+
|
|
662
|
+
### Bitmap Commands
|
|
663
|
+
|
|
664
|
+
Bitmap commands perform bitwise operations on strings.
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
const program = Effect.gen(function* () {
|
|
668
|
+
const redis = yield* RedisCore;
|
|
669
|
+
|
|
670
|
+
// Set and get bits
|
|
671
|
+
yield* redis.setBit("bitmap", 7, 1);
|
|
672
|
+
const bit = yield* redis.getBit("bitmap", 7);
|
|
673
|
+
|
|
674
|
+
// Count set bits
|
|
675
|
+
const count = yield* redis.bitCount("bitmap");
|
|
676
|
+
const countRange = yield* redis.bitCount("bitmap", { start: 0, end: 10 });
|
|
677
|
+
|
|
678
|
+
// Find first set/clear bit
|
|
679
|
+
const firstSet = yield* redis.bitPos("bitmap", 1);
|
|
680
|
+
const firstClear = yield* redis.bitPos("bitmap", 0);
|
|
681
|
+
const firstSetAfter = yield* redis.bitPos("bitmap", 1, { start: 5 });
|
|
682
|
+
|
|
683
|
+
// Bitwise operations
|
|
684
|
+
yield* redis.bitOp("AND", "dest", ["bitmap1", "bitmap2"]);
|
|
685
|
+
yield* redis.bitOp("OR", "dest", ["bitmap1", "bitmap2"]);
|
|
686
|
+
yield* redis.bitOp("XOR", "dest", ["bitmap1", "bitmap2"]);
|
|
687
|
+
yield* redis.bitOp("NOT", "dest", ["bitmap1"]);
|
|
688
|
+
|
|
689
|
+
// Bitfield operations
|
|
690
|
+
const fieldResult = yield* redis.bitField("mykey", {
|
|
691
|
+
operations: [
|
|
692
|
+
{ type: "u4", offset: 0, operation: "GET" },
|
|
693
|
+
{ type: "u4", offset: 4, operation: "SET", value: 15 },
|
|
694
|
+
],
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Supported Bitmap Commands:**
|
|
700
|
+
- `getBit`, `setBit`, `bitCount`, `bitPos`
|
|
701
|
+
- `bitOp`, `bitField`, `bitFieldRo`
|
|
702
|
+
|
|
703
|
+
### HyperLogLog Commands
|
|
704
|
+
|
|
705
|
+
HyperLogLog commands provide probabilistic cardinality estimation (count unique elements).
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
const program = Effect.gen(function* () {
|
|
709
|
+
const redis = yield* RedisCore;
|
|
710
|
+
|
|
711
|
+
// Add elements
|
|
712
|
+
yield* redis.pfAdd("hll", "a", "b", "c", "d", "e");
|
|
713
|
+
yield* redis.pfAdd("hll", "f", "g");
|
|
714
|
+
|
|
715
|
+
// Get cardinality (approximate count of unique elements)
|
|
716
|
+
const count = yield* redis.pfCount(["hll"]);
|
|
717
|
+
|
|
718
|
+
// Count across multiple HyperLogLogs
|
|
719
|
+
const multiCount = yield* redis.pfCount(["hll1", "hll2", "hll3"]);
|
|
720
|
+
|
|
721
|
+
// Merge HyperLogLogs
|
|
722
|
+
yield* redis.pfMerge("combined", ["hll1", "hll2", "hll3"]);
|
|
723
|
+
|
|
724
|
+
const mergedCount = yield* redis.pfCount(["combined"]);
|
|
725
|
+
});
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**Supported HyperLogLog Commands:**
|
|
729
|
+
- `pfAdd`, `pfCount`, `pfMerge`
|
|
730
|
+
|
|
731
|
+
### Scripting Commands
|
|
732
|
+
|
|
733
|
+
Scripting commands allow server-side Lua script execution.
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
const program = Effect.gen(function* () {
|
|
737
|
+
const redis = yield* RedisCore;
|
|
738
|
+
|
|
739
|
+
// Execute Lua script
|
|
740
|
+
const result = yield* redis.eval(
|
|
741
|
+
"return redis.call('GET',KEYS[1])",
|
|
742
|
+
{
|
|
743
|
+
keys: ["mykey"],
|
|
744
|
+
arguments: [],
|
|
745
|
+
},
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
// Script with arguments
|
|
749
|
+
const sum = yield* redis.eval(
|
|
750
|
+
`
|
|
751
|
+
local val1 = redis.call('GET', KEYS[1])
|
|
752
|
+
local val2 = tonumber(ARGV[1])
|
|
753
|
+
return val1 + val2
|
|
754
|
+
`,
|
|
755
|
+
{
|
|
756
|
+
keys: ["counter"],
|
|
757
|
+
arguments: ["10"],
|
|
758
|
+
},
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
// Load script (returns SHA1)
|
|
762
|
+
const sha = yield* redis.scriptLoad("return 'Hello Redis'");
|
|
763
|
+
|
|
764
|
+
// Execute by SHA
|
|
765
|
+
const cachedResult = yield* redis.evalSha(sha, {
|
|
766
|
+
keys: [],
|
|
767
|
+
arguments: [],
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// Check if scripts exist
|
|
771
|
+
const exists = yield* redis.scriptExists([sha]);
|
|
772
|
+
|
|
773
|
+
// Flush script cache
|
|
774
|
+
yield* redis.scriptFlush();
|
|
775
|
+
|
|
776
|
+
// Function operations (Redis 7.0+)
|
|
777
|
+
yield* redis.functionLoad("#!lua name=mylib\n return 'hello'");
|
|
778
|
+
const functions = yield* redis.functionList();
|
|
779
|
+
yield* redis.functionFlush();
|
|
780
|
+
});
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
**Supported Scripting Commands:**
|
|
784
|
+
- `eval`, `evalSha`, `evalRo`, `evalShaRo`
|
|
785
|
+
- `scriptDebug`, `scriptExists`, `scriptFlush`, `scriptKill`, `scriptLoad`
|
|
786
|
+
- `fCall`, `fCallRo`
|
|
787
|
+
- `functionDelete`, `functionDump`, `functionFlush`, `functionKill`
|
|
788
|
+
- `functionList`, `functionLoad`, `functionRestore`, `functionStats`
|
|
789
|
+
|
|
790
|
+
### Transactions (MULTI/EXEC)
|
|
791
|
+
|
|
792
|
+
Transactions allow you to batch multiple commands and execute them atomically.
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
const program = Effect.gen(function* () {
|
|
796
|
+
const redis = yield* RedisCore;
|
|
797
|
+
|
|
798
|
+
// Start a transaction
|
|
799
|
+
const [result, execResult] = yield* redis.multi((tx) =>
|
|
800
|
+
Effect.gen(function* () {
|
|
801
|
+
yield* tx.set("key1", "value1");
|
|
802
|
+
yield* tx.set("key2", "value2");
|
|
803
|
+
const val = yield* tx.get("key1");
|
|
804
|
+
yield* tx.incr("counter");
|
|
805
|
+
|
|
806
|
+
return val; // This is the "result" returned
|
|
807
|
+
}),
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
// result contains the return value from the transaction block
|
|
811
|
+
yield* Effect.log(`Transaction result: ${result}`);
|
|
812
|
+
|
|
813
|
+
// execResult is an array of responses from each command
|
|
814
|
+
// null if transaction was aborted
|
|
815
|
+
yield* Effect.log(`Exec responses: ${execResult}`);
|
|
816
|
+
|
|
817
|
+
// Transaction with condition
|
|
818
|
+
const [txResult, execOK] = yield* redis.multi((tx) =>
|
|
819
|
+
Effect.gen(function* () {
|
|
820
|
+
const currentValue = yield* tx.get("balance");
|
|
821
|
+
|
|
822
|
+
if (currentValue && Number(currentValue) >= 100) {
|
|
823
|
+
yield* tx.decrBy("balance", 100);
|
|
824
|
+
yield* tx.incrBy("savings", 100);
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return false;
|
|
829
|
+
}),
|
|
830
|
+
);
|
|
831
|
+
});
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Pipelines
|
|
835
|
+
|
|
836
|
+
Pipelines batch multiple commands for more efficient execution (like transactions but without atomicity guarantees).
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
const program = Effect.gen(function* () {
|
|
840
|
+
const redis = yield* RedisCore;
|
|
841
|
+
|
|
842
|
+
// Execute multiple commands in a pipeline
|
|
843
|
+
const [result, execResult] = yield* redis.pipeline((pipe) =>
|
|
844
|
+
Effect.gen(function* () {
|
|
845
|
+
yield* pipe.set("key1", "value1");
|
|
846
|
+
yield* pipe.set("key2", "value2");
|
|
847
|
+
yield* pipe.set("key3", "value3");
|
|
848
|
+
const val = yield* pipe.get("key1");
|
|
849
|
+
yield* pipe.mGet(["key1", "key2", "key3"]);
|
|
850
|
+
|
|
851
|
+
return val;
|
|
852
|
+
}),
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
// result contains the return value from the pipeline block
|
|
856
|
+
yield* Effect.log(`Pipeline result: ${result}`);
|
|
857
|
+
|
|
858
|
+
// execResult is an array of responses from each command
|
|
859
|
+
yield* Effect.log(`Pipeline responses:`, execResult);
|
|
860
|
+
|
|
861
|
+
// Pipeline with many operations
|
|
862
|
+
const [bulkResult, responses] = yield* redis.pipeline((pipe) =>
|
|
863
|
+
Effect.gen(function* () {
|
|
864
|
+
// Simulate bulk data operations
|
|
865
|
+
for (let i = 0; i < 1000; i++) {
|
|
866
|
+
yield* pipe.set(`key:${i}`, `value:${i}`);
|
|
867
|
+
}
|
|
868
|
+
return "bulk operation complete";
|
|
869
|
+
}),
|
|
870
|
+
);
|
|
871
|
+
});
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
## Complete Command Reference
|
|
875
|
+
|
|
876
|
+
### String Commands (24)
|
|
877
|
+
|
|
878
|
+
```
|
|
879
|
+
append, decr, decrBy, delEx, digest, get, getDel, getEx, getRange
|
|
880
|
+
getSet, incr, incrBy, incrByFloat, lcs, mGet, mSet, mSetEx, mSetNX
|
|
881
|
+
pSetEx, set, setEx, setNX, setRange, strLen
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
### Hash Commands (25)
|
|
885
|
+
|
|
886
|
+
```
|
|
887
|
+
hDel, hExists, hExpire, hExpireAt, hExpireTime, hGet, hGetAll, hGetDel
|
|
888
|
+
hGetEx, hIncrBy, hIncrByFloat, hKeys, hLen, hmGet, hPersist, hpExpire
|
|
889
|
+
hpExpireAt, hpExpireTime, hpTTL, hRandField, hScan, hSet, hSetEx
|
|
890
|
+
hSetNX, hStrLen, hTTL, hVals
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### List Commands (21)
|
|
894
|
+
|
|
895
|
+
```
|
|
896
|
+
blMove, blmPop, blPop, brPop, brPopLPush, lIndex, lInsert, lLen, lMove
|
|
897
|
+
lPop, lPos, lPush, lPushX, lRange, lRem, lSet, lTrim, rPop
|
|
898
|
+
rPopLPush, rPush, rPushX
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Set Commands (17)
|
|
902
|
+
|
|
903
|
+
```
|
|
904
|
+
sAdd, sCard, sDiff, sDiffStore, sInter, sInterCard, sInterStore, sIsMember
|
|
905
|
+
sMembers, smIsMember, sMove, sPop, sRandMember, sRem, sScan, sUnion
|
|
906
|
+
sUnionStore
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### Sorted Set Commands (34)
|
|
910
|
+
|
|
911
|
+
```
|
|
912
|
+
bzMPop, bzPopMax, bzPopMin, zAdd, zCard, zCount, zDiff, zDiffStore
|
|
913
|
+
zIncrBy, zInter, zInterCard, zInterStore, zLexCount, zmPop, zmScore
|
|
914
|
+
zPopMax, zPopMin, zRandMember, zRange, zRangeByLex, zRangeByScore
|
|
915
|
+
zRangeStore, zRank, zRem, zRemRangeByLex, zRemRangeByRank
|
|
916
|
+
zRemRangeByScore, zRevRank, zScan, zScore, zUnion, zUnionStore
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Generic Commands (30)
|
|
920
|
+
|
|
921
|
+
```
|
|
922
|
+
copy, del, dump, exists, expire, expireAt, expireTime, keys, migrate
|
|
923
|
+
move, persist, pExpire, pExpireAt, pExpireTime, pTTL, randomKey
|
|
924
|
+
rename, renameNX, restore, scan, sort, sortRo, touch, ttl, type
|
|
925
|
+
unlink, wait
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### JSON Commands (24)
|
|
929
|
+
|
|
930
|
+
```
|
|
931
|
+
arrAppend, arrIndex, arrInsert, arrLen, arrPop, arrTrim, clear, debugMemory
|
|
932
|
+
del, forget, get, merge, mGet, mSet, numIncrBy, numMultBy, objKeys
|
|
933
|
+
objLen, set, strAppend, strLen, toggle, type
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Geospatial Commands (10)
|
|
937
|
+
|
|
938
|
+
```
|
|
939
|
+
geoAdd, geoDist, geoHash, geoPos, geoRadius, geoRadiusByMember
|
|
940
|
+
geoRadiusByMemberRo, geoRadiusRo, geoSearch, geoSearchStore
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### Bitmap Commands (7)
|
|
944
|
+
|
|
945
|
+
```
|
|
946
|
+
bitCount, bitField, bitFieldRo, bitOp, bitPos, getBit, setBit
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### HyperLogLog Commands (3)
|
|
950
|
+
|
|
951
|
+
```
|
|
952
|
+
pfAdd, pfCount, pfMerge
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
### Scripting Commands (19)
|
|
956
|
+
|
|
957
|
+
```
|
|
958
|
+
eval, evalRo, evalSha, evalShaRo, fCall, fCallRo, functionDelete
|
|
959
|
+
functionDump, functionFlush, functionKill, functionList, functionLoad
|
|
960
|
+
functionRestore, functionStats, scriptDebug, scriptExists, scriptFlush
|
|
961
|
+
scriptKill, scriptLoad
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
## Error Handling
|
|
965
|
+
|
|
966
|
+
effect-redis uses Effect's error handling system with a custom `RedisError` type:
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
import { Effect } from "effect";
|
|
970
|
+
import { RedisError } from "@envoy1084/effect-redis";
|
|
971
|
+
|
|
972
|
+
const program = Effect.gen(function* () {
|
|
973
|
+
const redis = yield* RedisCore;
|
|
974
|
+
|
|
975
|
+
// Errors are automatically caught and wrapped in RedisError
|
|
976
|
+
const result = yield* redis.get("mykey").pipe(
|
|
977
|
+
Effect.catchTag("RedisError", (error) => {
|
|
978
|
+
yield* Effect.log(`Redis error: ${error.message}`);
|
|
979
|
+
return Effect.succeed("default_value");
|
|
980
|
+
}),
|
|
981
|
+
);
|
|
982
|
+
});
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
## Advanced Usage
|
|
986
|
+
|
|
987
|
+
### Combining Commands with Effect
|
|
988
|
+
|
|
989
|
+
```typescript
|
|
990
|
+
import { Effect, Array as EffectArray } from "effect";
|
|
991
|
+
|
|
992
|
+
const program = Effect.gen(function* () {
|
|
993
|
+
const redis = yield* RedisCore;
|
|
994
|
+
|
|
995
|
+
// Process multiple keys in parallel
|
|
996
|
+
const keys = ["user:1", "user:2", "user:3"];
|
|
997
|
+
const users = yield* Effect.all(
|
|
998
|
+
keys.map((key) => redis.get(key)),
|
|
999
|
+
{ concurrency: 3 },
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
// Conditional operations
|
|
1003
|
+
const exists = yield* redis.exists(["mykey"]);
|
|
1004
|
+
if (exists > 0) {
|
|
1005
|
+
const value = yield* redis.get("mykey");
|
|
1006
|
+
yield* Effect.log(value);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Error recovery
|
|
1010
|
+
const value = yield* redis
|
|
1011
|
+
.get("mayNotExist")
|
|
1012
|
+
.pipe(
|
|
1013
|
+
Effect.orElse(() => Effect.succeed("default")),
|
|
1014
|
+
);
|
|
1015
|
+
});
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
### Custom Context for Dependency Injection
|
|
1019
|
+
|
|
1020
|
+
```typescript
|
|
1021
|
+
import { Context, Effect, Layer } from "effect";
|
|
1022
|
+
|
|
1023
|
+
class MyService extends Context.Tag("MyService")<
|
|
1024
|
+
MyService,
|
|
1025
|
+
{ doSomething: () => Effect.Effect<string> }
|
|
1026
|
+
>() {}
|
|
1027
|
+
|
|
1028
|
+
const myServiceLive = Layer.succeed(MyService, {
|
|
1029
|
+
doSomething: () => Effect.succeed("done"),
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
const program = Effect.gen(function* () {
|
|
1033
|
+
const redis = yield* RedisCore;
|
|
1034
|
+
const service = yield* MyService;
|
|
1035
|
+
|
|
1036
|
+
const result = yield* service.doSomething();
|
|
1037
|
+
yield* redis.set("result", result);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// Compose layers
|
|
1041
|
+
const AppLayer = Layer.mergeAll(RedisLayer, myServiceLive);
|
|
1042
|
+
program.pipe(Effect.provide(AppLayer), runMain);
|
|
1043
|
+
```
|
|
1044
|
+
## Future Work
|
|
1045
|
+
|
|
1046
|
+
Following command groups are supported:
|
|
1047
|
+
|
|
1048
|
+
- [x] String commands
|
|
1049
|
+
- [x] Hash commands
|
|
1050
|
+
- [x] List commands
|
|
1051
|
+
- [x] Set commands
|
|
1052
|
+
- [x] Sorted set commands
|
|
1053
|
+
- [ ] Stream commands
|
|
1054
|
+
- [x] Bitmap commands
|
|
1055
|
+
- [x] HyperLogLog commands
|
|
1056
|
+
- [x] Geospatial commands
|
|
1057
|
+
- [x] JSON commands
|
|
1058
|
+
- [ ] Search commands
|
|
1059
|
+
- [ ] Time series commands
|
|
1060
|
+
- [ ] Vector set commands
|
|
1061
|
+
- [ ] Pub/Sub commands
|
|
1062
|
+
- [x] Transaction commands (Pipeline and Multi)
|
|
1063
|
+
- [x] Scripting commands
|
|
1064
|
+
- [ ] Connection commands
|
|
1065
|
+
- [ ] Server commands
|
|
1066
|
+
- [ ] Cluster commands
|
|
1067
|
+
- [x] Generic commands
|
|
1068
|
+
|
|
1069
|
+
We are working on adding support for other Redis command groups. If you have any suggestions or requests, please feel free to open an issue or submit a pull request.
|
|
1070
|
+
|
|
1071
|
+
## Contributing
|
|
1072
|
+
|
|
1073
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1074
|
+
|
|
1075
|
+
## License
|
|
1076
|
+
|
|
1077
|
+
MIT
|