@kurai-io/redis
Version:
Schema-based abstraction layer for redis database interactions
139 lines (138 loc) • 4.11 kB
JavaScript
import { createHash as r } from "node:crypto";
import * as a from "crypto";
import { pack as o, unpack as l } from "jsonpack";
class c {
/** Original redis client */
client;
options;
randomKeyBytesCount = 16;
/**
* @param client redis client instance
* @param options schema options
* @param config
*/
constructor(t, i, e) {
const s = i?.namespace ?? r("shake256", { outputLength: e?.randomNamespaceLength || 8 }).update(JSON.stringify([i])).digest("base64url");
this.client = t, this.options = {
ttl: i?.ttl,
readOnce: i?.readOnce ?? !1,
namespace: s
}, e?.randomKeyBytesCount && (this.randomKeyBytesCount = e.randomKeyBytesCount);
}
/**
* Compresses and writes the specified value to Redis.
*
* @param key key under which the value will be stored (without namespace).
* @param value value to store.
* @returns key used in Redis (without namespace).
*/
async set(t, i) {
await this.waitWhenReady();
const e = o(i);
return typeof this.options.ttl == "number" ? this.client.setEx(this.fullKey(t), this.options.ttl, e) : this.client.set(this.fullKey(t), e), t;
}
/**
* Expire key after a specific ttl or date
*/
async expire(t, i, e) {
typeof i == "number" ? this.client.expire(t, i, e) : this.client.expireAt(t, i, e);
}
/**
* Retrieves data from Redis and validates it against the template.
*
* @param key key in Redis (without a namespace).
* @param force if true, throws an error instead of returning null when
* data is missing or fails validation.
* @returns parsed data matching the template, or null if not in force mode.
*/
async get(t, i) {
await this.waitWhenReady();
const e = await this.client.get(this.fullKey(t));
if (!e) {
if (i) throw new Error(`Key ${t} not found`);
return null;
}
return this.options.readOnce && this.delete(t), l(Buffer.isBuffer(e) ? e.toString("utf8") : e);
}
/**
* Deletes the specified key from Redis.
*
* @param key key to delete (without a namespace).
* @returns result of the deletion operation.
*/
async delete(t) {
return await this.waitWhenReady(), this.client.del(this.fullKey(t));
}
/**
* Retrieves data from Redis and executes a callback if the data exists
* and matches the template.
*
* @param key key to retrieve.
* @param callback function to execute with the retrieved payload.
* @returns callback result or null if no data.
*/
async with(t, i) {
const e = await this.get(t);
return e ? i(e) : null;
}
/**
* Provides a method to set a value with an auto-generated random key.
*/
get randomKey() {
const t = this;
return {
/**
* Compresses and writes the value under a random key.
*
* @param value value to store.
* @returns generated a Redis key (without a namespace).
*/
set(i) {
const e = a.randomBytes(t.randomKeyBytesCount).toString("base64url");
return t.set(e, i);
}
};
}
fullKey(t) {
return this.options.namespace + "+" + t;
}
async waitWhenReady() {
if (this.client.isReady) return;
let t = 0;
return new Promise((i) => {
const e = setInterval(() => {
if (t += 1, t > 300)
throw clearInterval(e), new Error("Redis initialization timeout, if this is only error you encounter, double-check that Sigil app starts listening BEFORE any redis interactions");
this.client.isReady && (clearInterval(e), i());
}, 100);
});
}
}
class y {
client;
#t;
constructor(t, i) {
this.client = t, this.#t = i || {};
}
/**
* Defines a new data schema with the given template and options.
*
* @param options schema options.
* @returns a RedisSchema instance for data operations.
*/
model(t) {
return new c(this.client, t, this.#t);
}
/**
* Disconnects and cleans up the Redis client.
*/
destroy() {
this.client.destroy();
}
async connect() {
return await this.client.connect(), this.client;
}
}
export {
y as default
};