effect-redis 0.0.6 → 0.0.7

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.
@@ -0,0 +1 @@
1
+ export * from './redis';
@@ -0,0 +1,57 @@
1
+ import { Context, Effect, Layer } from 'effect';
2
+ import { type RedisArgument, createClient } from 'redis';
3
+ declare const RedisError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "RedisError";
5
+ } & Readonly<A>;
6
+ export declare class RedisError extends RedisError_base<{
7
+ cause: unknown;
8
+ message: string;
9
+ }> {
10
+ }
11
+ interface RedisConnectionOptionsShape {
12
+ options?: Parameters<typeof createClient>[0];
13
+ }
14
+ declare const RedisConnectionOptions_base: Context.TagClass<RedisConnectionOptions, "RedisConnectionOptions", RedisConnectionOptionsShape>;
15
+ declare class RedisConnectionOptions extends RedisConnectionOptions_base {
16
+ }
17
+ declare const RedisConnectionOptionsLive: (options?: Parameters<typeof createClient>[0]) => Layer.Layer<RedisConnectionOptions, never, never>;
18
+ interface RedisShape {
19
+ use: <T>(fn: (client: ReturnType<typeof createClient>) => T) => Effect.Effect<Awaited<T>, RedisError, never>;
20
+ }
21
+ declare const Redis_base: Context.TagClass<Redis, "Redis", RedisShape>;
22
+ declare class Redis extends Redis_base {
23
+ }
24
+ interface RedisPubSubShape {
25
+ publish: (channel: string, message: string) => Effect.Effect<void, RedisError, never>;
26
+ subscribe: (channel: string, handler: (message: string) => void) => Effect.Effect<void, RedisError, never>;
27
+ }
28
+ declare const RedisPubSub_base: Context.TagClass<RedisPubSub, "RedisPubSub", RedisPubSubShape>;
29
+ declare class RedisPubSub extends RedisPubSub_base {
30
+ }
31
+ interface RedisPersistenceShape {
32
+ setValue: (key: string, value: string) => Effect.Effect<void, RedisError, never>;
33
+ }
34
+ declare const RedisPersistence_base: Context.TagClass<RedisPersistence, "RedisPersistence", RedisPersistenceShape>;
35
+ declare class RedisPersistence extends RedisPersistence_base {
36
+ }
37
+ export interface StreamEntry {
38
+ id: RedisArgument;
39
+ message: Record<string, string>;
40
+ }
41
+ interface RedisStreamShape {
42
+ xadd: (key: RedisArgument, id: RedisArgument | '*', message: Record<string, RedisArgument>) => Effect.Effect<string, RedisError, never>;
43
+ xread: (key: RedisArgument, id: RedisArgument, // Use '$' to read only new entries from now
44
+ block?: number, // Block in milliseconds, 0 for indefinite
45
+ count?: number) => Effect.Effect<StreamEntry[], RedisError, never>;
46
+ xrange: (key: RedisArgument, start: RedisArgument, // '-' for earliest available
47
+ end: RedisArgument, // '+' for latest available
48
+ count?: number) => Effect.Effect<StreamEntry[], RedisError, never>;
49
+ }
50
+ declare const RedisStream_base: Context.TagClass<RedisStream, "RedisStream", RedisStreamShape>;
51
+ declare class RedisStream extends RedisStream_base {
52
+ }
53
+ declare const RedisLive: Layer.Layer<Redis, RedisError, RedisConnectionOptions>;
54
+ declare const RedisPersistenceLive: Layer.Layer<RedisPersistence, RedisError, RedisConnectionOptions>;
55
+ declare const RedisPubSubLive: Layer.Layer<RedisPubSub, RedisError, RedisConnectionOptions>;
56
+ declare const RedisStreamLive: Layer.Layer<RedisStream, RedisError, RedisConnectionOptions>;
57
+ export { RedisPersistence, RedisPubSub, RedisConnectionOptions, Redis, RedisStream, RedisPersistenceLive, RedisPubSubLive, RedisConnectionOptionsLive, RedisLive, RedisStreamLive, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-redis",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Simple Effect wrapper for Redis.",
5
5
  "module": "dist/index.js",
6
6
  "main": "dist/index.js",
@@ -21,9 +21,11 @@
21
21
  "@types/bun": "latest"
22
22
  },
23
23
  "peerDependencies": {
24
- "typescript": "^5",
25
24
  "effect": "^3.16.2",
26
- "redis": "^5.1.0"
25
+ "redis": "^5.1.0",
26
+ "typescript": "^5"
27
27
  },
28
- "dependencies": {}
28
+ "dependencies": {
29
+ "@effect/platform-bun": "^0.69.2"
30
+ }
29
31
  }
package/src/redis.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Context, Data, Effect, Layer } from 'effect';
2
- import { createClient } from 'redis';
2
+ import { type RedisArgument, createClient } from 'redis';
3
3
 
