effect-redis 0.0.9 → 0.0.10
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/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-redis",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Simple Effect wrapper for Redis.",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"type": "module",
|
|
9
|
-
"files": ["dist", "
|
|
9
|
+
"files": ["dist", "README.md"],
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build:main": "bun build --minify-syntax --minify-whitespace ./src/index.ts --outdir ./dist --target node --format esm",
|
|
12
12
|
"build:types": "bun tsc --emitDeclarationOnly --outDir dist",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './redis';
|
package/src/redis.ts
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
import { Context, Data, Effect, Layer, type Scope } from 'effect';
|
|
2
|
-
import { type RedisArgument, createClient } from 'redis';
|
|
3
|
-
|
|
4
|
-
export class RedisError extends Data.TaggedError('RedisError')<{
|
|
5
|
-
cause: unknown;
|
|
6
|
-
message: string;
|
|
7
|
-
}> {}
|
|
8
|
-
|
|
9
|
-
interface RedisConnectionOptionsShape {
|
|
10
|
-
options?: Parameters<typeof createClient>[0];
|
|
11
|
-
}
|
|
12
|
-
class RedisConnectionOptions extends Context.Tag('RedisConnectionOptions')<
|
|
13
|
-
RedisConnectionOptions,
|
|
14
|
-
RedisConnectionOptionsShape
|
|
15
|
-
>() {}
|
|
16
|
-
|
|
17
|
-
const RedisConnectionOptionsLive = (
|
|
18
|
-
options?: Parameters<typeof createClient>[0],
|
|
19
|
-
) =>
|
|
20
|
-
Layer.succeed(
|
|
21
|
-
RedisConnectionOptions,
|
|
22
|
-
RedisConnectionOptions.of({
|
|
23
|
-
options,
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
interface RedisShape {
|
|
28
|
-
use: <T>(
|
|
29
|
-
fn: (client: ReturnType<typeof createClient>) => T,
|
|
30
|
-
) => Effect.Effect<Awaited<T>, RedisError, never>;
|
|
31
|
-
}
|
|
32
|
-
class Redis extends Context.Tag('Redis')<Redis, RedisShape>() {}
|
|
33
|
-
|
|
34
|
-
interface RedisPubSubShape {
|
|
35
|
-
publish: (
|
|
36
|
-
channel: string,
|
|
37
|
-
message: string,
|
|
38
|
-
) => Effect.Effect<void, RedisError, never>;
|
|
39
|
-
subscribe: (
|
|
40
|
-
channel: string,
|
|
41
|
-
handler: (message: string) => void,
|
|
42
|
-
) => Effect.Effect<void, RedisError, never>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
class RedisPubSub extends Context.Tag('RedisPubSub')<
|
|
46
|
-
RedisPubSub,
|
|
47
|
-
RedisPubSubShape
|
|
48
|
-
>() {}
|
|
49
|
-
|
|
50
|
-
interface RedisPersistenceShape {
|
|
51
|
-
setValue: (
|
|
52
|
-
key: string,
|
|
53
|
-
value: string,
|
|
54
|
-
) => Effect.Effect<void, RedisError, never>;
|
|
55
|
-
sAdd: (
|
|
56
|
-
key: string,
|
|
57
|
-
members: string[],
|
|
58
|
-
) => Effect.Effect<void, RedisError, never>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
class RedisPersistence extends Context.Tag('RedisPersistence')<
|
|
62
|
-
RedisPersistence,
|
|
63
|
-
RedisPersistenceShape
|
|
64
|
-
>() {}
|
|
65
|
-
|
|
66
|
-
// Stream related types
|
|
67
|
-
export interface StreamEntry {
|
|
68
|
-
id: RedisArgument;
|
|
69
|
-
message: Record<string, string>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface RedisStreamShape {
|
|
73
|
-
// Add an entry to a stream
|
|
74
|
-
xadd: (
|
|
75
|
-
key: RedisArgument,
|
|
76
|
-
id: RedisArgument | '*',
|
|
77
|
-
message: Record<string, RedisArgument>,
|
|
78
|
-
) => Effect.Effect<string, RedisError, never>;
|
|
79
|
-
|
|
80
|
-
// Read from a stream with ability to block and wait for new entries
|
|
81
|
-
xread: (
|
|
82
|
-
key: RedisArgument,
|
|
83
|
-
id: RedisArgument, // Use '$' to read only new entries from now
|
|
84
|
-
block?: number, // Block in milliseconds, 0 for indefinite
|
|
85
|
-
count?: number, // Max number of entries to return
|
|
86
|
-
) => Effect.Effect<StreamEntry[], RedisError, never>;
|
|
87
|
-
|
|
88
|
-
// Read a range of entries from a stream
|
|
89
|
-
xrange: (
|
|
90
|
-
key: RedisArgument,
|
|
91
|
-
start: RedisArgument, // '-' for earliest available
|
|
92
|
-
end: RedisArgument, // '+' for latest available
|
|
93
|
-
count?: number,
|
|
94
|
-
) => Effect.Effect<StreamEntry[], RedisError, never>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
class RedisStream extends Context.Tag('RedisStream')<
|
|
98
|
-
RedisStream,
|
|
99
|
-
RedisStreamShape
|
|
100
|
-
>() {}
|
|
101
|
-
|
|
102
|
-
// Common code for redis client creation
|
|
103
|
-
const redisClientEffect: Effect.Effect<
|
|
104
|
-
ReturnType<typeof createClient>,
|
|
105
|
-
RedisError,
|
|
106
|
-
Scope.Scope | RedisConnectionOptions
|
|
107
|
-
> = Effect.gen(function* () {
|
|
108
|
-
const { options } = yield* RedisConnectionOptions;
|
|
109
|
-
|
|
110
|
-
return yield* Effect.acquireRelease(
|
|
111
|
-
Effect.tryPromise({
|
|
112
|
-
try: () =>
|
|
113
|
-
createClient(options)
|
|
114
|
-
.connect()
|
|
115
|
-
.then((r) => {
|
|
116
|
-
console.log('Connected to Redis');
|
|
117
|
-
r.on('error', (e) => {
|
|
118
|
-
console.log('Redis error(on error):', e.message);
|
|
119
|
-
r.destroy();
|
|
120
|
-
});
|
|
121
|
-
r.on('end', () => {
|
|
122
|
-
console.log('Connection to Redis ended');
|
|
123
|
-
});
|
|
124
|
-
return r;
|
|
125
|
-
}),
|
|
126
|
-
catch: (e) =>
|
|
127
|
-
new RedisError({
|
|
128
|
-
cause: e,
|
|
129
|
-
message: 'Error while connecting to Redis',
|
|
130
|
-
}),
|
|
131
|
-
}),
|
|
132
|
-
(client) =>
|
|
133
|
-
Effect.sync(() => {
|
|
134
|
-
if (client.isReady) {
|
|
135
|
-
client.quit();
|
|
136
|
-
}
|
|
137
|
-
}),
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const bootstrapRedisServiceEffect = Effect.gen(function* () {
|
|
142
|
-
const client: ReturnType<typeof createClient> = yield* redisClientEffect;
|
|
143
|
-
return Redis.of({
|
|
144
|
-
use: (fn) =>
|
|
145
|
-
Effect.gen(function* () {
|
|
146
|
-
const result = yield* Effect.try({
|
|
147
|
-
try: () => fn(client),
|
|
148
|
-
catch: (e) =>
|
|
149
|
-
new RedisError({
|
|
150
|
-
cause: e,
|
|
151
|
-
message: 'Synchronous error in `Redis.use`',
|
|
152
|
-
}),
|
|
153
|
-
});
|
|
154
|
-
if (result instanceof Promise) {
|
|
155
|
-
return yield* Effect.tryPromise({
|
|
156
|
-
try: () => result,
|
|
157
|
-
catch: (e) =>
|
|
158
|
-
new RedisError({
|
|
159
|
-
cause: e,
|
|
160
|
-
message: 'Asynchronous error in `Redis.use`',
|
|
161
|
-
}),
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
return result;
|
|
165
|
-
}),
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const RedisLive = Layer.scoped(Redis, bootstrapRedisServiceEffect);
|
|
170
|
-
|
|
171
|
-
const bootstrapRedisPersistenceServiceEffect = Effect.gen(function* () {
|
|
172
|
-
const client: ReturnType<typeof createClient> = yield* redisClientEffect;
|
|
173
|
-
|
|
174
|
-
return RedisPersistence.of({
|
|
175
|
-
setValue: (key, value) =>
|
|
176
|
-
Effect.tryPromise({
|
|
177
|
-
try: () => client.set(key, value),
|
|
178
|
-
catch: (e) =>
|
|
179
|
-
new RedisError({
|
|
180
|
-
cause: e,
|
|
181
|
-
message: 'Error in `Redis.setValue`',
|
|
182
|
-
}),
|
|
183
|
-
}),
|
|
184
|
-
sAdd: (key, members) =>
|
|
185
|
-
Effect.tryPromise({
|
|
186
|
-
try: () => client.sAdd(key, members),
|
|
187
|
-
catch: (e) =>
|
|
188
|
-
new RedisError({
|
|
189
|
-
cause: e,
|
|
190
|
-
message: 'Error in `Redis.sAdd`',
|
|
191
|
-
}),
|
|
192
|
-
}),
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const RedisPersistenceLive = Layer.scoped(
|
|
197
|
-
RedisPersistence,
|
|
198
|
-
bootstrapRedisPersistenceServiceEffect,
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const bootstrapRedisPubSubServiceEffect = Effect.gen(function* () {
|
|
202
|
-
const clientPublish: ReturnType<typeof createClient> =
|
|
203
|
-
yield* redisClientEffect;
|
|
204
|
-
const clientSubscribe: ReturnType<typeof createClient> =
|
|
205
|
-
yield* redisClientEffect;
|
|
206
|
-
|
|
207
|
-
return RedisPubSub.of({
|
|
208
|
-
publish: (channel, message) =>
|
|
209
|
-
Effect.tryPromise({
|
|
210
|
-
try: () => clientPublish.publish(channel, message),
|
|
211
|
-
catch: (e) =>
|
|
212
|
-
new RedisError({
|
|
213
|
-
cause: e,
|
|
214
|
-
message: 'Error in `Redis.publish`',
|
|
215
|
-
}),
|
|
216
|
-
}),
|
|
217
|
-
subscribe: (channel, handler) =>
|
|
218
|
-
Effect.tryPromise({
|
|
219
|
-
try: () => clientSubscribe.subscribe(channel, handler),
|
|
220
|
-
catch: (e) =>
|
|
221
|
-
new RedisError({
|
|
222
|
-
cause: e,
|
|
223
|
-
message: 'Error in `Redis.subscribe`',
|
|
224
|
-
}),
|
|
225
|
-
}),
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const RedisPubSubLive = Layer.scoped(
|
|
230
|
-
RedisPubSub,
|
|
231
|
-
bootstrapRedisPubSubServiceEffect,
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
// Redis Stream implementation
|
|
235
|
-
const bootstrapRedisStreamServiceEffect = Effect.gen(function* () {
|
|
236
|
-
// Separate connections: one dedicated to writes (producer) and one to reads (consumer)
|
|
237
|
-
const clientProducer = yield* redisClientEffect;
|
|
238
|
-
const clientConsumer = yield* redisClientEffect;
|
|
239
|
-
|
|
240
|
-
return RedisStream.of({
|
|
241
|
-
xadd: (
|
|
242
|
-
key: RedisArgument,
|
|
243
|
-
id: RedisArgument,
|
|
244
|
-
message: Record<string, RedisArgument>,
|
|
245
|
-
) =>
|
|
246
|
-
Effect.tryPromise({
|
|
247
|
-
try: async () => {
|
|
248
|
-
// Pass the message object directly to xAdd
|
|
249
|
-
return await clientProducer.xAdd(key, id, message);
|
|
250
|
-
},
|
|
251
|
-
catch: (e) =>
|
|
252
|
-
new RedisError({
|
|
253
|
-
cause: e,
|
|
254
|
-
message: 'Error in `RedisStream.xadd`',
|
|
255
|
-
}),
|
|
256
|
-
}),
|
|
257
|
-
|
|
258
|
-
xread: (
|
|
259
|
-
key: RedisArgument,
|
|
260
|
-
id: RedisArgument,
|
|
261
|
-
block?: number,
|
|
262
|
-
count?: number,
|
|
263
|
-
) =>
|
|
264
|
-
Effect.tryPromise({
|
|
265
|
-
try: async () => {
|
|
266
|
-
const options: Record<string, number> = {};
|
|
267
|
-
|
|
268
|
-
if (block !== undefined) {
|
|
269
|
-
options.BLOCK = block;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (count !== undefined) {
|
|
273
|
-
options.COUNT = count;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Create proper XReadStream objects instead of arrays
|
|
277
|
-
const streams = [{ key, id }];
|
|
278
|
-
const result = await clientConsumer.xRead(streams, options);
|
|
279
|
-
|
|
280
|
-
if (!result) {
|
|
281
|
-
return [];
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Transform result into StreamEntry[] format
|
|
285
|
-
if (!Array.isArray(result)) {
|
|
286
|
-
return [];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return result.flatMap((stream) => {
|
|
290
|
-
// Type guard to check if stream has the expected structure
|
|
291
|
-
if (
|
|
292
|
-
stream &&
|
|
293
|
-
typeof stream === 'object' &&
|
|
294
|
-
'messages' in stream &&
|
|
295
|
-
Array.isArray(stream.messages)
|
|
296
|
-
) {
|
|
297
|
-
return stream.messages.map((msg) => {
|
|
298
|
-
// Add type guard for msg
|
|
299
|
-
if (msg && typeof msg === 'object' && 'id' in msg) {
|
|
300
|
-
return {
|
|
301
|
-
id: String(msg.id), // Convert to string explicitly
|
|
302
|
-
message: msg.message as Record<string, string>,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
// Return a default or handle error case
|
|
306
|
-
return {
|
|
307
|
-
id: '',
|
|
308
|
-
message: {} as Record<string, string>,
|
|
309
|
-
};
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return [];
|
|
314
|
-
});
|
|
315
|
-
},
|
|
316
|
-
catch: (e) =>
|
|
317
|
-
new RedisError({
|
|
318
|
-
cause: e,
|
|
319
|
-
message: 'Error in `RedisStream.xread`',
|
|
320
|
-
}),
|
|
321
|
-
}),
|
|
322
|
-
|
|
323
|
-
xrange: (
|
|
324
|
-
key: RedisArgument,
|
|
325
|
-
start: RedisArgument,
|
|
326
|
-
end: RedisArgument,
|
|
327
|
-
count?: number,
|
|
328
|
-
) =>
|
|
329
|
-
Effect.tryPromise({
|
|
330
|
-
try: async () => {
|
|
331
|
-
const options: Record<string, number> = {};
|
|
332
|
-
|
|
333
|
-
if (count !== undefined) {
|
|
334
|
-
options.COUNT = count;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const result = await clientConsumer.xRange(key, start, end, options);
|
|
338
|
-
|
|
339
|
-
// Transform result into StreamEntry[] format
|
|
340
|
-
return result.map((msg) => ({
|
|
341
|
-
id: msg.id,
|
|
342
|
-
message: msg.message as Record<string, string>,
|
|
343
|
-
}));
|
|
344
|
-
},
|
|
345
|
-
catch: (e) =>
|
|
346
|
-
new RedisError({
|
|
347
|
-
cause: e,
|
|
348
|
-
message: 'Error in `RedisStream.xrange`',
|
|
349
|
-
}),
|
|
350
|
-
}),
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const RedisStreamLive = Layer.scoped(
|
|
355
|
-
RedisStream,
|
|
356
|
-
bootstrapRedisStreamServiceEffect,
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
export {
|
|
360
|
-
RedisPersistence,
|
|
361
|
-
RedisPubSub,
|
|
362
|
-
RedisConnectionOptions,
|
|
363
|
-
Redis,
|
|
364
|
-
RedisStream,
|
|
365
|
-
RedisPersistenceLive,
|
|
366
|
-
RedisPubSubLive,
|
|
367
|
-
RedisConnectionOptionsLive,
|
|
368
|
-
RedisLive,
|
|
369
|
-
RedisStreamLive,
|
|
370
|
-
};
|
|
File without changes
|
|
File without changes
|