@plinthjs/redis
v0.1.0
Published
Mason Redis client over node:net speaking RESP (the Laravel Redis integration equivalent).
Downloads
306
Maintainers
Readme
@plinthjs/redis
A dependency-free Redis client for Mason that speaks RESP over node:net, the Laravel Redis
integration (Illuminate\Redis) equivalent. A RedisManager resolves named connections from
config, and each RedisConnection offers typed, prefix-aware helpers for strings, keys, hashes,
lists, sets, sorted sets, streams, scripting, transactions and pub/sub, plus a raw command()
escape hatch.
Install
npm install @plinthjs/redisUsage
import { RedisManager } from '@plinthjs/redis'
const redis = new RedisManager({
default: 'default',
connections: {
default: { host: '127.0.0.1', port: 6379, db: 0, prefix: 'app:' },
cache: { host: '127.0.0.1', port: 6379, db: 1, username: 'app', password: 'secret' },
subscriber: { host: '127.0.0.1', port: 6379 },
},
})
const connection = redis.connection() // cached per name
await connection.connect() // commands reject until the socket is open
await connection.set('greeting', 'hello', { ex: 60 }) // 'OK'; stored as app:greeting
await connection.get('greeting') // 'hello'
await connection.incr('visits') // 1
await connection.ttl('greeting') // 60
await redis.connection('cache').connect()
redis.disconnectAll() // on shutdownconnection(name) throws for an unknown name. Register a custom builder with
redis.extend('name', () => new RedisConnection({ ... })).
Data types
await connection.hset('user:1', 'name', 'Ada')
await connection.hgetall('user:1') // { name: 'Ada' }
await connection.rpush('jobs', 'a', 'b')
await connection.lpop('jobs') // 'a'
await connection.sadd('tags', 'ts', 'node')
await connection.sismember('tags', 'ts') // true
await connection.zadd('scores', [
{ score: 10, member: 'ada' },
{ score: 7, member: 'linus' },
])
await connection.zrevrangeWithScores('scores', 0, 9) // [{ member: 'ada', score: 10 }, ...]
await connection.xadd('events', '*', { type: 'signup', user: 1 }) // the new entry id
await connection.mset({ a: 1, b: 2 })
await connection.mget('a', 'b') // ['1', '2']Walk the key space without blocking the server:
let cursor = '0'
do {
const page = await connection.scan(cursor, { match: 'session:*', count: 100 })
cursor = page.cursor
for (const key of page.keys) await connection.unlink(key)
} while (cursor !== '0')Pipelines, transactions and scripts
// Raw commands are sent as-is: the key prefix is not applied.
await connection.command('OBJECT', 'ENCODING', 'app:greeting')
await connection.pipeline([
['INCR', 'app:a'],
['INCR', 'app:b'],
]) // [1, 1]
await connection.multi([
['SET', 'app:x', '1'],
['INCR', 'app:x'],
]) // the EXEC reply: ['OK', 2]
// eval() applies the prefix to KEYS, so this reads app:greeting.
await connection.eval("return redis.call('GET', KEYS[1])", ['greeting'])Pub/sub
A connection that subscribes enters subscriber mode, so use a dedicated one:
const subscriber = redis.connection('subscriber')
await subscriber.connect()
await subscriber.subscribe('orders', (message, channel) => handle(channel, message))
await subscriber.psubscribe('orders.*', (message, channel, pattern) => handle(channel, message))
await connection.publish('orders', JSON.stringify({ id: 1 })) // receiver count
await subscriber.unsubscribe() // all channelsLow-level pieces
encodeCommand(args) builds a RESP frame, RespParser incrementally decodes replies,
RedisError represents -ERR replies (commands reject with it), and toText / asInteger /
asStringArray / asStringOrNull / asRecord coerce raw replies. Pass socketFactory in a
connection config (or RedisManagerOptions) to supply your own socket, for example in tests.