4
4
  export class RedisError extends Data.TaggedError('RedisError')<{
5
5
  cause: unknown;
@@ -31,36 +31,6 @@ interface RedisShape {
31
31
  }
32
32
  class Redis extends Context.Tag('Redis')<Redis, RedisShape>() {}
33
33
 
34
- const bootstrapRedisEffect = Effect.gen(function* () {
35
- const client = yield* redisClientEffect;
36
- return Redis.of({
37
- use: (fn) =>
38
- Effect.gen(function* () {
39
- const result = yield* Effect.try({
40
- try: () => fn(client),
41
- catch: (e) =>
42
- new RedisError({
43
- cause: e,
44
- message: 'Synchronous error in `Redis.use`',
45
- }),
46
- });
47
- if (result instanceof Promise) {
48
- return yield* Effect.tryPromise({
49
- try: () => result,
50
- catch: (e) =>
51
- new RedisError({
52
- cause: e,
53
- message: 'Asynchronous error in `Redis.use`',
54
- }),
55
- });
56
- }
57
- return result;
58
- }),
59
- });
60
- });
61
-
62
- const RedisLive = Layer.scoped(Redis, bootstrapRedisEffect);
63
-
64
34
  interface RedisPubSubShape {
65
35
  publish: (
66
36
  channel: string,
@@ -89,6 +59,43 @@ class RedisPersistence extends Context.Tag('RedisPersistence')<
89
59
  RedisPersistenceShape
90
60
  >() {}
91
61
 
62
+ // Stream related types
63
+ export interface StreamEntry {
64
+ id: RedisArgument;
65
+ message: Record<string, string>;
66
+ }
67
+
68
+ interface RedisStreamShape {
69
+ // Add an entry to a stream
70
+ xadd: (
71
+ key: RedisArgument,
72
+ id: RedisArgument | '*',
73
+ message: Record<string, RedisArgument>,
74
+ ) => Effect.Effect<string, RedisError, never>;
75
+
76
+ // Read from a stream with ability to block and wait for new entries
77
+ xread: (
78
+ key: RedisArgument,
79
+ id: RedisArgument, // Use '$' to read only new entries from now
80
+ block?: number, // Block in milliseconds, 0 for indefinite
81
+ count?: number, // Max number of entries to return
82
+ ) => Effect.Effect<StreamEntry[], RedisError, never>;
83
+
84
+ // Read a range of entries from a stream
85
+ xrange: (
86
+ key: RedisArgument,
87
+ start: RedisArgument, // '-' for earliest available
88
+ end: RedisArgument, // '+' for latest available
89
+ count?: number,
90
+ ) => Effect.Effect<StreamEntry[], RedisError, never>;
91
+ }
92
+
93
+ class RedisStream extends Context.Tag('RedisStream')<
94
+ RedisStream,
95
+ RedisStreamShape
96
+ >() {}
97
+
98
+ // Common code for redis client creation
92
99
  const redisClientEffect = Effect.gen(function* () {
93
100
  const { options } = yield* RedisConnectionOptions;
94
101
 
@@ -123,7 +130,37 @@ const redisClientEffect = Effect.gen(function* () {
123
130
  );
124
131
  });
125
132
 
126
- const bootstrapRedisPersistenceEffect = Effect.gen(function* () {
133
+ const bootstrapRedisServiceEffect = Effect.gen(function* () {
134
+ const client = yield* redisClientEffect;
135
+ return Redis.of({
136
+ use: (fn) =>
137
+ Effect.gen(function* () {
138
+ const result = yield* Effect.try({
139
+ try: () => fn(client),
140
+ catch: (e) =>
141
+ new RedisError({
142
+ cause: e,
143
+ message: 'Synchronous error in `Redis.use`',
144
+ }),
145
+ });
146
+ if (result instanceof Promise) {
147
+ return yield* Effect.tryPromise({
148
+ try: () => result,
149
+ catch: (e) =>
150
+ new RedisError({
151
+ cause: e,
152
+ message: 'Asynchronous error in `Redis.use`',
153
+ }),
154
+ });
155
+ }
156
+ return result;
157
+ }),
158
+ });
159
+ });
160
+
161
+ const RedisLive = Layer.scoped(Redis, bootstrapRedisServiceEffect);
162
+
163
+ const bootstrapRedisPersistenceServiceEffect = Effect.gen(function* () {
127
164
  const client = yield* redisClientEffect;
128
165
 
129
166
  return RedisPersistence.of({
@@ -141,10 +178,10 @@ const bootstrapRedisPersistenceEffect = Effect.gen(function* () {
141
178
 
142
179
  const RedisPersistenceLive = Layer.scoped(
143
180
  RedisPersistence,
144
- bootstrapRedisPersistenceEffect,
181
+ bootstrapRedisPersistenceServiceEffect,
145
182
  );
146
183
 
147
- const bootstrapRedisPubSubEffect = Effect.gen(function* () {
184
+ const bootstrapRedisPubSubServiceEffect = Effect.gen(function* () {
148
185
  const clientPublish = yield* redisClientEffect;
149
186
  const clientSubscribe = yield* redisClientEffect;
150
187
 
@@ -170,15 +207,145 @@ const bootstrapRedisPubSubEffect = Effect.gen(function* () {
170
207
  });
171
208
  });
172
209
 
173
- const RedisPubSubLive = Layer.scoped(RedisPubSub, bootstrapRedisPubSubEffect);
210
+ const RedisPubSubLive = Layer.scoped(
211
+ RedisPubSub,
212
+ bootstrapRedisPubSubServiceEffect,
213
+ );
214
+
215
+ // Redis Stream implementation
216
+ const bootstrapRedisStreamServiceEffect = Effect.gen(function* () {
217
+ // Separate connections: one dedicated to writes (producer) and one to reads (consumer)
218
+ const clientProducer = yield* redisClientEffect;
219
+ const clientConsumer = yield* redisClientEffect;
220
+
221
+ return RedisStream.of({
222
+ xadd: (
223
+ key: RedisArgument,
224
+ id: RedisArgument,
225
+ message: Record<string, RedisArgument>,
226
+ ) =>
227
+ Effect.tryPromise({
228
+ try: async () => {
229
+ // Pass the message object directly to xAdd
230
+ return await clientProducer.xAdd(key, id, message);
231
+ },
232
+ catch: (e) =>
233
+ new RedisError({
234
+ cause: e,
235
+ message: 'Error in `RedisStream.xadd`',
236
+ }),
237
+ }),
238
+
239
+ xread: (
240
+ key: RedisArgument,
241
+ id: RedisArgument,
242
+ block?: number,
243
+ count?: number,
244
+ ) =>
245
+ Effect.tryPromise({
246
+ try: async () => {
247
+ const options: Record<string, number> = {};
248
+
249
+ if (block !== undefined) {
250
+ options.BLOCK = block;
251
+ }
252
+
253
+ if (count !== undefined) {
254
+ options.COUNT = count;
255
+ }
256
+
257
+ // Create proper XReadStream objects instead of arrays
258
+ const streams = [{ key, id }];
259
+ const result = await clientConsumer.xRead(streams, options);
260
+
261
+ if (!result) {
262
+ return [];
263
+ }
264
+
265
+ // Transform result into StreamEntry[] format
266
+ if (!Array.isArray(result)) {
267
+ return [];
268
+ }
269
+
270
+ return result.flatMap((stream) => {
271
+ // Type guard to check if stream has the expected structure
272
+ if (
273
+ stream &&
274
+ typeof stream === 'object' &&
275
+ 'messages' in stream &&
276
+ Array.isArray(stream.messages)
277
+ ) {
278
+ return stream.messages.map((msg) => {
279
+ // Add type guard for msg
280
+ if (msg && typeof msg === 'object' && 'id' in msg) {
281
+ return {
282
+ id: String(msg.id), // Convert to string explicitly
283
+ message: msg.message as Record<string, string>,
284
+ };
285
+ }
286
+ // Return a default or handle error case
287
+ return {
288
+ id: '',
289
+ message: {} as Record<string, string>,
290
+ };
291
+ });
292
+ }
293
+
294
+ return [];
295
+ });
296
+ },
297
+ catch: (e) =>
298
+ new RedisError({
299
+ cause: e,
300
+ message: 'Error in `RedisStream.xread`',
301
+ }),
302
+ }),
303
+
304
+ xrange: (
305
+ key: RedisArgument,
306
+ start: RedisArgument,
307
+ end: RedisArgument,
308
+ count?: number,
309
+ ) =>
310
+ Effect.tryPromise({
311
+ try: async () => {
312
+ const options: Record<string, number> = {};
313
+
314
+ if (count !== undefined) {
315
+ options.COUNT = count;
316
+ }
317
+
318
+ const result = await clientConsumer.xRange(key, start, end, options);
319
+
320
+ // Transform result into StreamEntry[] format
321
+ return result.map((msg) => ({
322
+ id: msg.id,
323
+ message: msg.message as Record<string, string>,
324
+ }));
325
+ },
326
+ catch: (e) =>
327
+ new RedisError({
328
+ cause: e,
329
+ message: 'Error in `RedisStream.xrange`',
330
+ }),
331
+ }),
332
+ });
333
+ });
334
+
335
+ const RedisStreamLive = Layer.scoped(
336
+ RedisStream,
337
+ bootstrapRedisStreamServiceEffect,
338
+ );
174
339
 
175
340
  export {
176
341
  RedisPersistence,
177
342
  RedisPubSub,
178
343
  RedisConnectionOptions,
179
344
  Redis,
345
+ RedisStream,
180
346
  RedisPersistenceLive,
181
347
  RedisPubSubLive,
182
348
  RedisConnectionOptionsLive,
183
349
  RedisLive,
350
+ RedisStreamLive,
184
351
  };