@tekir/redis 0.1.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/src/redis.ts ADDED
@@ -0,0 +1,349 @@
1
+ import type { RedisConnectionConfig } from './types'
2
+
3
+ /**
4
+ * Redis client wrapper around Bun's native RedisClient.
5
+ *
6
+ * Provides a high-level API for string, hash, set, and pub/sub operations,
7
+ * plus JSON helpers and a cache-aside `remember` method.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const redis = new Redis({ url: 'redis://localhost:6379', prefix: 'app:' })
12
+ * await redis.set('greeting', 'hello')
13
+ * const val = await redis.get('greeting') // 'hello'
14
+ * ```
15
+ */
16
+ export class Redis {
17
+ private client: any
18
+ private prefix: string
19
+ private config: RedisConnectionConfig
20
+
21
+ /**
22
+ * @param config - Redis connection configuration.
23
+ */
24
+ constructor(config: RedisConnectionConfig = {}) {
25
+ this.config = config
26
+ this.prefix = config.prefix || ''
27
+
28
+ const { RedisClient } = Bun as any
29
+ const url = config.url || process.env.REDIS_URL || 'redis://localhost:6379'
30
+
31
+ this.client = new RedisClient(url, {
32
+ connectionTimeout: config.connectionTimeout,
33
+ idleTimeout: config.idleTimeout,
34
+ autoReconnect: config.autoReconnect ?? true,
35
+ maxRetries: config.maxRetries ?? 10,
36
+ enableAutoPipelining: config.enableAutoPipelining ?? true,
37
+ tls: config.tls,
38
+ })
39
+ }
40
+
41
+ private key(k: string): string {
42
+ return this.prefix ? `${this.prefix}:${k}` : k
43
+ }
44
+
45
+ // ─── Connection ────────────────────────────────────────
46
+
47
+ /**
48
+ * Open the connection to the Redis server.
49
+ *
50
+ * @returns A promise that resolves once the connection is established.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * await redis.connect()
55
+ * ```
56
+ */
57
+ async connect(): Promise<void> { await this.client.connect() }
58
+
59
+ /**
60
+ * Close the connection to the Redis server.
61
+ */
62
+ close(): void { this.client.close() }
63
+
64
+ /**
65
+ * Whether the client is currently connected to Redis.
66
+ *
67
+ * @returns `true` if the connection is active.
68
+ */
69
+ get connected(): boolean { return this.client.connected }
70
+
71
+ // ─── String operations ────────────────────────────────
72
+
73
+ /**
74
+ * Get the value of a key.
75
+ *
76
+ * @param key - The key to retrieve.
77
+ * @returns The string value, or `null` if the key does not exist.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const val = await redis.get('name') // 'Alice' | null
82
+ * ```
83
+ */
84
+ async get(key: string): Promise<string | null> { return this.client.get(this.key(key)) }
85
+
86
+ /**
87
+ * Set the value of a key.
88
+ *
89
+ * @param key - The key to set.
90
+ * @param value - The string or numeric value to store.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * await redis.set('counter', 42)
95
+ * ```
96
+ */
97
+ async set(key: string, value: string | number): Promise<void> { await this.client.set(this.key(key), String(value)) }
98
+
99
+ /**
100
+ * Delete one or more keys.
101
+ *
102
+ * @param keys - The keys to delete.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * await redis.del('key1', 'key2')
107
+ * ```
108
+ */
109
+ async del(...keys: string[]): Promise<void> { await this.client.del(...keys.map(k => this.key(k))) }
110
+
111
+ /**
112
+ * Check whether a key exists.
113
+ *
114
+ * @param key - The key to check.
115
+ * @returns `true` if the key exists.
116
+ */
117
+ async exists(key: string): Promise<boolean> { return this.client.exists(this.key(key)) }
118
+
119
+ /**
120
+ * Increment the integer value of a key by one.
121
+ *
122
+ * @param key - The key to increment.
123
+ * @returns The new value after incrementing.
124
+ */
125
+ async incr(key: string): Promise<number> { return this.client.incr(this.key(key)) }
126
+
127
+ /**
128
+ * Decrement the integer value of a key by one.
129
+ *
130
+ * @param key - The key to decrement.
131
+ * @returns The new value after decrementing.
132
+ */
133
+ async decr(key: string): Promise<number> { return this.client.decr(this.key(key)) }
134
+
135
+ /**
136
+ * Set a timeout on a key (in seconds).
137
+ *
138
+ * @param key - The key to set the expiry on.
139
+ * @param seconds - Time-to-live in seconds.
140
+ */
141
+ async expire(key: string, seconds: number): Promise<void> { await this.client.expire(this.key(key), seconds) }
142
+
143
+ /**
144
+ * Get the remaining time-to-live of a key in seconds.
145
+ *
146
+ * @param key - The key to query.
147
+ * @returns Remaining TTL in seconds, `-1` if no expiry is set, `-2` if the key does not exist.
148
+ */
149
+ async ttl(key: string): Promise<number> { return this.client.ttl(this.key(key)) }
150
+
151
+ // ─── Hash operations ──────────────────────────────────
152
+
153
+ /**
154
+ * Get the value of a single field in a hash.
155
+ *
156
+ * @param key - The hash key.
157
+ * @param field - The field name within the hash.
158
+ * @returns The field value, or `null` if the field or key does not exist.
159
+ */
160
+ async hget(key: string, field: string): Promise<string | null> { return this.client.hget(this.key(key), field) }
161
+
162
+ /**
163
+ * Set multiple field-value pairs in a hash.
164
+ *
165
+ * @param key - The hash key.
166
+ * @param fields - An array of alternating field names and values (e.g. `['f1', 'v1', 'f2', 'v2']`).
167
+ */
168
+ async hmset(key: string, fields: string[]): Promise<void> { await this.client.hmset(this.key(key), fields) }
169
+
170
+ /**
171
+ * Get the values of multiple fields in a hash.
172
+ *
173
+ * @param key - The hash key.
174
+ * @param fields - An array of field names to retrieve.
175
+ * @returns An array of values corresponding to the requested fields (`null` for missing fields).
176
+ */
177
+ async hmget(key: string, fields: string[]): Promise<(string | null)[]> { return this.client.hmget(this.key(key), fields) }
178
+
179
+ /**
180
+ * Increment a numeric field in a hash by a given amount.
181
+ *
182
+ * @param key - The hash key.
183
+ * @param field - The field name to increment.
184
+ * @param increment - The integer amount to add.
185
+ * @returns The new value of the field after incrementing.
186
+ */
187
+ async hincrby(key: string, field: string, increment: number): Promise<number> { return this.client.hincrby(this.key(key), field, increment) }
188
+
189
+ // ─── Set operations ───────────────────────────────────
190
+
191
+ /**
192
+ * Add one or more members to a set.
193
+ *
194
+ * @param key - The set key.
195
+ * @param members - The members to add.
196
+ * @returns The number of members that were added (excluding already-present members).
197
+ */
198
+ async sadd(key: string, ...members: string[]): Promise<number> { return this.client.sadd(this.key(key), ...members) }
199
+
200
+ /**
201
+ * Remove one or more members from a set.
202
+ *
203
+ * @param key - The set key.
204
+ * @param members - The members to remove.
205
+ * @returns The number of members that were removed.
206
+ */
207
+ async srem(key: string, ...members: string[]): Promise<number> { return this.client.srem(this.key(key), ...members) }
208
+
209
+ /**
210
+ * Check whether a value is a member of a set.
211
+ *
212
+ * @param key - The set key.
213
+ * @param member - The value to check for.
214
+ * @returns `true` if the member exists in the set.
215
+ */
216
+ async sismember(key: string, member: string): Promise<boolean> { return this.client.sismember(this.key(key), member) }
217
+
218
+ /**
219
+ * Get all members of a set.
220
+ *
221
+ * @param key - The set key.
222
+ * @returns An array of all members in the set.
223
+ */
224
+ async smembers(key: string): Promise<string[]> { return this.client.smembers(this.key(key)) }
225
+
226
+ // ─── Pub/Sub ──────────────────────────────────────────
227
+
228
+ /**
229
+ * Publish a message to a channel.
230
+ *
231
+ * @param channel - The channel name.
232
+ * @param message - The message string to publish.
233
+ */
234
+ async publish(channel: string, message: string): Promise<void> { await this.client.publish(channel, message) }
235
+
236
+ /**
237
+ * Subscribe to a channel and receive messages via a callback.
238
+ *
239
+ * @param channel - The channel name to subscribe to.
240
+ * @param callback - Invoked for each message received on the channel.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * await redis.subscribe('events', (msg, ch) => {
245
+ * console.log(`Received on ${ch}: ${msg}`)
246
+ * })
247
+ * ```
248
+ */
249
+ async subscribe(channel: string, callback: (message: string, channel: string) => void): Promise<void> {
250
+ await this.client.subscribe(channel, callback)
251
+ }
252
+
253
+ /**
254
+ * Unsubscribe from a channel, or from all channels if none is specified.
255
+ *
256
+ * @param channel - The channel to unsubscribe from. Omit to unsubscribe from all.
257
+ */
258
+ async unsubscribe(channel?: string): Promise<void> { await this.client.unsubscribe(channel) }
259
+
260
+ // ─── Raw command ──────────────────────────────────────
261
+
262
+ /**
263
+ * Send a raw Redis command.
264
+ *
265
+ * @param command - The Redis command string (e.g. `'PING'`, `'INFO'`).
266
+ * @param args - Arguments for the command.
267
+ * @returns The raw response from Redis.
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * const pong = await redis.send('PING') // 'PONG'
272
+ * ```
273
+ */
274
+ async send(command: string, args: string[] = []): Promise<any> { return this.client.send(command, args) }
275
+
276
+ // ─── JSON helpers ─────────────────────────────────────
277
+
278
+ /**
279
+ * Get a value from Redis and parse it as JSON.
280
+ *
281
+ * @param key - The key to retrieve.
282
+ * @returns The parsed object, or `null` if the key does not exist or parsing fails.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * const user = await redis.getJSON<{ name: string }>('user:1')
287
+ * ```
288
+ */
289
+ async getJSON<T = any>(key: string): Promise<T | null> {
290
+ const val = await this.get(key)
291
+ if (val === null) return null
292
+ try { return JSON.parse(val) } catch { return null }
293
+ }
294
+
295
+ /**
296
+ * Serialize a value as JSON and store it in Redis, with an optional TTL.
297
+ *
298
+ * @param key - The key to store the value under.
299
+ * @param value - The value to JSON-serialize and store.
300
+ * @param expireSeconds - Optional time-to-live in seconds.
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * await redis.setJSON('user:1', { name: 'Alice' }, 3600)
305
+ * ```
306
+ */
307
+ async setJSON(key: string, value: any, expireSeconds?: number): Promise<void> {
308
+ await this.set(key, JSON.stringify(value))
309
+ if (expireSeconds) await this.expire(key, expireSeconds)
310
+ }
311
+
312
+ /**
313
+ * Cache-aside helper: return the cached value if it exists, otherwise execute
314
+ * the callback, store the result in Redis with a TTL, and return it.
315
+ *
316
+ * @param key - The cache key.
317
+ * @param seconds - Time-to-live in seconds for the cached value.
318
+ * @param callback - Async function invoked to compute the value on a cache miss.
319
+ * @returns The cached or freshly computed value.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * const users = await redis.remember('all-users', 60, async () => {
324
+ * return db.query('SELECT * FROM users')
325
+ * })
326
+ * ```
327
+ */
328
+ async remember<T>(key: string, seconds: number, callback: () => Promise<T>): Promise<T> {
329
+ const cached = await this.getJSON<T>(key)
330
+ if (cached !== null) return cached
331
+ const value = await callback()
332
+ await this.setJSON(key, value, seconds)
333
+ return value
334
+ }
335
+
336
+ /**
337
+ * Delete all keys in the currently selected database.
338
+ *
339
+ * @returns A promise that resolves once the database has been flushed.
340
+ */
341
+ async flushdb(): Promise<void> { await this.send('FLUSHDB', []) }
342
+
343
+ /**
344
+ * Get the underlying Bun `RedisClient` instance for advanced operations.
345
+ *
346
+ * @returns The raw Bun RedisClient.
347
+ */
348
+ getClient(): any { return this.client }
349
+ }
package/src/types.ts ADDED
@@ -0,0 +1,32 @@
1
+ /** Configuration for a single Redis connection. */
2
+ export interface RedisConnectionConfig {
3
+ /** Redis connection URL (default: `'redis://localhost:6379'`). */
4
+ url?: string
5
+ /** Key prefix applied to all operations (e.g. `'myapp:'`). */
6
+ prefix?: string
7
+ /** Connection timeout in milliseconds. */
8
+ connectionTimeout?: number
9
+ /** Idle timeout in milliseconds before the connection is closed. */
10
+ idleTimeout?: number
11
+ /** Whether to automatically reconnect on connection loss (default: `true`). */
12
+ autoReconnect?: boolean
13
+ /** Maximum number of reconnection attempts (default: `10`). */
14
+ maxRetries?: number
15
+ /** Enable automatic pipelining for improved throughput (default: `true`). */
16
+ enableAutoPipelining?: boolean
17
+ /** Enable TLS, or provide a TLS options object. */
18
+ tls?: boolean | object
19
+ }
20
+
21
+ /**
22
+ * Redis configuration supporting both single-connection and multi-connection setups.
23
+ *
24
+ * For a single connection, specify the connection fields directly.
25
+ * For multiple connections, use the `connections` map and optionally set `default`.
26
+ */
27
+ export type RedisConfig = RedisConnectionConfig & {
28
+ /** Name of the default connection (default: `'default'`). */
29
+ default?: string
30
+ /** Named connection configurations. */
31
+ connections?: Record<string, RedisConnectionConfig>
32
+ }