@kurai-io/redis
Version:
Schema-based abstraction layer for redis database interactions
165 lines (164 loc) • 4.97 kB
JavaScript
import { seal as r } from "@sigiljs/seal";
import { createHash as o } from "node:crypto";
import l from "./seal-json-parser.mjs";
import * as c from "crypto";
class h {
/** Original redis client */
client;
template;
options;
randomKeyBytesCount = 16;
/**
* @param client redis client instance
* @param template seal schema template
* @param options schema options
* @param config
*/
constructor(t, i, e, n) {
const s = e?.namespace ?? o("shake256", { outputLength: n?.randomNamespaceLength || 8 }).update(JSON.stringify([r.exportMetadataOf(i), e || {}])).digest("base64url");
this.client = t, this.options = {
ttl: e?.ttl,
readOnce: e?.readOnce ?? !1,
namespace: s
}, this.template = i, n?.randomKeyBytesCount && (this.randomKeyBytesCount = n.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 = JSON.stringify(this.serialize(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 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 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, this.template);
}
/**
* Deletes the specified key from Redis.
*
* @param key key to delete (without 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 Redis key (without namespace).
*/
set(i) {
const e = c.randomBytes(t.randomKeyBytesCount).toString("base64url");
return t.set(e, i);
}
};
}
fullKey(t) {
return this.options.namespace + "+" + t;
}
serialize(t) {
if (typeof t != "object") return t;
const i = Object.keys(r.exportMetadataOf(this.template).shape || {});
if (i.length) {
const e = [];
for (const n of i) {
const s = t[n];
e.push(s && typeof s == "object" ? this.serialize(s) : s);
}
return e;
}
return Object.values(t).map((e) => e && typeof e == "object" ? this.serialize(e) : e);
}
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 p {
client;
#t;
constructor(t, i) {
this.client = t, this.#t = i || {};
}
/**
* Creates a new object schema template compatible with Seal.
*
* @param template schema definition.
* @returns an ObjectSchema based on the provided template.
*/
template(t) {
return r.object(t);
}
/**
* Defines a new data schema with the given template and options.
*
* @param template redis schema template.
* @param options schema options.
* @returns a RedisSchema instance for data operations.
*/
model(t, i) {
return new h(this.client, t, i, this.#t);
}
/**
* Disconnects and cleans up the Redis client.
*/
destroy() {
this.client.destroy();
}
async connect() {
return await this.client.connect(), this.client;
}
}
export {
p as default
};