effect-redis 0.0.5 → 0.0.6
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 +180 -180
- package/dist/index.js +7 -7
- package/package.json +2 -2
- package/src/redis.ts +21 -27
package/README.md
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
# Effect-Redis
|
|
2
|
-
|
|
3
|
-
A lightweight wrapper around the official `redis` client that integrates seamlessly with the [Effect](https://github.com/Effect-TS/effect) ecosystem.
|
|
4
|
-
|
|
5
|
-
* Resource–safe connections (acquire / release handled for you)
|
|
6
|
-
* _Layers_ for dependency-injection
|
|
7
|
-
* Tagged error type (`RedisError`) with rich cause information
|
|
8
|
-
* Declarative, interrupt-safe `publish` / `subscribe` / `setValue` helpers
|
|
9
|
-
* Tiny surface — bring your own redis commands via the `Redis` service when you need full power
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
pnpm add effect-redis
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
> The library itself is runtime-agnostic. Under Bun you will usually also have `@effect/platform-bun` around — that is **not** required by `effect-redis`.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Quick-start
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { Effect, Layer } from "effect";
|
|
27
|
-
import {
|
|
28
|
-
RedisConnectionOptionsLive,
|
|
29
|
-
RedisPubSubLive,
|
|
30
|
-
RedisPersistenceLive,
|
|
31
|
-
RedisPubSub,
|
|
32
|
-
RedisPersistence,
|
|
33
|
-
} from "effect-redis";
|
|
34
|
-
|
|
35
|
-
const redisLayer = RedisConnectionOptionsLive({
|
|
36
|
-
url: "redis://localhost:6379",
|
|
37
|
-
}).pipe(Layer.provideMerge(RedisPubSubLive), Layer.provideMerge(RedisPersistenceLive));
|
|
38
|
-
|
|
39
|
-
const program = Effect.gen(function* () {
|
|
40
|
-
/* services */
|
|
41
|
-
const pubsub = yield* RedisPubSub;
|
|
42
|
-
const storage = yield* RedisPersistence;
|
|
43
|
-
|
|
44
|
-
/* persistence */
|
|
45
|
-
yield* storage.setValue("user:42", JSON.stringify({ name: "Ada" }));
|
|
46
|
-
|
|
47
|
-
/* pub / sub */
|
|
48
|
-
yield* pubsub.subscribe("notifications", (msg) => {
|
|
49
|
-
console.log("🔔", msg);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
yield* pubsub.publish("notifications", "Hello world!");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
Effect.runPromise(program.pipe(Effect.provide(redisLayer)));
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Provided Layers & Services
|
|
61
|
-
|
|
62
|
-
| Layer | Service | What you get |
|
|
63
|
-
| ---------------------------------------- | ---------------------- | --------------------------------------------- |
|
|
64
|
-
| `RedisConnectionOptionsLive(options?)` | — | Supplies connection settings downstream |
|
|
65
|
-
| `RedisLive` | `Redis` | Raw access to an already connected client |
|
|
66
|
-
| `RedisPubSubLive` | `RedisPubSub` | `publish / subscribe` helpers |
|
|
67
|
-
| `RedisPersistenceLive` | `RedisPersistence` | Simple `setValue` helper |
|
|
68
|
-
|
|
69
|
-
All Layers are *scoped* — the underlying connection is opened once and **closed automatically** when the scope ends.
|
|
70
|
-
|
|
71
|
-
### Error model
|
|
72
|
-
|
|
73
|
-
Every operation can fail with **`RedisError`** — a tagged error enriched with the original cause:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
import { Effect } from "effect";
|
|
77
|
-
import { RedisPersistence, RedisError } from "effect-redis";
|
|
78
|
-
|
|
79
|
-
const safe = RedisPersistence.pipe(
|
|
80
|
-
Effect.flatMap(({ setValue }) => setValue("x", "y")),
|
|
81
|
-
Effect.catchTag("RedisError", (e: RedisError) => Effect.logError(e.message))
|
|
82
|
-
);
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Usage patterns
|
|
88
|
-
|
|
89
|
-
### 1. Pub / Sub micro-service
|
|
90
|
-
|
|
91
|
-
Extracted from the repo’s own `winfut.ts` example:
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
import { BunRuntime } from "@effect/platform-bun";
|
|
95
|
-
import { Effect, Queue, Stream, pipe } from "effect";
|
|
96
|
-
import { RedisConnectionOptionsLive, RedisPubSubLive, RedisPubSub } from "effect-redis";
|
|
97
|
-
|
|
98
|
-
const program = Effect.gen(function* () {
|
|
99
|
-
const q = yield* Queue.unbounded<string>();
|
|
100
|
-
const rps = yield* RedisPubSub;
|
|
101
|
-
|
|
102
|
-
/* subscribe */
|
|
103
|
-
yield* rps.subscribe("raw", (msg) => Queue.unsafeOffer(q, msg));
|
|
104
|
-
|
|
105
|
-
/* process stream */
|
|
106
|
-
yield* pipe(
|
|
107
|
-
Stream.fromQueue(q),
|
|
108
|
-
Stream.filter((m) => m.startsWith("T:WIN")),
|
|
109
|
-
Stream.tap((m) => rps.publish("winfut", m)),
|
|
110
|
-
Stream.runDrain
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
BunRuntime.runMain(
|
|
115
|
-
Effect.provide(
|
|
116
|
-
program,
|
|
117
|
-
Layer.provide(RedisPubSubLive, RedisConnectionOptionsLive({ url: "redis://localhost:6379" }))
|
|
118
|
-
)
|
|
119
|
-
);
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### 2. Metrics aggregation
|
|
123
|
-
|
|
124
|
-
See `metrics.ts` for the full blown version, the gist is:
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
const metricsProg = Effect.gen(function* () {
|
|
128
|
-
const rps = yield* RedisPubSub;
|
|
129
|
-
yield* rps.subscribe("winfut", handleTick);
|
|
130
|
-
// … aggregate & publish …
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 3. Low-level commands via `Redis`
|
|
135
|
-
|
|
136
|
-
The thin `Redis` service gives you the connected **node-redis client** when you need functions not wrapped by this lib:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
import { Redis } from "effect-redis";
|
|
140
|
-
|
|
141
|
-
const incrProg = Redis.pipe(
|
|
142
|
-
Effect.flatMap(({ use }) =>
|
|
143
|
-
use((client) => client.incr("counter"))
|
|
144
|
-
)
|
|
145
|
-
);
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Reference
|
|
151
|
-
|
|
152
|
-
### `RedisConnectionOptionsLive(options?) → Layer`
|
|
153
|
-
Creates a live Layer that exposes `RedisConnectionOptions` downstream. The `options` object is forwarded to `createClient(options)` from `redis`.
|
|
154
|
-
|
|
155
|
-
### `RedisPubSub` service
|
|
156
|
-
```ts
|
|
157
|
-
publish(channel: string, message: string): Effect<void, RedisError>
|
|
158
|
-
subscribe(channel: string, handler: (msg: string) => void): Effect<void, RedisError>
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### `RedisPersistence` service
|
|
162
|
-
```ts
|
|
163
|
-
setValue(key: string, value: string): Effect<void, RedisError>
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### `Redis` service
|
|
167
|
-
```ts
|
|
168
|
-
use<T>(fn: (client: RedisClient) => T | Promise<T>): Effect<T, RedisError>
|
|
169
|
-
```
|
|
170
|
-
Gives access to the raw `RedisClient` from `redis`. The connection is shared and stays open for the whole scope.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Contributing / TODO
|
|
175
|
-
|
|
176
|
-
* Expose more common redis commands (get, del, etc.)
|
|
177
|
-
* Connection pooling?
|
|
178
|
-
* Add tests against `redis-mock`
|
|
179
|
-
|
|
180
|
-
PRs welcome!
|
|
1
|
+
# Effect-Redis
|
|
2
|
+
|
|
3
|
+
A lightweight wrapper around the official `redis` client that integrates seamlessly with the [Effect](https://github.com/Effect-TS/effect) ecosystem.
|
|
4
|
+
|
|
5
|
+
* Resource–safe connections (acquire / release handled for you)
|
|
6
|
+
* _Layers_ for dependency-injection
|
|
7
|
+
* Tagged error type (`RedisError`) with rich cause information
|
|
8
|
+
* Declarative, interrupt-safe `publish` / `subscribe` / `setValue` helpers
|
|
9
|
+
* Tiny surface — bring your own redis commands via the `Redis` service when you need full power
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add effect-redis
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> The library itself is runtime-agnostic. Under Bun you will usually also have `@effect/platform-bun` around — that is **not** required by `effect-redis`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick-start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Effect, Layer } from "effect";
|
|
27
|
+
import {
|
|
28
|
+
RedisConnectionOptionsLive,
|
|
29
|
+
RedisPubSubLive,
|
|
30
|
+
RedisPersistenceLive,
|
|
31
|
+
RedisPubSub,
|
|
32
|
+
RedisPersistence,
|
|
33
|
+
} from "effect-redis";
|
|
34
|
+
|
|
35
|
+
const redisLayer = RedisConnectionOptionsLive({
|
|
36
|
+
url: "redis://localhost:6379",
|
|
37
|
+
}).pipe(Layer.provideMerge(RedisPubSubLive), Layer.provideMerge(RedisPersistenceLive));
|
|
38
|
+
|
|
39
|
+
const program = Effect.gen(function* () {
|
|
40
|
+
/* services */
|
|
41
|
+
const pubsub = yield* RedisPubSub;
|
|
42
|
+
const storage = yield* RedisPersistence;
|
|
43
|
+
|
|
44
|
+
/* persistence */
|
|
45
|
+
yield* storage.setValue("user:42", JSON.stringify({ name: "Ada" }));
|
|
46
|
+
|
|
47
|
+
/* pub / sub */
|
|
48
|
+
yield* pubsub.subscribe("notifications", (msg) => {
|
|
49
|
+
console.log("🔔", msg);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
yield* pubsub.publish("notifications", "Hello world!");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Effect.runPromise(program.pipe(Effect.provide(redisLayer)));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Provided Layers & Services
|
|
61
|
+
|
|
62
|
+
| Layer | Service | What you get |
|
|
63
|
+
| ---------------------------------------- | ---------------------- | --------------------------------------------- |
|
|
64
|
+
| `RedisConnectionOptionsLive(options?)` | — | Supplies connection settings downstream |
|
|
65
|
+
| `RedisLive` | `Redis` | Raw access to an already connected client |
|
|
66
|
+
| `RedisPubSubLive` | `RedisPubSub` | `publish / subscribe` helpers |
|
|
67
|
+
| `RedisPersistenceLive` | `RedisPersistence` | Simple `setValue` helper |
|
|
68
|
+
|
|
69
|
+
All Layers are *scoped* — the underlying connection is opened once and **closed automatically** when the scope ends.
|
|
70
|
+
|
|
71
|
+
### Error model
|
|
72
|
+
|
|
73
|
+
Every operation can fail with **`RedisError`** — a tagged error enriched with the original cause:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { Effect } from "effect";
|
|
77
|
+
import { RedisPersistence, RedisError } from "effect-redis";
|
|
78
|
+
|
|
79
|
+
const safe = RedisPersistence.pipe(
|
|
80
|
+
Effect.flatMap(({ setValue }) => setValue("x", "y")),
|
|
81
|
+
Effect.catchTag("RedisError", (e: RedisError) => Effect.logError(e.message))
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Usage patterns
|
|
88
|
+
|
|
89
|
+
### 1. Pub / Sub micro-service
|
|
90
|
+
|
|
91
|
+
Extracted from the repo’s own `winfut.ts` example:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { BunRuntime } from "@effect/platform-bun";
|
|
95
|
+
import { Effect, Queue, Stream, pipe } from "effect";
|
|
96
|
+
import { RedisConnectionOptionsLive, RedisPubSubLive, RedisPubSub } from "effect-redis";
|
|
97
|
+
|
|
98
|
+
const program = Effect.gen(function* () {
|
|
99
|
+
const q = yield* Queue.unbounded<string>();
|
|
100
|
+
const rps = yield* RedisPubSub;
|
|
101
|
+
|
|
102
|
+
/* subscribe */
|
|
103
|
+
yield* rps.subscribe("raw", (msg) => Queue.unsafeOffer(q, msg));
|
|
104
|
+
|
|
105
|
+
/* process stream */
|
|
106
|
+
yield* pipe(
|
|
107
|
+
Stream.fromQueue(q),
|
|
108
|
+
Stream.filter((m) => m.startsWith("T:WIN")),
|
|
109
|
+
Stream.tap((m) => rps.publish("winfut", m)),
|
|
110
|
+
Stream.runDrain
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
BunRuntime.runMain(
|
|
115
|
+
Effect.provide(
|
|
116
|
+
program,
|
|
117
|
+
Layer.provide(RedisPubSubLive, RedisConnectionOptionsLive({ url: "redis://localhost:6379" }))
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2. Metrics aggregation
|
|
123
|
+
|
|
124
|
+
See `metrics.ts` for the full blown version, the gist is:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const metricsProg = Effect.gen(function* () {
|
|
128
|
+
const rps = yield* RedisPubSub;
|
|
129
|
+
yield* rps.subscribe("winfut", handleTick);
|
|
130
|
+
// … aggregate & publish …
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 3. Low-level commands via `Redis`
|
|
135
|
+
|
|
136
|
+
The thin `Redis` service gives you the connected **node-redis client** when you need functions not wrapped by this lib:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { Redis } from "effect-redis";
|
|
140
|
+
|
|
141
|
+
const incrProg = Redis.pipe(
|
|
142
|
+
Effect.flatMap(({ use }) =>
|
|
143
|
+
use((client) => client.incr("counter"))
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Reference
|
|
151
|
+
|
|
152
|
+
### `RedisConnectionOptionsLive(options?) → Layer`
|
|
153
|
+
Creates a live Layer that exposes `RedisConnectionOptions` downstream. The `options` object is forwarded to `createClient(options)` from `redis`.
|
|
154
|
+
|
|
155
|
+
### `RedisPubSub` service
|
|
156
|
+
```ts
|
|
157
|
+
publish(channel: string, message: string): Effect<void, RedisError>
|
|
158
|
+
subscribe(channel: string, handler: (msg: string) => void): Effect<void, RedisError>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `RedisPersistence` service
|
|
162
|
+
```ts
|
|
163
|
+
setValue(key: string, value: string): Effect<void, RedisError>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `Redis` service
|
|
167
|
+
```ts
|
|
168
|
+
use<T>(fn: (client: RedisClient) => T | Promise<T>): Effect<T, RedisError>
|
|
169
|
+
```
|
|
170
|
+
Gives access to the raw `RedisClient` from `redis`. The connection is shared and stays open for the whole scope.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Contributing / TODO
|
|
175
|
+
|
|
176
|
+
* Expose more common redis commands (get, del, etc.)
|
|
177
|
+
* Connection pooling?
|
|
178
|
+
* Add tests against `redis-mock`
|
|
179
|
+
|
|
180
|
+
PRs welcome!
|